简体   繁体   中英

Json.NET deserialization and property setter side-effects

A question arose when I came across a property setter which had side-effects (it was bad design against Separation of concerns ).

The following question is a bit theoretical, and the problem should ideally be fixed by removing the side-effects from the property setters.

But if modifying the DTO class is not an option, is there any mechanism to prevent JSON deserialization from causing side-effects when using Json.NET?

Problem scenario:

Shown below are Myclass and two JSON snippets.

When I call JsonConvert.Deserialize<MyClass>(json) , I get different results depending on the order of the JSON properties.

Data transfer class:

public class MyClass
{
    private string _evilField1;
    public string EvilField1
    {
        get
        {
            return _evilField1;
        }
        set
        {
            _evilField1 = value;
            Info = "EvilField1.Set messed Info";
        }
    }

    private string _evilField2;
    public string EvilField2
    {
        get
        {
            return _evilField2;
        }
        set
        {
            _evilField2 = value;
            Info = "EvilField2.Set messed Info";
        }
    }

    public string Info { get; set; }
}

Input data

{
  "EvilField1": "Foo",
  "EvilField2": "Foo",
  "Info": "Initial value"
}

{
  "EvilField1": "Foo",
  "Info": "Initial value",
  "EvilField2": "Foo"
}

Deserialized data

The first JSON does not trigger the side-effect, but the second JSON brings nasty output:

{
  "EvilField1": "Foo",
  "Info": "Initial value",
  "EvilField2": "Foo"
}

{
  "EvilField1": "Foo",
  "EvilField2": "Foo",
  "Info": "EvilField2.Set messed Info"
}

You could create a custom JsonConverter for the problematic class. This will allow you to populate it in whatever way you need to in order to work around the unwanted side effects.

public class MyClassConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(MyClass);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null) return null;

        JObject obj = JObject.Load(reader);

        // Populate the "Info" field last so it will not be overwritten
        return new MyClass
        {
            EvilField1 = (string)obj["EvilField1"],
            EvilField2 = (string)obj["EvilField2"],
            Info = (string)obj["Info"]
        };
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

To use the converter you can either add a [JsonConverter] attribute the problematic class...

[JsonConverter(typeof(MyClassConverter))]
public class MyClass
{
    ...
}

... or you can pass an instance of the converter to the DerserializeObject() method via settings:

var settings = new JsonSerializerSettings();
settings.Converters.Add(new MyClassConverter());

var obj = JsonConvert.DeserializeObject<MyClass>(json, settings);

Fiddle: https://dotnetfiddle.net/Ob4dPA

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM