Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
313 views
in Technique[技术] by (71.8m points)

c# - Newtonsoft.Json issue with deserialising relational model

I've a simple relational model with a Parent and Child relation as follows:

    public class Parent
    {
        public Parent(int id)
        {
            Id = id;
        }

        public int Id { get; private set; }
        public IList<Child> Children { get; set; } = new List<Child>();
    }

    public class Child
    {
        public Parent Parent { get; set; }
    }

I create a small object graph consisting of a list of two children who share the same parent:

    var parent = new Parent(1);
    var child1 = new Child {Parent = parent};
    var child2 = new Child {Parent = parent};
    parent.Children.Add(child1);
    parent.Children.Add(child2);

    var data = new List<Child> {child1, child2};

Next, I serialize this using SerializeObject:

    var settings = new JsonSerializerSettings
    {
        PreserveReferencesHandling = PreserveReferencesHandling.All
    };
    var json = Newtonsoft.Json.JsonConvert.SerializeObject(data, Formatting.Indented, settings);

As far I can see, the resulting json looks fine:

{
  "$id": "1",
  "$values": [
    {
      "$id": "2",
      "Parent": {
        "$id": "3",
        "Id": 1,
        "Children": {
          "$id": "4",
          "$values": [
            {
              "$ref": "2"
            },
            {
              "$id": "5",
              "Parent": {
                "$ref": "3"
              }
            }
          ]
        }
      }
    },
    {
      "$ref": "5"
    }
  ]
}

However, when I deserialize the json I don't get the expected object because for the second child the Parent property is null, causing the second assertion to fail:

    var data2 = Newtonsoft.Json.JsonConvert.DeserializeObject<IList<Child>>(json, settings);
    Debug.Assert(data2[0].Parent != null);
    Debug.Assert(data2[1].Parent != null);

Without the constructor of Parent this problem does not occur and the Parent property of the second child has the expected value.

Any ideas what this could be?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Parameterized constructors don't work well with PreserveReferencesHandling because there is an inherent chicken-and-egg problem: the necessary information to pass to the constructor may not be loaded from the JSON by the time the deserializer needs to create the object. So there needs to be a way for it to create an empty object and then fill in the appropriate information later.

This is a known limitation and is documented:

Note:
References cannot be preserved when a value is set via a non-default constructor. With a non-default constructor, child values must be created before the parent value so they can be passed into the constructor, making tracking reference impossible. ISerializable types are an example of a class whose values are populated with a non-default constructor and won't work with PreserveReferencesHandling.

To work around the problem with your model you could create a private, parameterless constructor in your Parent class and mark it with [JsonConstructor]. Then mark the Id property with [JsonProperty], which will allow Json.Net to use the private setter. So you would have:

public class Parent
{
    public Parent(int id)
    {
        Id = id;
    }

    [JsonConstructor]
    private Parent()
    { }

    [JsonProperty]
    public int Id { get; private set; }
    public IList<Child> Children { get; set; } = new List<Child>();
}

As an aside, since none of your Lists are shared amongst the objects, you could use PreserveReferencesHandling.Objects instead of PreserveReferencesHandling.All. This will make the JSON a little smaller, but will still preserve the references you care about.

Working demo here: https://dotnetfiddle.net/cLk9DM


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...