Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserialize JSON to multiple properties

Tags:

json

c#

json.net

I am programming against a third party API which returns JSON data, but the format can be a little strange. Certain properties can either be an object (which contains an Id property), or a string (which is the Id of the object). For example, both of the following are valid:

{
    ChildObject: 'childobjectkey1'
}

and

{
    ChildObject: {
        Id: 'childobjectkey1',
        // (other properties)
    }
}

I'm trying to deserialize this using JSON.net into a strongly typed class, but haven't had much luck so far. My best idea was to serialise it to two properties, one a string and the other an object, and to use a custom JsonConverter for each to allow for the variable behaviour:

public abstract class BaseEntity
{
    public string Id { get; set; }
}

public class ChildObject : BaseEntity { }

public class MyObject
{
    [JsonProperty("ChildObject")]
    [JsonConverter(typeof(MyCustomIdConverter))]
    public string ChildObjectId { get; set; }

    [JsonProperty("ChildObject")]
    [JsonConverter(typeof(MyCustomObjectConverter))]
    public ChildObject ChildObject { get; set; }
}

However, setting the JsonProperty attribute on two properties with the same PropertyName causes the exception:

Newtonsoft.Json.JsonSerializationException: A member with the name 'ChildObject' already exists on '.....'. Use the JsonPropertyAttribute to specify another name.

I'm fairly sure the JsonConverter approach will work if I can get over this hurdle - I suspect the error is there because the JsonProperty attribute is used for Serialization as well as Deserialization. In this instance I have no interest in Serializing this class - it will only ever be used as the target for Deserialization.

I have no control over the remote end (it's a third party API), but I would like to be able to achieve this serialisation. I don't mind if it's using the approach I've started on, or one I've not thought of yet.

This question is also related, but there were no answers.

like image 789
Richard Avatar asked Jan 02 '14 22:01

Richard


1 Answers

Rather than creating two separate converters for each of the fields, it would be wise to create a single converter for the "main" property and link the other one to it. ChildObjectId is derived from the ChildObject.

public class MyObject
{
    [JsonIgnore]
    public string ChildObjectId
    {
        get { return ChildObject.Id; }

        // I would advise against having a setter here
        // you should only allow changes through the object only
        set { ChildObject.Id = value; }
    }

    [JsonConverter(typeof(MyObjectChildObjectConverter))]
    public ChildObject ChildObject { get; set; }
}

Now to convert the ChildObject can be a bit of a challenge. There are two possible representations of the object: a string or an object. Determine what representation you have and perform the conversion.

public class MyObjectChildObjectConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(ChildObject);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var obj = serializer.Deserialize<JToken>(reader);
        switch (obj.Type)
        {
        case JTokenType.Object:
            return ReadAsObject(obj as JObject);
        case JTokenType.String:
            return ReadAsString((string)(JValue)obj);
        default:
            throw new JsonSerializationException("Unexpected token type");
        }
    }

    private object ReadAsObject(JObject obj)
    {
        return obj.ToObject<ChildObject>();
    }

    private object ReadAsString(string str)
    {
        // do a lookup for the actual object or whatever here
        return new ChildObject
        {
            Id = str,
        };
    }
}
like image 89
Jeff Mercado Avatar answered Sep 29 '22 02:09

Jeff Mercado