Let's say I have an instance of a class like this which I want to serialize to JSON:
public class MyClass
{
public MyClass() { }
private List<string> texts;
public List<string> Texts
{
get
{
return new List<string> { "You got me!" };
}
set
{
texts = value;
Console.WriteLine("Setting property!");
}
}
}
I want to to make sure the set accessor for "Texts" is called during deserialization, i.e make sure that the private property "texts" in MyClass gets set. However, if I run a small test case:
public void TestMyClass()
{
var myClass = new MyClass();
var jsonBefore = JsonConvert.SerializeObject(myClass);
var jsonAfter = JsonConvert.DeserializeObject<MyClass>(jsonBefore);
}
The set method never gets called, i.e private "texts" is null after deserialization. How can I make sure that the set accessor in public "Texts" gets called during deserialization?
The first reason is because you use newtonsoft json serializer that is quite different from built in .net one.
var json = new JavaScriptSerializer().Serialize(myClass);
var obj = new JavaScriptSerializer().Deserialize<MyClass>(json);
This code is going to work fine.
The second reason is in your get property and how it's processed by newtonsoft json serializer, you have default list creating (it is better to put it into constructor as as it made now doesn't make any sense)
If you still want to use your get as it implemented now, use JsonSerializerSettings.ObjectCreationHandling setting for customizing object creating,
var jsonBefore = JsonConvert.SerializeObject(myClass);
var jsonAfter = JsonConvert.DeserializeObject<MyClass>(jsonBefore, new JsonSerializerSettings
{
ObjectCreationHandling = ObjectCreationHandling.Replace
});
If you don't want to set null in Texts property then you can set default value to it.
Like private List<string> texts = new List<string>(){ "Default Value" };
and instead of
get
{
return new List<string> { "You got me!" };
}
you should use
get
{
return texts;
}
Serialization is the process of translating data structures or object state into a format that can be stored (in our case it's json).
Object state consists of fields values, all methods or other behavioral logic is simply ignored.
Properties are basically methods that work with backing fields so they're not used on serialization / deserialization because they do not represent object's state.
In our case field texts is serialized with use of reflection. Then it is deserialized directly and it is possible that serializer doesn't even know that property exists.
Related
In System.Text.Json how does the "setter" work when using properties that are of type List? it is not how i would expect, especially in relation to the setter for a string as example.
Here's what I mean:
public class Program
{
public static void Main()
{
var json = #"
{
""doot"": ""bloop bloop"",
""bars"":
[
{
""id"": 1
},
{
""id"": 2
}
]
}
";
var obj = JsonSerializer.Deserialize<Foo>(json);
Console.WriteLine($"obj.Doot: {obj.Doot}");
if (obj.Bars == null)
Console.WriteLine("Bars is null.");
else
Console.WriteLine($"obj.Bars.Count: {obj.Bars.Count}");
}
public class Foo
{
private string _doot;
[JsonPropertyName("doot")]
public string Doot
{
get => _doot;
set
{
Console.WriteLine($"Setting Doot to {value}");
_doot = value;
}
}
private List<Bar> _bars;
[JsonPropertyName("bars")]
public List<Bar> Bars
{
get => _bars;
set
{
Console.WriteLine($"Bars Value count during setter: {value.Count}");
_bars = value;
}
}
}
public class Bar
{
[JsonPropertyName("id")]
public int Id {get;set;}
}
}
output:
Setting Doot to bloop bloop
Bars Value count during setter: 0
obj.Doot: bloop bloop
obj.Bars.Count: 2
In the above, when the Doot setter fires, value has the value being set to the backing field _doot. When the Bars setter fires, value has something in it, but it is not the two Bar instances, as shown by the Console.WriteLine statement within the setter, which reports a 0 count.
If I needed to do some logic based on the values being set within the property Bars, how can I accomplish that if value does not have the information?
fiddle for reference:
https://dotnetfiddle.net/7z2O9S
EDIT:
it was pointed out to me elsewhere (caschw if you're here thanks) that the list is likely initialized as an empty list, then list.Add or list.AddRange could be used after initialization (i'm unclear which is used under the covers of System.Text.Json); meaning the setter would only ever be done once unless I change the reference to the list. This makes sense, but still, how would I hook into running some logic whenever the collection of Bars is changed?
The assumption in your edit is correct. The way the list is deserialized is that first the list object is being created, and then the individual elements are added. If you think about how JSON works, then it makes sense for a parser and deserializer to work that way:
When the parser encounters a [ it knows that a JSON array is being started. So when that object is to be deserialized into a .NET collection type, then it can already create that collection at that moment. After all, the deserializer knows the target type it should deserialize to.
So the deserializer creates the list object, and then starts to deserialize the items inside. In your case, these are Bar objects. So it constructs those, sets the properties, and—once the object is completed—finally adds them to the list.
You can actually see this in the source. There are a number of collection converters, depending on what target type you have, but they all inherit from IEnumerableDefaultConverter which basically has the behavior I described above. For List<T>, the actual work then happens in the ListOfTConverter which basically just initializes a new list and calls Add for each item.
If I needed to do some logic based on the values being set within the property Bars, how can I accomplish that if value does not have the information?
I don’t think you should. You should see the deserialization process as a single operation and not interfere with it. I would suggest you to do that afterwards, e.g. as a post-processing step after deserialization.
I am confused on how XmlSerializer works behind the scenes. I have a class that deserializes XML into an object. What I am seeing is for the following two elements that are NOT part of the Xml being deserialized.
[XmlRootAttribute("MyClass", Namespace = "", IsNullable = false)]
public class MyClass
{
private string comments;
public string Comments
{
set { comments = value; }
get { return comments; }
}
private System.Collections.Generic.List<string> tests = null;
public System.Collections.Generic.List<string> Tests
{
get { return tests; }
set { tests = value; }
}
}
Let's take the following XML as an example:
<MyClass>
<SomeNode>value</SomeNode>
</MyClass>
You notice that Tests and Comments are NOT part of the XML.
When this XML gets deserialized Comments is null(which is expected) and Tests is an empty list with a count of 0.
If someone could explain this to me it would be much appreciated. What I would prefer is that if <Tests> is missing from the XML then the list should remain null, but if a (possibly empty) node <Tests /> is present then the list should get allocated.
What you are observing is that members referring to modifiable collections such as List<T> are automatically pre-allocated by XmlSerializer at the beginning of deserialization. I am not aware of any place where this behavior is documented. It may be related to the behavior described in this answer to XML Deserialization of collection property with code defaults, which explains that, since XmlSerializer supports adding to get-only and pre-allocated collections, if a pre-allocated collection contains default items then the deserialized items will be appended to it - possibly duplicating the contents. Microsoft may simply have chosen to pre-allocate all modifiable collections at the beginning of deserialization as the simplest way of implementing this.
The workaround from that answer, namely to use a surrogate array property, works here as well. Since an array cannot be appended to, XmlSerializer must accumulate all the values and set them back when deserialization is finished. But if the relevant tag is never encountered, XmlSerializer apparently does not begin accumulating values and so does not call the array setter. This seems to prevent the default pre-allocation of collections that you don't want:
[XmlRootAttribute("MyClass", Namespace = "", IsNullable = false)]
public class MyClass
{
private string comments;
public string Comments
{
set { comments = value; }
get { return comments; }
}
private System.Collections.Generic.List<string> tests = null;
[XmlIgnore]
public System.Collections.Generic.List<string> Tests
{
get { return tests; }
set { tests = value; }
}
[XmlArray("Tests")]
public string[] TestsArray
{
get
{
return (Tests == null ? null : Tests.ToArray());
}
set
{
if (value == null)
return;
(Tests = Tests ?? new List<string>(value.Length)).AddRange(value);
}
}
}
Sample .Net fiddle showing that Tests is allocated only when appropriate.
When you apply [System.Xml.Serialization.XmlElement(IsNullable = true)] to the property, the List will be null after deserialization.
Another possibility is to use the "magic" "Specified" suffix:
public bool TestsSpecified {get;set;}
If you have a serialized field/property XXX and a boolean property XXXSpecified, then the bool property is set according to whether or not the main field/property was set.
We wound up here after a google search for the same issue.
What we ended up doing was checking for Count == 0, after deserialization, and manually setting the property to null;
...
var varMyDeserializedClass = MyXmlSerializer.Deserialize(new StringReader(myInput));
if (varMyDeserializedClass.ListProperty.Count == 0)
{
varMyDeserializedClass.ListProperty = null;
}
...
It's a cheap workaround, but provides the expected result and is useful to avoid refactoring or redesign.
I am trying to dynamically instantiate a class from my visual studio solution based on a JSON string.
Before I describe my exact issue I want to give an example of what I want to achieve.
Say I have the following JSON :
{
"Type": "AutoIncrementTag,
"StartFrom": 0,
"Skip": 10,
"LeadingZero": false
}
So from that Json, I want to find the class called "AutoIncrementTag" and instantiate it, setting its "StartFrom", "Skip" and "LeadingZero" parameters to the correspoding values.
Note 1: I have a couple of theese "Tag" classes and I want to instantiate a different on the "Type" attribute in my Json string.
Note 2: My Json string will contain more than 1 of these class "descriptions" (I believe they are called JSON Objects but I'm not too familliar with the JSON format just yet)
Note 3: I am using Newtonsoft.Json for all the Json parsing/converting.
So, now for my issue.
I managed to get the Type property using
JObject.Parse(myJsonString).GetValue("Type").ToString();
However, how would I go about getting all the other values, since they will be different depending on what Type I have? (I need a way to dynamically iterate and get the values of the other properties.)
And second, how do I then map these properties to a C# object.
I thought about using
Activator.CreateInstance(Type type, object[] args)
But how can I (dynamically) get an object[] from the properties described in my json format.
JSON.Net (i.e. Newtonsoft.Json) does this for you already. For example, lets start off with a basic class:
public class Thing
{
public int SomeValue { get; set; }
public string AnotherValue { get; set; }
}
And an instance of it:
var thing = new Thing { SomeValue = 5, AnotherValue = "blah" };
We can deserialise with a custom settings object, specifically setting the TypeNameHandling property
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
var json = JsonConvert.SerializeObject(thing, settings);
Which will give output something like this:
{
"$type":"Thing, Namespace",
"SomeValue": 5,
"AnotherValue": "blah"
}
And to get it back into the right kind of object, just use the same settings:
var anotherThing = JsonConvert.DeserializeObject(json, settings);
I'm only using Code Analysis for cleaning, organizing and ensuring these changes are globally performed for all instances of a particular warning. I'm down to the final, and it's CA2227.
CA2227 Collection properties should be read only Change '' to be
read-only by removing the property setter.
Note this is for mapping of EDI documents. These classes are to represent a whole or part of an EDI document.
public class PO1Loop
{
public SegmentTypes.PO1LoopSegmentTypes.PO1 PO1 { get; set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID1> PIDRepeat1 { get; set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID2> PIDRepeat2 { get; set; }
public SegmentTypes.PO1LoopSegmentTypes.PO4 PO4 { get; set; }
/* Max Use: 8 */
public Collection<SegmentTypes.PO1LoopSegmentTypes.ACK> ACKRepeat { get; set; }
}
You can see all of the Collection properties will give me this warning, and there are hundreds of them. When using the above class I instantiate it without any data. Then externally I add the data and set each individual variable through its public accessor. I do not instantiate this class with all the data prepared and passed using a constructor method (IMO for the size these can reach it can easily wreak havoc on the eyes). When complete and all properties are assigned the class as a whole is then used to generate that part of a document it represents.
My question is, for the usage described above, what would be a better approach for setting this up correctly? Do I keep the public accessors and suppress this warning entirely, or is there a entirely different solution that would work?
Here's what MSDN says about the error, and also how you can avoid it.
Here's my take on the issue.
Consider, the following class:
class BigDataClass
{
public List<string> Data { get; set; }
}
This class will throw that exact same issue. Why? Because Collections do not need a setter. Now, we can do anything with that object: assign Data to an arbitrary List<string>, add elements to Data, remove elements from Data, etc. If we remove the setter, we only lose the ability to directly assign to that property.
Consider the following code:
class BigDataClass
{
private List<string> data = new List<string>();
public List<string> Data { get { return data; } } // note, we removed the setter
}
var bigData = new BigDataClass();
bigData.Data.Add("Some String");
This code is perfectly valid and in fact the recommended way to do things. Why? Because the List<string> is a reference to a memory location, that contains the remainder of the data.
Now, the only thing you cannot now do with this, is directly set the Data property. I.e. the following is invalid:
var bigData = new BigDataClass();
bigData.Data = new List<string>();
This is not necessarily a bad thing. You'll notice that on many .NET types this model is used. It's the basics of immutability. You usually do not want direct access to the mutability of Collections, as this can cause some accidental behavior that has strange issues. This is why Microsoft recommends you omit setters.
Example:
var bigData = new BigDataClass();
bigData.Data.Add("Some String");
var l2 = new List<string>();
l2.Add("String 1");
l2.Add("String 2");
bigData.Data = l2;
Console.WriteLine(bigData.Data[0]);
We might be expecting Some String, but we'll get String 1. This also means that you cannot reliably attach events to the Collection in question, so you cannot reliably determine if new values are added or values are removed.
A writable collection property allows a user to replace the collection with a completely different collection.
Essentially, if you only ever need to run the constructor, or assignment, once, then omit the set modifier. You won't need it, direct assignment of collections is against best-practices.
Now, I'm not saying never use a setter on a Collection, sometimes you may need one, but in general you should not use them.
You can always use .AddRange, .Clone, etc. on the Collections, you only lose the ability of direct assignment.
Serialization
Lastly, what do we do if we wish to Serialize or Deserialize a class that contains our Collection without a set? Well, there is always more than one way to do it, the simplest (in my opinion) is to create a property that represents the serialized collection.
Take our BigDataClass for example. If we wished to Serialize, and then Deserialize this class with the following code, the Data property would have no elements.
JavaScriptSerializer jss = new JavaScriptSerializer();
BigDataClass bdc = new BigDataClass();
bdc.Data.Add("Test String");
string serd = jss.Serialize(bdc);
Console.WriteLine(serd);
BigDataClass bdc2 = jss.Deserialize<BigDataClass>(serd);
So, to fix this, we can simply modify our BigDataClass a bit to make it use a new string property for Serialization purposes.
public class BigDataClass
{
private List<string> data = new List<string>();
[ScriptIgnore]
public List<string> Data { get { return data; } } // note, we removed the setter
public string SerializedData { get { JavaScriptSerializer jss = new JavaScriptSerializer(); return jss.Serialize(data); } set { JavaScriptSerializer jss = new JavaScriptSerializer(); data = jss.Deserialize<List<string>>(value); } }
}
Another option is always the DataContractSerializer (which is really a better option, in general.) You can find information about it on this StackOverflow question.
With current VS2019 we can simply do this:
public List<string> Data { get; } = new List<string>();
This satisfies CA2227 and can be serialized/deserialized.
The deserialization works because List<> has an "Add" method, and the serializer knows how to handle a read-only collection property with an Add method (the property is read-only but not the elements) (I use Json.Net, other serializers may behave differently).
Edit:
As pointed out it should be "=" and not "=>" (compiler will prevent you using "=>"). If we used "public List Data => new List();" then it would create a new list every time the property was accessed which is not what we want either.
Edit:
Note that this will NOT work if the type of the property is an interface, such as IList
Edit:
I think the handling of interfaces is determined by the serializer used. The following works perfectly. I'm sure all common serializers know how to handle ICollection. And if you have some custom interface that does not implement ICollection then you should be able to configure the serializer to handle it, but in that case CA2227 probably won't be triggered making it irrelevant here. (As it is a read-only property you have to assign a concrete value within the class so it should always be serializing and de-serializing a non-null value)
public class CA2227TestClass
{
public IList Data { get; } = new List<string>();
}
[TestMethod]
public void CA2227_Serialization()
{
var test = new CA2227TestClass()
{
Data = { "One", "Two", "Three" }
};
var json = JsonConvert.SerializeObject(test);
Assert.AreEqual("{\"Data\":[\"One\",\"Two\",\"Three\"]}", json);
var jsonObject = JsonConvert.DeserializeObject(json, typeof(CA2227TestClass)) as CA2227TestClass;
Assert.IsNotNull(jsonObject);
Assert.AreEqual(3, jsonObject.Data.Count);
Assert.AreEqual("One", jsonObject.Data[0]);
Assert.AreEqual("Two", jsonObject.Data[1]);
Assert.AreEqual("Three", jsonObject.Data[2]);
Assert.AreEqual(typeof(List<string>), jsonObject.Data.GetType());
}
💡 Alternative Solution 💡
In my situation, making the property read-only was not viable because the whole list (as a reference) could change to a new list.
I was able to resolve this warning by changing the properties' setter scope to be internal.
public List<Batch> Batches
{
get { return _Batches; }
internal set { _Batches = value; OnPropertyChanged(nameof(Batches)); }
}
Note one could also use private set...
The hint's (achilleas heal) of this warning seems really pointed to libraries for the documentation says (Bolding mine):
An externally visible writable property is a type that implements
System.Collections.ICollection.
For me it was, "Ok, I won't make it viewable externally...." and internal was fine for the app.
Thanks to #Matthew, #CraigW and #EBrown for helping me understanding the solution for this warning.
public class PO1Loop
{
public SegmentTypes.PO1LoopSegmentTypes.PO1 PO1 { get; set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID1> PIDRepeat1 { get; private set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID2> PIDRepeat2 { get; private set; }
public SegmentTypes.PO1LoopSegmentTypes.PO4 PO4 { get; set; }
/* Max Use: 8 */
public Collection<SegmentTypes.PO1LoopSegmentTypes.ACK> ACKRepeat { get; private set; }
public PO1Loop()
{
PIDRepeat1 = new Collection<SegmentTypes.PO1LoopSegmentTypes.PID1>();
PIDRepeat2 = new Collection<SegmentTypes.PO1LoopSegmentTypes.PID2>();
ACKRepeat = new Collection<SegmentTypes.PO1LoopSegmentTypes.ACK>();
}
}
When wanting to assign data to the collection types use AddRange, Clear or any other variation of method for modifying a collection.
Only while binding DTO, you need to suppress warnings.
otherwise a custom ModelBinder is required custom ModelBinder to bind collections.
quoting the rule documentation:
When to suppress warnings
You can suppress the warning if the property is part of a Data Transfer Object (DTO) class.
Otherwise, do not suppress warnings from this rule.
https://learn.microsoft.com/pt-br/visualstudio/code-quality/ca2227?view=vs-2019
DTOs often require serialization and deserialization. Thus, they are required to be mutable.
Having to create an alternate backing property is a pain.
Simply change the property type from List<string> to IReadOnlyList<string> then this works as expected without CA2227.
The collection is set via the property but you can also cast to List<string> if you wish to append or delete items.
class Holder
{
public IReadOnlyList<string> Col { get; set; } = new List<string>();
}
var list = new List<string> { "One", "Two" };
var holder = new Holder() { Col = list } ;
var json = JsonConvert.SerializeObject(holder);
// output json {"Col":["One","Two"]}
var deserializedHolder = JsonConvert.DeserializeObject<Holder>(json);
I had to fix some of the CA2227 violations, so i had to add the "readonly" keyword to the collection field and then of course, had to remove the setter property. Some code that have used the setter, just created a new collection object which initially was empty. This code sure did not compile so i had to add a SetXxx() method in order to realize the missing setter's functionality. I did it like this:
public void SetXxx(List<string> list)
{
this.theList.Clear();
this.theList.AddRange(list);
}
The code of callers using the setter has been replaced with a call to the method SetXxx().
Instead of creating a complete new list, the existing list now will be cleared and filled with new items from another list, passed in as a parameter. The original list, due to the fact it is readonly and created only once, will always remain.
I believe this is also a good way to avoid that the garbagae collector has to delete old objects that got out of scope and second, to create new collection objects although there is already one.
As an addition to Der Kommissar's excellent answer.
Starting with .NET 5 (C# 9.0) there are init-only properties. These properties are only settable under specific circumstances, see here for reference.
The following example should not raise a warning CA2227, yet still allow for the collection being set during object initialization.
using System.Collections.Generic;
namespace BookStore
{
public class BookModel
{
public ICollection<string> Chapters { get; init; }
}
}
Note that the current version of the .NET SDK still raises a warning when using the built-in analyzer (not the NuGet package). This is a known bug and should be fixed in the future.
To cover all the possible scenarios to resolve CA2227 error:
This covers the Entity relationship mapping when we use Entity Framework.
class Program
{
static void Main(string[] args)
{
ParentClass obj = new ParentClass();
obj.ChildDetails.Clear();
obj.ChildDetails.AddRange();
obj.LstNames.Clear();
obj.LstNames.AddRange();
}
}
public class ChildClass
{ }
public class ParentClass
{
private readonly ICollection<ChildClass> _ChildClass;
public ParentClass()
{
_ChildClass = new HashSet<ChildClass>();
}
public virtual ICollection<ChildClass> ChildDetails => _ChildClass;
public IList<string> LstNames => new List<string>();
}
Consider the class:
public class foo
{
public object newObject
{
get
{
return new object();
}
}
}
According to MSDN:
Properties are members that provide a flexible mechanism to read,
write, or compute the values of private fields. Properties can be used
as though they are public data members, but they are actually special
methods called accessors. This enables data to be accessed easily
And:
Properties enable a class to expose a public way of getting and
setting values, while hiding implementation or verification code.
A get property accessor is used to return the property value, and a
set accessor is used to assign a new value. These accessors can have
different access levels. For more information, see Accessor
Accessibility.
The value keyword is used to define the value being assigned by the
set indexer.
Properties that do not implement a set method are read only.
while still providing the safety and flexibility of methods.
Does this therefore mean that at some point in time the value of the newObject property has a reference to the returned new object?
edit removed readonly from property
edit2 also would like to clarify that this is not the best use for a property but its done to try and illustrate the question more effectively.
You return a new object on each access to the property and that is not the expected behavior of properties. Instead you should return the same value each time (e.g. a value stored in a field). A property getter is simply glorified syntax for a method that returns a value. Your code compiles into something like this (the compiler creates a getter by prefixing the property name with get_ which is then emitted as IL):
public class foo
{
public object get_newObject()
{
return new object();
}
}
Each call to the getter will create a new object that foo doesn't know about or has access to.
Does this therefore mean that at some point in time the value of the newObject property has a reference to the returned new object?
No.
Property using a backing field:
class Foo {
readonly Object bar = new Object();
public Object Bar { get { return this.bar; } }
}
Using automatic properties:
class Foo {
public Foo() {
Bar = new Object();
}
public Object Bar { get; private set; }
}
A property is accessed using the same easy syntax as a public field. However, by using a property you can add code to the getter and the setter allowing you to do stuff like lazy loading in the getter or validation in the setter (and much more).
Under the hood, your property will simply be calling a function named get_newObject() that looks like this:
public object get_newObject()
{
return new object();
}
Since that is the case, it will always return a new object every time it is invoked.
If you want to retain a reference to the object, then I would recommend creating a private field to hold the data and having the property access that field, like so:
private object myObject;
public object newObject
{
if(myObject == null)
{
myObject = new object();
}
return myObject;
}
Since your property doesn't define set, and your field is private, newObject is basically eradonly outside of the containing class.
Properties in C# are "syntactic sugar". The code within the get block of a property is in fact put into a hidden get_PropertyName() method, and the set block into a hidden set_PropertyName() method. In the case of your code, the following method will be created:
public object get_newObject()
{
return new object();
}
You can see these hidden methods if you view the compiled assembly using Reflector, or ildasm.
When the property is used, the C# compiler converts any "get" accesses of your property into calls of the get_newObject() method. As an example:
If you were to write the following:
var foo = new foo();
var aNewObject = foo.newObject;
The compiler would convert that to:
var foo = new foo();
var aNewObject = foo.get_newObject();
So, in answer to your other question, the newly created object returned when someone "gets" the property won't be stored within your foo instance, the caller will simply get a new object every time.
Not exactly. Properties are just syntactic sugar so that you don't have to write accessor methods (like Java).
So this:
private int _myInteger;
public int MyInteger
{
get { return _myInteger; }
set { _myInteger = value; }
}
is equivilant to this:
private int _myInteger;
public int GetMyInteger()
{
return _myInteger;
}
public void SetMyInteger(int value)
{
_myInteger = value;
}
and it gets better with this, which is also equivilant:
public int MyInteger { get; set; }