Deserializing JSON containing single or array of o

2019-06-09 09:23发布

问题:

This question already has an answer here:

  • How to handle both a single item and an array for the same property using JSON.net 6 answers

I'm trying to deserialize event data that includes image urls.

When the event includes a single image, the format of the JSON string is:

"images": {
  "image": {
      "small": {...},
      "width": "48",
      "creator": null,
      "thumb": {...},
      "height": "48",
      "url": "http://example.com/image1.jpeg",
      "medium": {...},
      "id": "1"
    }
}

When multiple images are available, the format of the response changes to:

"images": {
    "image": [
      {
        "small": {...},
        "width": "48",
        "creator": null,
        "thumb": {...},
        "height": "48",
        "url": "http://example.com/image1.jpeg",
        "medium": {...},
        "id": "1"
      },
      {
        "small": {...},
        "width": "48",
        "creator": null,
        "thumb": {...},
        "height": "48",
        "url": "http://example.com/image2.jpeg",
        "medium": {...},
        "id": "2"
      }
    ]
  }

When I'm trying to deserialize, I can get one or other model to work, but not both at the same time.

My model is something like:

public class Event
{
    public string id { get; set; }
    public string imageId { get; set; }
    public Image image { get; set; }
    public Images images { get; set; }
}

public class Image
{
    public string id { get; set; }
    public string width { get; set; }
    public string caption { get; set; }
    public string url { get; set; }
    public string height { get; set; }
    public int smallId { get; set; }
    public Small small { get; set; }
    public int mediumId { get; set; }
    public Medium medium { get; set; }
    public int thumbId { get; set; }
    public Thumb thumb { get; set; }
}

If I define my Images class as follows, it works with arrays:

public class Images
{
    public string id { get; set; }
    public List<Image> image { get; set; }
}

If I define it as follows, it works with single images:

public class Images
{
    public string id { get; set; }
    public Image image { get; set; }
}

In theory, I could contain both a List and an Image in the Images class, but I can't use the same name for both and the deserialization doesn't work if I change either name.

I understand that both responses are valid JSON (they represent what they're supposed to), but is this a bad implementation?

Or am I missing something in my model/deserialization that should allow me to deserialize both the single object and array?

Thanks.

UPDATE Deserialization code:

var feedUrl = url;
var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));

var serializer = new DataContractJsonSerializer(typeof(EventDetail));

var streamTask = client.GetStreamAsync(feedUrl);
EventDetail eventDetail = serializer.ReadObject(await streamTask) as EventDetail;

return eventDetail;

回答1:

Without seeing your serialization/deserialization code I think the problem is there.

One other Solution could be that you send an empty list of Images (Images) if you only have one Image and the other way around. So you always have Imageand Images in your JSON.



回答2:

Similar question was asked in the past: "How to handle both a single item and an array for the same property using JSON.net". The approach is basically to use customConverter as follow:

public class Images
{
    [JsonProperty("id")]
    public string Id { get; set; }

    [JsonProperty("image")]
    [JsonConverter(typeof(SingleOrArrayConverter<Image>))]
    public List<Image> Image { get; set; }
}


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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Array)
        {
            return token.ToObject<List<T>>();
        }
        return new List<T> { token.ToObject<T>() };
    }

    public override bool CanWrite
    {
        get { return false; }
    }

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