Search code examples
c#jsonjson.netconverters

How to deserialize tokens in a custom Json.NET converter


I am building a generic save system for Unity. As serializer I use Json.NET.

The main data container is a class that holds a Dictionary<string/*dataKey*/, ISaveableData/*data to be saved*/>. Because ISaveableData is an interface, I use the concrete type as key in the json. This way I know into which type the data needs to be deserialized when loading. Everything works fine up to the point where I try to deserialize the data into the concrete class. I created a custom converter to handle deserialization, but I seem to get something wrong.

My current Read() method in the custom converter looks something like this:

public override Data ReadJson(JsonReader reader, Type objectType, Data existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        //load json object
        JObject jObject = JObject.Load(reader);

        Data data = new();

        foreach (JToken token in jObject.Children())
        {
            //get key
            string key = token.Path; //in my example 'key' will either be "DataTypeOne" or "DataTypeTwo"

            //determine correct type, based on the key (pseudo-code, this step works fine)
            Type type = GetTypeByKey(key);

            //now I know the type, I want to deserialize the token into an instance of the specified type
            //this fails with error 'Unexpected token while deserializing object: PropertyName. Path 'DataTypeOne'
            object deserializedInstance = token.ToObject(type, serializer);
            
            //add to data
            data.Add((ISaveable)deserializedInstance);
        }
        
        return data;
    }

The json looks something like this:

{
  "SaveSlotName": "Example",
  "Data": {
    "DataTypeOne": {
      "SomeValue": 5
    },
    "DataTypeTwo": {
      "SomeOtherValue": "Hello"
    }
  }
}

Why does JToken.ToObject(type, serializer) throw an error? How is the Read() method supposed to be used?


Solution

  • I found the issue.

    I tried to deserialize the token, when instead I need to deserialize the value of the token.

    This is the new Read() method. Maybe it's helpful for others running into the same issue.

    public override Data ReadJson(JsonReader reader, Type objectType, Data existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
        {
            //load json object
            JObject jObject = JObject.Load(reader);
    
            Data data = new();
    
            foreach (JToken token in jObject.Properties())
            {
                //get key
                string key = token.Path; //in my example 'key' will either be "DataTypeOne" or "DataTypeTwo"
    
                //determine correct type, based on the key (pseudo-code, this step works fine)
                Type type = GetTypeByKey(key);
    
                //get the value of this property
                JToken value = property.Value;
    
                //now I know the type and the property value and can deserialize it into an instance of the specified type
                object deserializedInstance = value.ToObject(type, serializer);
                
                //add to data
                data.Add((ISaveable)deserializedInstance);
            }
            
            return data;
        }