Home > Back-end >  Why the .Net JsonConvert.DeserializeObject can't set private properties but JsonConvert.Populat
Why the .Net JsonConvert.DeserializeObject can't set private properties but JsonConvert.Populat

Time:12-14

so in order to use private setters with Json.Net, the [JsonProperty] attribute had to be used, however, it seems that private setters don't cause any issue if we use JsonConvert.PopulateObject,

Example:

If we have Class A that will be serialized

public class Class_A
{
 [JsonProperty] 
 Class_B m_subData = new Class_B("Default data");
}

and one of its members is Class B that has a property with a private setter:

public class Class_B{

    public string Data {get;private set;}
    public Class_B(string data){
        Data = data;
    }
}

Then this deserialization attempt will fail and classAObj will have a classB member that contains the "Default data" instead of whatever the Json content actually has.

public class DataManager{

    public void DeserializeData(string jsonData){

        var classAObj= (Class_A) JsonConvert.DeserializeObject(jsonData, dataType);
    }
}

which is expected and we have to add the [JsonProperty] attribute to class_B properties that has private setters.

What i don't understand is why this works:

public class DataManager{

    public void DeserializeData(string jsonData){

        var classAObj = (Class_A) Activator.CreateInstance(dataType);
        var settings = new JsonSerializerSettings
            {
                ObjectCreationHandling = ObjectCreationHandling.Replace
            };
         JsonConvert.PopulateObject(jsonData, classAObj, settings);
    }
        
}

So despite still having private setters in Class_B and NOT having the [JsonProperty] attribute, the JsonConvert.PopulateObject will indeed populate the object with the correct data, the only difference is that its an "existing object", but, how is it able to set its private properties without going through the constructor ?

Thank you!

CodePudding user response:

By default, the deserializer ignore private field and property with private setter. To specify the deserializer to include private field, You have to decorate the field/property with the attribute [JsonProperty]. Other possibility it's declare a constructor that has a parameter matching the property in the json.

Like Class_B in your example :

public class Class_B
{
    public string Data { get; private set; } = "Original data";
    public Class_B(string data)
    {
        Data = data;
    }
}

When deserialized, the constructor is called with parameter value from json :

var b = JsonConvert.DeserializeObject<Class_B>(@"{""data"":""Modified data""}");
Console.WriteLine(b.Data);
// Output : Modified data

Then why when Class_A is deserialized, the Class_B's constructor isn't called like below?

Because, by default the deserializer reuse existing instance. The deserializer reuse the instance of Class_B created by Class_A, then it don't call the constructor. As the property Data has private setter, it's ignored by the deserializer.

ObjectCreationHandling.Replace specifies to the deserializer to always create a new instance (never reuse existing).

With this setting, the deserializer don't reuse the instance of Class_B created by Class_A, but it create a new one and call the constructor of Class_B.

The desieralizer don't populate the property Data, it's the constructor that initialize the property.

To finish, you can also use this settings with JsonConvert.DeserializeObject and get the same result that your example with JsonConvert.PopulateObject :

using Newtonsoft.Json;

var settings = new JsonSerializerSettings {
    ObjectCreationHandling = ObjectCreationHandling.Replace
};
var a2 = JsonConvert.DeserializeObject<Class_A>(@"{""m_subData"":{""Data"":""Modified data""}}", settings);
Console.WriteLine(a2.GetData());
// Output : Modified data

public class Class_A
{
    [JsonProperty]
    Class_B m_subData = new Class_B("Default data");
    public string GetData() => m_subData.Data;
}

public class Class_B
{
    public string Data { get; private set; }
    public Class_B(string data)
    {
        Data = data;
    }
}
  • Related