1

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.

4
  • It looks like archivoPrueba.dat does not contain valid json. Have you looked at it in a text editor? Commented Jun 8, 2020 at 15:43
  • I think the file is correct: [ { "$type": "testJSON.Enemy, bugJSON", "tipo": "enemigo", "animator": null }, { "$type": "testJSON.Enemy, bugJSON", "tipo": "enemigo", "animator": null } ] Commented Jun 8, 2020 at 15:49
  • 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. Commented Jun 8, 2020 at 15:53
  • I was able to create a minimal reproducible example here: dotnetfiddle.net/cbjYMw Commented Jun 8, 2020 at 22:22

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Hi, one question, on line 58 you have used a "Dump" method with the list but in my code editor (VSCode) that method is not recognized. Where does the "Dump" method come from?.
@IndieDev - it's a debugging extension method available on dotnetfiddle.net which I used to print out the contents of the list.
I understand, thanks for everything, I already solved my problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.