Ian Vink
Ian Vink

Reputation: 68750

Deserializing JSON with varying schemas

I use JSON.Net with .NET 4.7.

In an API I call, I can get two kinds of error jsons for the same call seemingly randomly :(.

Error Type 1:

{
   "Code": 0,
   "Msg": ["Some Warning"]
}

Error Type 2:

{
    "Code": 0,
    "Msg": [
          {"Error": 5 },
          {"Error": 6 }
    ]
}

As you can see, the Msg changes its structure. I'd like to have a common POCO to deserialize into, but how when the Msg changes?

JsonConvert.DeserializeObject<MyCommonPoco>(theJson);

Upvotes: 1

Views: 677

Answers (1)

poke
poke

Reputation: 387667

You can write a custom JsonConverter that handles different types. You could for example have the following JSON:

{
    "Code": 0,
    "Msg": [
        "A single string",
        { "Message": "An object with a message" },
        { "Message": "An object with a message and a code", "Code": 5 },
        { "Code": 5 }
    ]
}

In this example, the Msg array can contain primitive strings, or a complex object. And that object can contain a Message, a Code, or both.

You would choose the most common data structure for this, one that is able to represent all that information. That means for example that you will have to wrap that primitive string into a compatible object.

public class Error
{
    public int Code { get; set; }
    public ErrorMessage[] Msg { get; set; }
}

public class ErrorMessage
{
    public string Message { get; set; }
    public int? Code { get; set; }
}

You can then use a custom

And then, with a custom JsonConverter, you could parse the above JSON:

public class ErrorMessageConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
        => objectType == typeof(ErrorMessage);

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // handle the primitive string
        if (reader.TokenType == JsonToken.String)
            return new ErrorMessage { Message = (string)reader.Value };

        // handle a complex object; just assume that it is an `ErrorMessage` here
        else if (reader.TokenType == JsonToken.StartObject)
            return JObject.Load(reader).ToObject<ErrorMessage>();

        throw new NotSupportedException();
    }

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

This would be one way to use that converter:

// the single quotes are just here to make it more readable; JSON.NET support this
// although it isn’t valid JSON
var json = @"{
    'Code': 0,
    'Msg': [
        'A single string',
        { 'Message': 'An object with a message' },
        { 'Message': 'An object with a message and a code', 'Code': 5 },
        { 'Code': 5 }
    ]
}";

var settings = new JsonSerializerSettings()
settings.Converters.Add(new ErrorMessageConverter());

var result = JsonConvert.DeserializeObject<Error>(json, settings);

Successfully parsed result

Upvotes: 5

Related Questions