skip to Main Content

I have a json structure, that a few levels down turns a generic object into one that would need to be converted into a Custom Object type for further processing.

Cut down example to show what I mean:

{
  "main": [
    {
      ...
      "list": [
        {
          ...
          "sublist": [
            {
              ...
              "items": [
                {
                  <identical structure>  <-- Section sets parameters, static strings
                  "Generic Object": {...} <-- Becomes ObjectType1 object, based on above
                },
                {
                  <identical structure>  <-- Section sets parameters, static strings
                  "Generic Object": {...} <-- Becomes ObjectType2 object, based on above
                }]
            }]
        }]
    }]
}

I have the "Generic Object" set simply to type Object in initial de-serialization. However, when I try to convert it to a more specialized type by

var object = (TypecastObjectType1)<locationOfObject>.VariableObject;

This results in an error at that specific line:

'Unable to cast object of type 'Newtonsoft.Json.Linq.JObject' to type 'TypecastObjectType1'.'

Looking at source Json and the structure, it’s matching my custom get-set class layout, so I am obviously missing something.

The reason it’s a generic object itself, is because it is only typecast when that specific part is used/interacted with, which would then set the object type at that point for a more specialized selections of contents for that specific operation type.

2

Answers


  1. The error message tells me that the <locationOfObject>.VariableObject is obtained using LINQ to JSON, so its type is JObject. To deserialize a JObject into a .NET object, you should consider using <locationOfObject>.VariableObject.ToObject<TypecastObjectType1>() instead of a simple cast.

    To handle "Generic Objects", I’d suggest creating a custom serialization binder. Your JSON will look like this:

    {
      <identical structure>
      "Generic Object": {
        "$type": "TypecastObjectType1",  // or "TypecastObjectType2"
        <other properties>
      }
    }
    

    The binder itself is a class derived from Newtonsoft.Json.Serialization.DefaultSerializationBinder. In your case it will look like this:

    using Newtonsoft.Json.Serialization;
    
    public class GenericObjectBinder : DefaultSerializationBinder {
      private static readonly Dictionary<string, Type> string2type = new() {
        { "1", typeof(TypecastObjectType1) },  // Map "$type" value to .NET type
        { "2", typeof(TypecastObjectType2) }
      };
      private static readonly Dictionary<Type, string> type2string = string2type.ToDictionary(pair => pair.Value, pair => pair.Key);  // Map .NET type to "$type" value
    
      public override Type BindToType(string? assemblyName, string typeName) {
        if (string2type.TryGetValue(typeName, out var type) && assemblyName == null) {
          return type;
        }
        return base.BindToType(assemblyName, typeName);
      }
    
      public override void BindToName(Type serializedType, out string? assemblyName, out string typeName) {
        if (type2string.TryGetValue(serializedType, out var name)) {
          assemblyName = null;
          typeName = name;
          return;
        }
        base.BindToName(serializedType, out assemblyName, out typeName!);
      }
    }
    

    To inject this binder you’ll need the following JsonSerializerSettings:

    var jsonSettings = new JsonSerializerSettings {
      TypeNameHandling = TypeNameHandling.Auto,
      SerializationBinder = new GenericObjectBinder()
    };
    
    Login or Signup to reply.
  2. In OOP we know inheritance can go from least specific most common (object) to most specific, most complex merely by containing the proper interface definitions. The runtime has no problem morphing into one or more of the types of any implementated interface merely by casting.

    The other ability to morph objects is using the Containment method. Any object may contain one or many other objects. For example a car can contain 4 tires. The tires are not a car, rather the car contains the tires. We typically don’t downcast a Tire to an object because we loose knowledge of what it is. Class names carry meaning and all classes start as objects. This is why we can call the ToString method on tire to see a console output.

    Storing objects as Json has no bad effect on any object. This is due to the excellent schema of json and libraries like Newtonsoft.Json that converts from string to object types with simplicity.

    One last point is that of generics. With generics there are behavioral concerns. For example lists are perfect for generics because we don’t care what type of list they are we just care about iteration, summation, averages and counts.

    If you are running a car shop which does tire maintenance. You first will only accept cars. Then you will want the tire information to get specs. The maintenance is done on the tires including possible replacement. All being certified work based on tire and car maker recommendations.

    Favor composition over inheritance and serialize to string for storage. Use ORM to convert strings to objects. Good luck.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search