0

I have a requirement to deserialize JSON where the sender can send either a single instance of Child as one of the nodes, or a Child[]. (a.k.a JsonSchema 'oneOf`).

public class Parent
{
  public Child[] Data { get; set; }
}

public class Child
{
  public string ChildName { get; set; }
}

Where the JSON can be either an array

{
  "data":
    [
      {
        "childName": "Bob"
      }
    ]
}

or a single object

{
  "data":
    {
      "childName": "Bob"
    }
}

This is what I have so far

public class ObjectToArrayConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(T[]);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.StartObject)
        {
            JObject singleJObject = (JObject)serializer.Deserialize(reader);
            return new T[] { singleJObject.Value<T>() };
        }
        var arrayJObject = (JObject)serializer.Deserialize(reader);
        return arrayJObject.Value<T[]>();
    }

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

Which I use like so

var settings = new JsonSerializerSettings();
settings.Converters.Add(new ObjectToArrayConverter<Child>());
var parent = JsonConvert.DeserializeObject<Parent>(json, settings);

But when the JSON data node is a single object, the line return new T[] { jObject.Value<T>() }; gives me the exception

System.InvalidCastException: 'Cannot cast Newtonsoft.Json.Linq.JObject to Newtonsoft.Json.Linq.JToken.'

And when the JSON data node is an array of objects, the line var arrayJObject = (JObject)serializer.Deserialize(reader); gives me the exception

System.InvalidCastException: 'Unable to cast object of type 'Newtonsoft.Json.Linq.JArray' to type 'Newtonsoft.Json.Linq.JObject'.

Peter Morris
  • 20,174
  • 9
  • 81
  • 146

1 Answers1

1

What is the issue here is that you are using the extension method Value instead of ToObject. Value converts the "value" part of the data so in your case "Bob". ToObject converts whole object {"childName":"Bob"} to provided class.

    return arrayJObject.ToObject<T[]>(); //This should work and...
    return new T[] { singleJObject.ToObject<T>() };

Also, I would suggest changing to this - so it would be more readable

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    var result = (JToken)serializer.Deserialize(reader);
    if (result == null)
    {
        throw new ArgumentNullException(nameof(result));
    }

    if (result.Type == JTokenType.Object)
    {
        return new T[] { result.ToObject<T>() };
    }
    else if(result.Type == JTokenType.Array)
    {
        return result.ToObject<T[]>();
    }
    throw new NotSupportedException();
}
Tomasz Juszczak
  • 2,276
  • 15
  • 25