2

Usually, for derived types deserialization, you have to define a custom converter. I mean when there is a base class - Base and others derived from the Base. For that case you have to define a custom convevrter and use some data (maybe an enum) from each derived class to know to which type to convert the data. The problem is when this convertor is called for reference values, for which representation looks like {"$ref":"1"} and the convertor can't obtain any info from this.

Any ideas how to handle this?

Here is the source for better understanding:

public enum ElementType
 {
     Invalid = 0,
     FirstElement,
     SecondElement
 }

 public class SubElement
 {
     [JsonConstructor]
     private SubElement() { }
     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));
     }
     [JsonProperty]
     public string Name { get; private set; }
     [JsonProperty]
     public Element Parent { get; private set; }
 }

 [JsonObject(IsReference = true)]
 [JsonConverter(typeof(BaseConverter))]
 public abstract class Element
 {
     [JsonConstructor]
     protected Element() { }
     public Element(string name, IList<SubElement> subelements)
     {
         Name = name;
         Subelements = subelements;
     }
     [JsonProperty]
     public string Name { get; private set; }
     [JsonProperty]
     public IList<SubElement> Subelements { get; private set; }
     public abstract ElementType Type { get; }
 }

 public class FirstElement : Element
 {
     [JsonConstructor]
     private FirstElement() { }
     public FirstElement(string name, IList<SubElement> subelements) : base(name, subelements) { }
     public override ElementType Type => ElementType.FirstElement;
 }

 public class BaseConverter : CustomCreationConverter<Element>
 {
     private ElementType elementType;
     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
     {
         var obj = JObject.ReadFrom(reader);
         elementType = obj["Type"].ToObject<ElementType>();
         return base.ReadJson(obj.CreateReader(), objectType, existingValue, serializer);
     }

     public override Element Create(Type objectType)
     {
         switch (elementType)
         {
             case ElementType.FirstElement:
                 return Activator.CreateInstance(typeof(FirstElement), true) as Element;
             default:
                 throw new NotImplementedException();
         }
     }
 }

 class Program
 {
     static void Main(string[] args)
     {
         Element firstElement = new FirstElement("FirstElement", new List<SubElement>());
         firstElement.Subelements.Add(new SubElement("Subelement1", firstElement));
         firstElement.Subelements.Add(new SubElement("Subelement2", firstElement));

         string serialized = JsonConvert.SerializeObject(firstElement);
         Console.WriteLine(serialized);
         Element deserialized = JsonConvert.DeserializeObject<Element>(serialized);
     }
 }

Serialization output:

{
  "$id": "1",
  "Type": 1,
  "Name": "FirstElement",
  "Subelements": [
    {
      "Name": "Subelement1",
      "Parent": {
        "$ref": "1"
      }
    },
    {
      "Name": "Subelement2",
      "Parent": {
        "$ref": "1"
      }
    }
  ]
}
Gabriel Anton
  • 444
  • 1
  • 6
  • 14
  • Does this answer your question? [Json.net serialize/deserialize derived types?](https://stackoverflow.com/questions/8513042/json-net-serialize-deserialize-derived-types) – Sinatr Aug 24 '20 at 07:12
  • @Sinatr I need the converter to deserialize properly derived types (e.g. when i have a list of **Elements**). – Gabriel Anton Aug 24 '20 at 07:16
  • @Sinatr No, this does not answer my question. I already have the converter for derived classes, but when i try to deserialize a reference type, where the data is just like `{"$ref":"1"}` i don't know to obtain the object data. Serialized data looks like: `{ "$id": "1", "Type": 1, "Name": "FirstElement", "Subelements": [ { "Name": "Subelement1", "Parent": { "$ref": "1" } }, { "Name": "Subelement2", "Parent": { "$ref": "1" } } ] }` – Gabriel Anton Aug 24 '20 at 07:21
  • Just use `TypeNameHandling = TypeNameHandling.Auto` to serialize data. That would add their exact `"$type"` into json (similarly to `"$ref"`). – Sinatr Aug 24 '20 at 07:22
  • @Sinatr Yes, that may solve the problem, but saving the real type of objects it is not a good approach, neither for file readability or security. – Gabriel Anton Aug 24 '20 at 07:27
  • The main difference from provided question, is there i have reference _tree like_ structure, and hence, the [JsonObject(IsReference = true)] attribute. – Gabriel Anton Aug 24 '20 at 07:33
  • And `TypeNameHandling.Auto` does not do the job, TypeNameHandling.All does, but again..does not look like a standard json format anymore. – Gabriel Anton Aug 24 '20 at 10:22

1 Answers1

1

If someone facing same scenario, I found a workaround by adding a dictionary in BaseConverter class, which maps the elements by their $id when they are created in Create method and, when the current object is an $ref, ReadJson method will return the corresponding mapped element.

Gabriel Anton
  • 444
  • 1
  • 6
  • 14