JSON .Net not respecting PreserveReferencesHandling on Deserialization

后端 未结 1 1184
天命终不由人
天命终不由人 2021-01-07 21:42

I have doubly linked list that I am trying to deserialise.

My scenario closely relates to this SO: Doubly Linked List to JSON

I have the following JSON setti

相关标签:
1条回答
  • 2021-01-07 22:18

    Here is what I tried and worked fine:

    The classes

    public class TemplateDataLinkedListBase<T> where T : TemplateDataLinkedListBase<T>
    {
        [JsonProperty(TypeNameHandling = TypeNameHandling.Objects)]
        public T Parent { get; set; }
    
        [JsonProperty(TypeNameHandling = TypeNameHandling.Objects)]
        public List<T> Children { get; set; }
    }
    
    public class TemplateDataQueryElement : TemplateDataLinkedListBase<TemplateDataQueryElement>
    {
        public string Query { get; set; }
    
        public TemplateDataQueryElement()
        {
            Children = new List<TemplateDataQueryElement>();
        }
    }
    

    Initialization

    var childLowest = new TemplateDataQueryElement
    {
        Query = "Lowest"
    };
    
    var childMiddle = new TemplateDataQueryElement
    {
        Query = "Middle",
        Children = new List<TemplateDataQueryElement>
        {
            childLowest
        }
    };
    
    childLowest.Parent = childMiddle;
    
    var parent = new TemplateDataQueryElement
    {
        Query = "Parent",
        Children = new List<TemplateDataQueryElement>
        {
            childMiddle
        }
    };
    
    childMiddle.Parent = parent;
    

    Serialization settings

    var _jsonSettings = new JsonSerializerSettings()
    {
        TypeNameHandling = TypeNameHandling.Auto,
        ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
        PreserveReferencesHandling = PreserveReferencesHandling.Objects,
        ObjectCreationHandling = ObjectCreationHandling.Auto
    };
    

    Serialization

    var serializedStr = JsonConvert.SerializeObject(parent, Formatting.Indented, _jsonSettings);
    

    The serialized json looks like this:

    {
      "$id": "1",
      "Query": "Parent",
      "Parent": null,
      "Children": [
        {
          "$id": "2",
          "Query": "Middle",
          "Parent": {
            "$ref": "1"
          },
          "Children": [
            {
              "$id": "3",
              "Query": "Lowest",
              "Parent": {
                "$ref": "2"
              },
              "Children": []
            }
          ]
        }
      ]
    }
    

    Deserialization

    var deserializedStructure = JsonConvert.DeserializeObject<TemplateDataQueryElement>(serializedStr, _jsonSettings);
    

    The references in the deserializedStructure are preserved correctly.

    Demo https://dotnetfiddle.net/j1Qhu6

    UPDATE 1

    The reason my example works, and the code you posted in the additional links doesn't is because my classes contain default constructor, and yours don't. Analyzing your classes, adding a default constructor to them, it won't break the functionality and the deserialization will be successful with Parent property initialized correctly. So what you basically need to do is add a default constructor to both classes:

    public class TemplateDataLinkedListBase<T> where T : TemplateDataLinkedListBase<T>
    {
        [JsonProperty(TypeNameHandling = TypeNameHandling.Objects)]
        public T Parent { get; set; }
    
        [JsonProperty(TypeNameHandling=TypeNameHandling.Objects)]
        public List<T> Children { get; set; }
        public string EntityName { get; set; }
        public HashSet<string> Fields { get; set; }
    
        public string Key { get { return getKey(); } }
    
    
        public TemplateDataLinkedListBase()
        {
            Children = new List<T>();
            Fields = new HashSet<string>();
        }
    
        public TemplateDataLinkedListBase(string entityName)
        {
            EntityName = entityName;
            Children = new List<T>();
            Fields = new HashSet<string>();
        }
    
        private string getKey()
        {
            List<string> keys = new List<string>();
            keys.Add(this.EntityName);
            getParentKeys(ref keys, this);
            keys.Reverse();
            return string.Join(".", keys);
    
        }
    
        private void getParentKeys(ref List<string> keys, TemplateDataLinkedListBase<T> element)
        {
            if (element.Parent != null)
            {
                keys.Add(element.Parent.EntityName);
                getParentKeys(ref keys, element.Parent);
            }
        }
    
        public T AddChild(T child)
        {
            child.Parent = (T)this;
            Children.Add(child);
            return (T)this;
        }
    
        public T AddChildren(List<T> children)
        {
            foreach (var child in children)
            {
                child.Parent = (T)this;
            }
            Children.AddRange(children);
            return (T)this;
        }
    
        public void AddFields(IEnumerable<string> fields)
        {
            foreach (var field in fields)
                this.Fields.Add(field);
        }
    
        public TemplateDataLinkedListBase<T> Find(string searchkey)
        {
            if (this.Key == searchkey)
            {
                return this;
            }
            else
            {
                foreach (var child in Children)
                {
                    if (child.Key == searchkey)
                    {
                        return child;
                    }
                    else
                    {
                        var childResult = child.Find(searchkey);
                        if (childResult != null) return childResult;
                    }
                }
            }
            return null;
        }
    }
    
    public class TemplateDataQueryElement : TemplateDataLinkedListBase<TemplateDataQueryElement>, ITemplateDataQueryElement
    {
        public string TemplateModelName { get; set; }
        public string RecordId { get; set; }
        public string ParentForeignKeyName { get; set; }
        public string Query { get; set; }
        public dynamic ObjectData { get; set; }
        public ITemplateDataParseResult ParseResult { get; set; }
    
    
        public TemplateDataQueryElement() : base()
        {
            Fields.Add("Id"); //Always retrieve Id's
            ObjectData = new ExpandoObject();
        }
    
        public TemplateDataQueryElement(string entityName)
            : base(entityName)
        {
            Fields.Add("Id"); //Always retrieve Id's
            ObjectData = new ExpandoObject();
        }
    
        public override string ToString()
        {
            return string.Format("{0}: {1}", EntityName, Query);
        }
    }
    

    The EntityName property which you set through your constructor, will be deserialized correctly, since it is a public property.

    0 讨论(0)
提交回复
热议问题