Type Converting.

May 12, 2009 at 11:44 AM

Hi

I'm trying to deserialize a JSON object into a .NET object that has a property collection of interfaces.  I want to use a Unity IOC container to create the actual object.

 

public class MyClass

{

public List<ISomeType> Children { get; private set;}

}

The deserialization fails because the interface (ISomeType) can't be instantiated.  Is it possible to provide a "type converter" that will provide an instance from the container instead of using the default type creation?

Hope this makes sense,

Thanks

 

Coordinator
May 13, 2009 at 10:29 AM

If you get the latest source and use it then check out a JsonConverter I just added called CustomCreationConverter. I haven't written any tests around it yet so no promises but inheriting from that should let you achieve what you are looking for.

May 13, 2009 at 11:58 AM

Thanks - it works perfectly :)

Jun 17, 2009 at 6:19 PM

I'm facing a similar problem with deserializing a List<T> where T is an abstract class.

Now i'm trying to make it work using the CustomCreationConverter, but some questions arise:

1. How do i know which type the serialized object actually is?

2. Trying to implement the Converter, i instantly run into a ReferenceLoop excection. Without the converter it serializes just fine tho, that's quite confusing.

I appreciate any help!

Coordinator
Jun 19, 2009 at 12:00 AM

The converter you create should only converter T. The serializer will handle serializing/deserializing the list.

Jan 25, 2010 at 12:31 PM

I'm sorry to bother you again, but I don't get it.

I got a similar scenario where a List<IComponent> contains different objects all implementing the IComponent interface.

I created the CustomCreationConverter<IComponent> but how should I decide in overridden method IComponent Created(Type objectType) which class to return? I assumed the following code works:

public override IComponent Create(Type objectType)
{
   if (objectType == typeof(AComponent))
       return new AComponent();

   if (objectType == typeof(BComponent))
        return new BComponent();

    if (objectType == typeof(CComponent))
        return new CComponent();

    throw new ApplicationException(String.Format("The given objectType {0} is not supported!", objectType));
}

But it doesn't as objectType contains IComponent as type.

How to solve this?

Any help is appreciated! Many Thanks.

Coordinator
Jan 26, 2010 at 9:29 AM

It want be those types because all it knows is that it is deserializing to an IComponent.

You can include the type name in JSON using Json.NET if you want to track the exact object type of some values.

Jan 25, 2011 at 11:41 AM

Hello,

You have suggested adding the type name to the JSON object, how can we read the type name in order to deserialise to the correct object type?

Our example:

    public abstract class CVehicle
    {
        public abstract string Type { get; set; }
    }

    public class CCar : CVehicle
    {
        [JsonProperty]
        public override string Type { get { return "Car"; } set { } }

        [JsonProperty]
        public string Engine { get; set; }
    }

    public class CBike : CVehicle
    {
        [JsonProperty]
        public override string Type { get { return "Bike"; } set { } }

        [JsonProperty]
        public string Pedal { get; set; }
    }

We can serialise a car to:

{"Type":"Car","Engine":"Honda"}

We use the following code to serialise and deserialise a car:

CCar oCar = new CCar() { Engine = "Honda" };
string sJsonData = JsonConvert.SerializeObject(oCar);
CVehicle oVehicle = JsonConvert.DeserializeObject<CVehicle>(sJsonData);

How can we deserialise the JSON representation of the car into the correct type using the Type property of the JSON.

Any help would be appreciated

Thanks

Feb 15, 2011 at 3:48 PM

I agree! Currently the CustomCreationConverter<>.Create() method isn't very useful if you need to instantiate a class based on the JSON contents.

It would be much more useful if it could access the Json configuration for the object it's supposed to instantiate, i.e. I'd love for the signature to look something like this:

public override T Create(Type type, IDictionary<string, object> jsonObjet);

Then to custom create method could do:

public override IComponent Create(Type objectType, IDictionary<string, object> args)
{
    switch(args["Type"]) {
        case: "Car":
            return new CCar();
        case: "Bike":
            return new CBike();
    }
    throw new ApplicationException(String.Format("The given vehicle type {0} is not supported!", args["Type"]));
}

This is the approach the System.WebScript.Serialization.JavaScriptConverter.Deserialize() takes.  I'm not sure how easily this would be done in JSON.NET, since it only parses the JSON object's content after instantiating the .Net object. SWS takes a bottom up approach where it first parses all the JSON before instantiating .Net objects.

Feb 16, 2011 at 8:49 AM

I think that what James was getting at is that JSON.Net has support for this kind of stuff built in.  You can have the library include type information for you at serialise time, which it will then use to deserialise into the correct type under-the-hood.  Check out the TypeNameHandling property on JsonSerializer.

Feb 16, 2011 at 7:51 PM
cdlk wrote:

I think that what James was getting at is that JSON.Net has support for this kind of stuff built in.  You can have the library include type information for you at serialise time, which it will then use to deserialise into the correct type under-the-hood.  Check out the TypeNameHandling property on JsonSerializer.

This only works if the data you are deserializing was serialized with JSON.Net, and it was serialized using the same Assembly/classes.

This won't work if you're consuming 3rd party data or should decide to refactor your .Net classes and hope for them to still be able to deserialize legacy data.

Feb 16, 2011 at 10:07 PM

Yep that's true, and it is a problem which I have run into.  I considered getting the third party app to generate JSON with the type attributes which Json.NET would expect, but this is quite a fragile solution for legacy data as you say.

The other solution I came up with is to have a custom converter for the interface/base class property which reads the type attribute and does the switch as in your code sample; it uses this to instantiate the correct object and then gets the serialiser to populate that object.

Feb 16, 2011 at 10:38 PM
cdlk wrote:

The other solution I came up with is to have a custom converter for the interface/base class property which reads the type attribute and does the switch as in your code sample; it uses this to instantiate the correct object and then gets the serialiser to populate that object.

I wouldn't mind taking this approach, but I don't see how it could read the type attribute before calling the serialiser to populate the object.  As far as I can tell, the serialized wants to do its own reading of the object.

Feb 17, 2011 at 8:59 AM

If you create a custom converter you have access to the JSON (through the JsonReader) for that 'node' in the object graph.  You can get the JObject representation of the JSON using something like JObject.Load(reader).  Your type property can then be read off the JObject without deserializing the whole thing into a .NET object, e.g. jObject["type"].Value<string>().

Once you've found out which object you need to new up, you can invoke the serializer (which you have access to in ReadJson) and have it Populate the object you just made.  The only minor annoyance I have found here is that there is no way to reset the JsonReader, i.e. you can't have the serializer populate the new object from the same reader you used to Load the JObject above.  The only way I could see to do this is with jObject.CreateReader() on the JObject created above, to make a new reader.  I'm not sure how expensive this operation is, but I'd like to be able to reset and re-use the original JsonReader here.

Feb 17, 2011 at 11:43 PM

Brilliant, I didn't realize I could get the reader off the deserialized JObject to populate my target object.  This is exactly the solution I was after.

FYI, this is how I've ended up implementing this type of deserialization.

The JsonCreationConverter<T> class does most of the work:

    abstract class JsonCreationConverter<T> : JsonConverter
    {
        /// <summary>
        /// Create an instance of objectType, based properties in the JSON object
        /// </summary>
        /// <param name="objectType">type of object expected</param>
        /// <param name="jObject">contents of JSON object that will be deserialized</param>
        /// <returns></returns>
        protected abstract T Create(Type objectType, JObject jObject);

        public override bool CanConvert(Type objectType)
        {
            return typeof(T).IsAssignableFrom(objectType);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            // Load JObject from stream
            JObject jObject = JObject.Load(reader);

            // Create target object based on JObject
            T target = Create(objectType, jObject);

            // Populate the object properties
            serializer.Populate(jObject.CreateReader(), target);

            return target;
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }

This class can be then derived to implement the actual construction of objects:

	class CVehicleConverter : JsonCreationConverter<CVehicle>
	{
		protected override CVehicle Create(Type objectType, JObject jObject)
		{
			var type = (string)jObject.Property("Type");
			switch (type)
			{
				case "Car":
					return new CCar();
				case "Bike":
					return new CBike();
			}
			throw new ApplicationException(String.Format("The given vehicle type {0} is not supported!", type));
		}
	}

Jul 22, 2011 at 7:58 PM

I'm really struggling with this...   it seems like it should be so simple!

I'm trying to parse an HTTP POST.    if I try accepting a string,  I get a runtime error at the client "System.String" is not supported for deserialization of an array.  Using the following approach:

 [WebMethod(EnableSession = true)]
    public string EvaluationTest(String EvalData)
    {
   var EvalInfo = JsonConvert.DeserializeObject(EvalData);

....   }

 

the following approach throws a similar: "EvaluationCollection" is not supported for deserialization of an array.   Using

    [WebMethod(EnableSession = true)]
    public string EvaluationTest(EvaluationCollection EvalData)
    {
      //  var EvalInfo = JsonConvert.DeserializeObject(EvalData);

...   }      ...   No,  I can't figure out the DeserializationObject parameters from the JSON.NET documentation.

 

this is the Json (snippet) I want to parse:

{ "EvalData": [ {"ss": 2,"UiD": 1 }, { "ss": 2,"UiD": 2 } ]}

my classes:

 

  public abstract class EvaluationCollection
    {public OneEvaluation[] EvalData; }

  public class OneEvaluation
    {
        public int UiD;
        public int ss;
    }

 

probably the best post I've seen, in three days of looking,  was this:  http://stackoverflow.com/questions/5838811/deserializing-json-result-with-json-javascriptserializer/5838907#5838907

  A little help is MUCH appreciated !

Dec 17, 2012 at 11:01 PM

Hi!

I am trying to override what type the ISO 8601 DateTime string is deserialized to. Mainly because I do not want to use .Net's implementation of DateTime on the inside of our application. I have created my own implementation of DateTime, created a custom JsonConverter, the WriteJson method works fine and produces ISO 8601 strings as expected (an example: 2012-12-16T20:20:57.0225219Z). But when I try to deserialize this with my custom json converter, it just explodes with "System.FormatException : Input string was not in a correct format." I never reach my overridden ReadJson method. So is there a way of reconfiguring the JsonConverter so that such date time strings may bypass the default implementation in Json and be forwarded to my custom converter?

Thanks,

Steinar