0

I'm trying to deserialize json from 3rd-party service:

{
   "route1":[
      {
         "Id":1
      }
   ],
   "route2":{
      "error":"Id not found"
   }
}

It's a Dictionary, but value can be array or an object. I need the only array, so I decided to put an empty array when I found an object in JsonConverter.

public class Item
{
    public int Id { get; set; }
}

public class JsonInfo
{
    [JsonConverter(typeof(ItemConverter))]
    public List<Item> Items{ get; set; }
}
public class ItemConverter : Newtonsoft.Json.JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        switch (reader.TokenType)
        {
            case JsonToken.String:
            case JsonToken.StartObject:
                return new List<Item>();

            case JsonToken.StartArray:
                var array = JArray.Load(reader);
                var data = array.Select(ParseValue).ToList();
                return data;

            default:
                return null;
        }
    }

    public override bool CanConvert(Type objectType)
    {
        return false;
    }

    private Item ParseValue(JToken value)
    {
        switch (value.Type)
        {
            case JTokenType.Object:
                return value.ToObject<Item>();

            default:
                return null;
        }
    }
}

But when I'm trying to deserialize Dictionary<string, JsonInfo> it raises an error (must be json array). I think the problem that converter trying to find JsonInfo property in json, instead of array inside this class.

Maybe I missed something?
Do we have an attribute, that allows skipping property name?

Aborigen
  • 21
  • 4
  • Sure [there is](https://stackoverflow.com/q/10169648/1997232) an attribute. Problem solved? – Sinatr Jun 29 '21 at 07:44
  • Ingore doesn't solve it, because I need to deserialize this property. I do not need to deserialize the JsonInfo object, I need to deserialize the list inside this class, i.e. skip object, but at the same time I can't use Dictionary>, because I need to apply custom JsonConverter – Aborigen Jun 29 '21 at 10:22
  • How does `ItemConverter` relate to `VirtualVenueConverter`? – Peter Csala Jun 29 '21 at 10:50
  • Sorry... It the same things, updated – Aborigen Jun 29 '21 at 11:24

2 Answers2

0

You don't need to create a custom converter.

You can achieve the same with following few lines of code:

var semiParsedJson = JObject.Parse(rawJson);
var result = new Dictionary<string, List<Item>>(); 
foreach (var item in semiParsedJson)
    if (item.Value is JArray)
        result.Add(item.Key, item.Value.ToObject<List<Item>>());
    else 
        result.Add(item.Key, new List<Item>>());
  1. We semi parse the json to a JObject
  2. We are iterating through its properties via a simple foreach where item's type is KeyValuePair<string, JToken?>
  3. If the Value is a JArray (not a JObject) then we simply populate the dictionary with the content of Value
  4. Finally we let Newtonsoft to convert the Value to a List<Item> collection on our behalf

With this approach you don't need to write a custom converter and you don't need to decorate your domain model with serialization attributes.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • Thank you for your answer. It seems to be working solution, but I don't want to change default deserializer. (it will affect other endpoints) – Aborigen Jun 29 '21 at 11:28
  • You don't have to change the default deserializer. You can simply put this code into a method and call it after you have read the 3rd party service's response. – Peter Csala Jun 29 '21 at 11:56
0

I solved my issue by applying custom converter not to the property, but to the entire class

[JsonConverter(typeof(ItemConverter))]
public class JsonInfo
{
    public List<Item> Items{ get; set; }
}

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        switch (reader.TokenType)
        {
            case JsonToken.String:
                return null;
            case JsonToken.StartObject:
                reader.Skip();
                return new JsonInfo{ Items = new List<Item>() };

            case JsonToken.StartArray:
                var array = JArray.Load(reader);
                var data = array.Select(ParseValue).ToList();
                return new JsonInfo{ Items = data };

            default:
                return null;
        }
    }

    public override bool CanConvert(Type objectType)
    {
        return false;
    }

    private Item ParseValue(JToken value)
    {
        switch (value.Type)
        {
            case JTokenType.Object:
                return value.ToObject<Item>();

            default:
                return null;
        }
    }
}
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Aborigen
  • 21
  • 4