Search code examples
c#jsonserializationjson-deserialization

Deserialization reference cycle don't bring the data into constructor


I have the following scenario (Newtonsoft.Json):

public class SubElement
{
    [JsonConstructor]
    public SubElement(string name, Element parent)
    {
        if (string.IsNullOrEmpty(name))
        {
            throw new ArgumentException("message", nameof(name));
        }

        Name = name;
        Parent = parent ?? throw new ArgumentNullException(nameof(parent));
    }
    public string Name { get;private set; }
    public Element Parent { get; }
}

[JsonObject(IsReference =true)]
public class Element
{
    [JsonConstructor]
    public Element(string name, IList<SubElement> subelements)
    {
        Name = name;
        Subelements = subelements;
    }
    public string Name { get; set; }
    public IList<SubElement> Subelements { get; }
}

Element element = new Element("test", new List<SubElement>());
element.Subelements.Add(new SubElement("first", element));
element.Subelements.Add(new SubElement("second", element));
string serialized = JsonConvert.SerializeObject(element);
Console.WriteLine(serialized);
Element deserialized = JsonConvert.DeserializeObject<Element>(serialized);

On deserialization process, SubElement constructor get ivoked with parent Element as being null, although, in the serialized data it is stored properly. I used [JsonObject(IsReference =true)] attribute in order to manage circular referencing but it seems it isn't enough for deserialization to work.


Solution

  • You are trying to serialize/deserialize tree-like structure, containing reference to parent.

    I believe the problem is that when [JsonConstructor] is called for children, the parent is not yet constructed. This however doesn't matter if you just deserialize properties (we will need parameterless constructor in this case):

    [JsonObject(IsReference = true)]
    public class Element
    {
        [JsonProperty] // required for private setter
        public string Name { get; private set; }
        [JsonProperty]
        public IList<SubElement> Ports { get; private set; }
    
        [JsonConstructor] // required for private constructor
        Element() { }
    
        ... // your public constructors (not used for serialization)
    }
    
    public class SubElement
    {
        [JsonProperty]
        public string Name { get; private set; }
        [JsonProperty]
        public Element Parent { get; private set; }
    
        [JsonConstructor]
        SubElement() { }
    
        ...
    }
    

    I've tried to keep your architecture. References used: deserialize private setters, deserialize private constructor.

    The json looks same:

    {"$id":"1","Name":"test","Ports":[{"Name":"first","Parent":{"$ref":"1"}},{"Name":"second","Parent":{"$ref":"1"}}]}