1
votes

I am trying to serialize and deserialize a polymorphic type hierarchy using a custom JsonConverter along the lines of the ones shown in answers to How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?. However, when I call the ReadJson() method of the converter to deserialize some JSON I previously serialized, it crashes. How can I use the converter to deserialize my JSON?

The following code reproduces the problem. It is a simplification of the original code with only one subtype in the polymorphic type hierarchy.

using System;
using System.IO;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace testJSON
{
    class               Program
    {
        public List< Ente > ListaEntes = new List< Ente >(); 

        static void Main(string[] args)
        {
            Program program = new Program();
            program.ListaEntes.Add( new Enemy( 10 ) );
            program.ListaEntes.Add( new Enemy( 20 ) );

            JsonSerializer serializer = new JsonSerializer();
            serializer.TypeNameHandling = TypeNameHandling.Objects;
            serializer.Formatting = Formatting.Indented;

            string folder = "C:\\Users\\pablo\\PasotaPV8_data\\archivoPrueba.dat";
            StreamWriter sw = new StreamWriter( @folder );
            JsonWriter writer = new JsonTextWriter( sw );
            serializer.Serialize( writer, program.ListaEntes );
            writer.Close();
            sw.Close();

            program.ListaEntes.Clear();

            StreamReader sr = new StreamReader( @folder );
            JsonEnteConverter jsonEnteConverter = new JsonEnteConverter();
            JsonReader reader = new JsonTextReader( sr );

            program.ListaEntes = ( List< Ente > ) jsonEnteConverter.ReadJson( reader, null, null, serializer );

        }
    }

    public class        Ente
    {
        public string tipo;
        public Animator animator;
    }

    public class        Enemy : Ente
    {
        public          Enemy()
        {
            animator = new Animator( this );
        }

        public          Enemy( int pVar )
        {
            tipo = "enemigo";
        }
    }

    public class        Animator
    {
        Ente father;

        public          Animator( Enemy pEnemy )
        {
            father = pEnemy;
        }
    }

    public class        JsonEnteConverter : Newtonsoft.Json.Converters.CustomCreationConverter< Ente >
    {
        public override Ente Create( Type objectType )
        {
            throw new NotImplementedException();
        }

        public          Ente Create( JObject jObject )
        {
            string type = jObject.Property( "tipo" ).ToString(); //get property Type from your json

            switch ( type )
            {
                case "enemigo":
                    return new Enemy();
            }
            throw new ApplicationException(String.Format("Type not found", type));
        }

        public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
        {
            JObject jObject = JObject.Load( reader );
            var targetObject = Create( jObject );
            serializer.Populate( jObject.CreateReader(), targetObject );
            return targetObject;
        }
    }
}

And the error:

Unhandled exception. Newtonsoft.Json.JsonReaderException: Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray. Path '', line 1, position 1. at Newtonsoft.Json.Linq.JObject.Load(JsonReader reader, JsonLoadSettings settings) at Newtonsoft.Json.Linq.JObject.Load(JsonReader reader) at testJSON.JsonEnteConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer) in C:\proyectos\proyectosC#\bugJSON\Program.cs:line 90 at testJSON.Program.Main(String[] args) in C:\proyectos\proyectosC#\bugJSON\Program.cs:line 35

Demo fiddle reproducing the problem here: https://dotnetfiddle.net/cbjYMw.

1
It looks like archivoPrueba.dat does not contain valid json. Have you looked at it in a text editor?Crowcoder
I think the file is correct: [ { "$type": "testJSON.Enemy, bugJSON", "tipo": "enemigo", "animator": null }, { "$type": "testJSON.Enemy, bugJSON", "tipo": "enemigo", "animator": null } ]IndieDev
It's an array but you are trying to read it as a JObject. You are doing way more work than you need to with all the readers and writers, but that is the base problem.Crowcoder
I was able to create a minimal reproducible example here: dotnetfiddle.net/cbjYMwdbc

1 Answers

1
votes

You have a few problems here:

  1. Your basic problem is that you are attempting to deserialize a List<Ente> by directly calling JsonEnteConverter.ReadJson(), however JsonEnteConverter is designed to deserialize a single instance of Ente, not a collection of them. This causes the exception you are seeing.

    Instead, you need to add JsonEnteConverter to JsonSerializerSettings.Converters, manufacture a JsonSerializer from the settings, then use that to deserialize a List<Ente> as follows:

    var readSettings = new JsonSerializerSettings
    {
        TypeNameHandling = TypeNameHandling.Objects,
        Converters = { new JsonEnteConverter() }, // FIXED
    };
    using (var sr = new StreamReader( @folder )) // FIXED dispose of readers properly
    using (var reader = new JsonTextReader( sr ))
    {
        ListaEntes = JsonSerializer.Create(readSettings).Deserialize<List<Ente>>(reader);
    }
    
  2. In JsonEnteConverter.Create() you attempt to check the value of the "tipo" property by calling jObject.Property( "tipo" ).ToString();. However, JObject.Property(string) returns the JProperty of the specified name corresponding to the combined name/value pair. Thus the string value evaluates to "tipo": "enemigo".

    Instead you need to get just the value by doing var type = (string)jObject["tipo"]:

    public          Ente Create( JObject jObject )
    {
        var type = (string)jObject["tipo"]; // FIXED
        switch ( type )
        {
            case "enemigo":
                return new Enemy();
        }
        throw new ApplicationException(String.Format("Type not found", type));
    }
    
  3. StreamWriter, JsonTextWriter, StreamReader and JsonTextReader are all disposable so should be properly disposed of via a using statement, e.g. as shown above.

  4. Since you are using a custom creation converter for the Ente type hierarchy, you may not need to use TypeNameHandling. But if you do, for security reasons you should consider writing a custom ISerializationBinder for reasons explained in TypeNameHandling caution in Newtonsoft Json.

Demo fiddle showing the working JsonEnteConverter here: https://dotnetfiddle.net/VNL5PN.