Reputation: 25
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.
Upvotes: 1
Views: 1654
Reputation: 117102
You have a few problems here:
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);
}
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));
}
StreamWriter
, JsonTextWriter
, StreamReader
and JsonTextReader
are all disposable so should be properly disposed of via a using
statement, e.g. as shown above.
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.
Upvotes: 1