0

I am attempting to migrate serialized JSON objects using an abstract implementation of JsonConverter using Json.NET. The idea is that you would implement this class for any type that you would want to migrate versions. When that type is being deserialized, it calls one or more methods that modify the underlying JSON before using it to create the object.

public abstract class JsonMigrator<T> : JsonConverter<T> where T : class
{
    public override void WriteJson(JsonWriter writer, T? value, JsonSerializer serializer)
    {
        writer.WriteValue(value);
    }

    public override T? ReadJson(JsonReader reader, Type objectType, T? existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;

        var item = JObject.Load(reader);

        //if this is a reference to an earlier object, return already created object
        if (item["$ref"] != null)
        {
            return serializer.ReferenceResolver.ResolveReference(serializer, (string)item["$ref"]) as T;
        }

        //call migration methods to transform json data
        var migratedData = PerformMigrations(item, serializer);

        var dataReader = migratedData.CreateReader();

        //this doesn't work, infinte recursion
        //var created = serializer.Deserialize(dataReader);

        //call constructor and then populate with data
        var created = ConstructObjectSomehow();
        serializer.Populate(dataReader, created);
        return created as T;
    }

    // Inspects object for Version property and performs each migration in succession to bring to current
    internal JToken PerformMigrations(JObject serializedData, JsonSerializer serializer)
    {
        var serializedVersion = serializedData.Value<int>("Version");

        for (int i = serializedVersion; i < OrderedMigrations.Count; i++)
        {
            serializedData = OrderedMigrations[i](serializedData, serializer);
        }

        return serializedData;
    }

    public abstract List<Func<JObject, JsonSerializer, JObject>> OrderedMigrations { get; }
}

When I do it this way, it causes a SO because the converter just gets called recursively when I try to create the instance using the serializer.deserialize method. The constructor for the test class I'm using doesn't have a true parameterless constructor, so I can't restrict the generic type to new().

I am thinking that I can probably get it to work using reflection and/or Activator.CreateInstance. But I'm wondering if there is a more robust approach, since Json.Net is obviously already able to create instances of this object. (ie. it all works without this converter)

Smolakian
  • 414
  • 3
  • 15
  • 1
    If your objects have parameterized constructors, then `ConstructObjectSomehow()` + `Populate()` isn't going to work for you. You may need to disable the converter recursively as shown in e.g. [Generic method of modifying JSON before being returned to client](https://stackoverflow.com/q/35532466/3744182) or [JSON.Net throws StackOverflowException when using `[JsonConvert()]`](https://stackoverflow.com/a/29720068/3744182). – dbc Dec 07 '22 at 04:23
  • 1
    [Generic method of modifying JSON before being returned to client](https://stackoverflow.com/a/35533682/3744182) may be more useful to you because it keeps a thread-safe stack of the current type being deserialized, to prevent infinite recursion. This should allow you to do conversions for nested objects of different types. Recursive trees won't be converted correctly though. Do you need me to make that an answer? – dbc Dec 07 '22 at 04:31
  • If you want the rep. Otherwise I can figure it out from here. – Smolakian Dec 07 '22 at 04:33

0 Answers0