0

I have a Serializer/Deserializer that use a references PreserveReferencesHandling = PreserveReferencesHandling.All.

The issue, is that I have circular references.

Here is a very simple example.

class Node
{
    public Node(object value)
    {
        Value = value;
    }
    public object Value { get; set; }
    public Node Left { get; set; }
    public Node Right { get; set; }
}

My test scenario is:

var obj = new Node("o")
{
    Left = new Node("oL"),
    Right = new Node("oR")
};
obj.Right.Right = obj; // Circular reference!

When I deserialize, i have the following IReferenceResolver

private class InternalReferenceResolver : IReferenceResolver
{
    private readonly Deserializer _par;

    public InternalReferenceResolver(Deserializer par)
    {
        _par = par;
    }

    public object ResolveReference(object context, string reference)
    {
        object refObject;
        if (!_par._processed.TryGetValue(reference, out refObject))
        {
            refObject = _par.DeserializeObject(reference);
        }
        return refObject;
    }

    public string GetReference(object context, object value)
    {
        throw new NotSupportedException("Only for Serialization");
    }

    public bool IsReferenced(object context, object value)
    {
        return false;
    }

    public void AddReference(object context, string reference, object value)
    {
        _par._processed.Add(reference, value);
    }
}    

As you can see, when JSON.NET inform me of a new ref->object (via AddReference()) I add it to a dictionary.

When JSON.NET request an object for a specific reference (via ResolveReference()) I recurse, and deserialize that reference.

The issue is, JSON.NET calls ResolveReference() for each of the object references, before it calls it's AddReference().

I would expect the flow of Deserialization to be:

  1. Identify Object Type
  2. Construct the object
  3. AddReference(id, newObj)
  4. Resolve References + Populate Properties

What I see happens is:

  1. Identify Object Type
  2. Resolve References
  3. Construct the object
  4. AddReference(id, newObj)
  5. Populate Properties

My Questions:

  1. Why is it made the latter, am i missing something with my suggested flow?

  2. how can I overcome this issue, having a "Bare" object just for referencing and only then actually resolve the references ?

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
Tomer W
  • 3,395
  • 2
  • 29
  • 44
  • It's possibly because you have a parameterized constructor, requiring the properties to be loaded and deserialized before construction. Try adding a parameterless constructor. Possibly related: [Usage of non-default constructor breaks order of deserialization in Json.net](https://stackoverflow.com/q/36866131). – dbc Sep 28 '18 at 22:22
  • And here we go: [Issue with serializing/deserializing object graph with self-referencing objects in combination with JSON constructor. #715](https://github.com/JamesNK/Newtonsoft.Json/issues/715): JamesNK commented on Nov 28, 2015: *Non-default constructors and preserving references don't work well together because the child values of a type have to be deserialized before the parent is created, so the reference resolves to null.* That being said, since your question lacks a [mcve], your code might have other problems that aren't fixed by adding a parameterless constructor. – dbc Sep 28 '18 at 22:29

2 Answers2

3

The Two-Phase deserialization you are seeing is arises because your Node class only has a parameterized constructor. As explained in Issue with serializing/deserializing object graph with self-referencing objects in combination with JSON constructor. #715:

JamesNK commented on Nov 28, 2015

Non-default constructors and preserving references don't work well together because the child values of a type have to be deserialized before the parent is created, so the reference resolves to null.

Thus, since Value is a mutable property anyway, you should add a parameterless constructor to Node:

class Node
{
    public Node() : this(null) { }

    public Node(object value)
    {
        Value = value;
    }
    // Remainder unchanged.
}

It can be non-public if you mark it with [JsonConstructor] or deserialize using the setting ConstructorHandling.AllowNonPublicDefaultConstructor. And if Value were immutable, you would need to make it privately settable and mark it with [JsonProperty]

[JsonProperty]
public object Value { get; private set; }

(Data contract attributes can be used in place of Json.NET attributes if you prefer.)

Notes:

dbc
  • 104,963
  • 20
  • 228
  • 340
  • I see, What if the `Value` property was `get;` only ? i would have to set it to be [JsonProperty] too ? – Tomer W Sep 28 '18 at 22:51
  • Another thing, Can i tell JSON.NET to only handle the Constructor parameters and leave the rest... then call AddReference (or any other) ... and only then resolve additional references? – Tomer W Sep 28 '18 at 22:53
  • Yes, Issue 715 indicates you would need to make it privately settable and add `[JsonProperty]`. (You could use data contract attributes if you don't want to add Json.NET attributes to your model.) – dbc Sep 28 '18 at 22:53
  • *Another thing, Can i tell JSON.NET to only handle the Constructor parameters and leave the rest...* -- not according to Issue 715. Since a [JSON](http://www.json.org/) object is an *unordered set of name/value pairs*, Json.NET needs to load the entire object to ensure it has loaded all the constructor parameters. Thus the non-constructor parameters have to get loaded into... something. And apparently Json.NET uses the final target type rather than an intermediate `JToken`. Though you might try [`MetadataPropertyHandling.ReadAhead`](https://stackoverflow.com/q/29493292). – dbc Sep 28 '18 at 22:57
0

Well, I Found a solution for my problem:

The first Deserialization i perform, i use a custom IContractResolver that exclude all properties that are not relevant to the constructor...

at the second pass, I use Populate and use the default IContractResolver

    private class InternalOnlyCtorContractResolver : IContractResolver
    {
        private readonly IContractResolver _base;

        public InternalOnlyCtorContractResolver(IContractResolver _base)
        {
            this._base = _base;
        }

        public JsonContract ResolveContract(Type type)
        {
            var contract = _base.ResolveContract(type);
            var objectContract = contract as JsonObjectContract;
            if (objectContract != null)
            {
                var creatorParameters = new HashSet<string>(objectContract.CreatorParameters.Select(p => p.PropertyName));
                var irrelevantProperties = objectContract.Properties
                    .Where(p => !creatorParameters.Contains(p.PropertyName))
                    .ToArray();
                foreach (var irrelevantProperty in irrelevantProperties)
                {
                    objectContract.Properties.Remove(irrelevantProperty);
                }
                //TODO Can be optimized better
            }
            return contract;
        }
    }

If for some reason, the Circular Reference is needed by the Constructor,
It still cause a loop, but it is kinda impossible to create without having a second constructor anyway.

Tomer W
  • 3,395
  • 2
  • 29
  • 44