1

I have a class similar to this one:

public class Values
{
    public int BestValue { get; set; }
    public List<string> AllValues { get; set; }
}

This class could be (because of history) be stored either as

"MyFirstValue"  /* if only one value */

or

{ "BestValue" : 0, "AllValues" : ["DefaultValue", "OtherValue"] }

How can I deserialize this automatically using a JsonConverter? (Or any other solution.)

dbc
  • 104,963
  • 20
  • 228
  • 340
Jean
  • 4,911
  • 3
  • 29
  • 50

1 Answers1

2

You will need to create a custom JsonConverter for this purpose:

internal class ValuesConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(Values).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var tokenType = reader.SkipComments().TokenType;
        if (tokenType == JsonToken.Null)
            return null;
        var value = existingValue as Values ?? (Values)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        if (tokenType == JsonToken.Date)
        {
            // Avoid annoying bug that converts date strings to local format described in 
            // https://stackoverflow.com/questions/35166060/json-net-get-specific-json-date-value
            value.AllValues = new List<string> { JToken.Load(reader).ToString(Formatting.None).Trim('"') };
        }
        else if (tokenType.IsPrimitive())
        {
            value.AllValues = new List<string> { (string)JToken.Load(reader) };
        }
        else
        {
            serializer.Populate(reader, value);
        }
        return value;
    }

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

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

public static partial class JsonExtensions
{
    public static JsonReader SkipComments(this JsonReader reader)
    {
        while (reader.TokenType == JsonToken.Comment && reader.Read())
            ;
        return reader;
    }

    public static bool IsPrimitive(this JsonToken tokenType)
    {
        switch (tokenType)
        {
            case JsonToken.Integer:
            case JsonToken.Float:
            case JsonToken.String:
            case JsonToken.Boolean:
            case JsonToken.Undefined:
            case JsonToken.Null:
            case JsonToken.Date:
            case JsonToken.Bytes:
                return true;
            default:
                return false;
        }
    }   
}

Then apply it to your type as follows:

[JsonConverter(typeof(ValuesConverter))]
public class Values
{
    public int BestValue { get; set; }
    public List<string> AllValues { get; set; }
}

Or, add it to JsonSerializerSettings.Converters then modify your settings as shown in JsonSerializerSettings and Asp.Net Core.

Notes -

Working sample .Net fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • Thanks! Actually I already started to implement the converter, but I was missing the code inside the "read" method. The documentation on how to implement is actually not really clear... The `Populate` method is mostly what I was missing! I was also missing the registration part, I thought the attribute was sufficient to register it. – Jean Apr 16 '18 at 07:57
  • 1
    Hum miread, the attribute is sufficient :D I saw several times these registrations not using attribute and I don't like them much. – Jean Apr 16 '18 at 08:18