Search code examples
c#jsonobjecttypecasting-operator

Converting generic Object type to a more specialized Object


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.


Solution

  • 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()
    };