Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserialize JSON that might be an integer or a list of strings in C#

I am using C# to deserialize a collection of JSON strings. Each string has a property employer_normalized that is supposed to contain a string and List<int> where the values in the List are always positive. In some cases employer_normalized is set to -1, so I want to override these cases with some behavior that sets employer_normalized to null.

Here is my class:

public class EmployerNormalized
{
  public string company;
  public List<int> code;
}

Good JSON

"employer_normalized": {
        "company": "self",
        "code": [
          "4581 ",
          "6732 ",
          "9121",
          "9999 ",
          "5947 ",
          "8322 ",
          "8351 ",
          "7335 ",
          "9999 ",
          "4225 ",
          "8399 "
        ]
      }

Bad JSON

"employer_normalized": -1

I am currently using Json.NET to do my JSON parsing. What is an elegant solution to solving this problem? Is it best to just set the employer_normalized value to null if it is -1? If so, how can I do this?

like image 852
RouteMapper Avatar asked Dec 12 '13 15:12

RouteMapper


2 Answers

You can use a custom JsonConverter to deal with this situation. Wherever you are expecting a EmployerNormalized, the converter can check whether the value of that property is -1 and return null, otherwise deserialize it normally.

Here is the code for the converter:

public class EmployerNormalizedConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(EmployerNormalized));
    }

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

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

And here is a demo showing how to use it:

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        {
            ""good"" : {
                ""company"": ""self"",
                ""code"": [
                    ""4581 "",
                    ""6732 "",
                    ""9121"",
                    ""9999 "",
                    ""5947 "",
                    ""8322 "",
                    ""8351 "",
                    ""7335 "",
                    ""9999 "",
                    ""4225 "",
                    ""8399 ""
                ]
            },
            ""bad"" : -1
        }";

        Wrapper wrapper =
            JsonConvert.DeserializeObject<Wrapper>(json, 
                new EmployerNormalizedConverter());

        DumpEmployer("good", wrapper.good);
        DumpEmployer("bad", wrapper.bad);
    }

    private static void DumpEmployer(string prop, EmployerNormalized emp)
    {
        Console.WriteLine(prop);
        if (emp != null)
        {
            Console.WriteLine("  company: " + emp.company);
            Console.WriteLine("  codes: " + 
                string.Join(", ", emp.code.Select(c => c.ToString())));
        }
        else
            Console.WriteLine("  (null)");
    }

    public class Wrapper
    {
        public EmployerNormalized good { get; set; }
        public EmployerNormalized bad { get; set; }
    }

    public class EmployerNormalized
    {
        public string company;
        public List<int> code;
    }
}

Here is the output:

good
  company: self
  codes: 4581, 6732, 9121, 9999, 5947, 8322, 8351, 7335, 9999, 4225, 8399
bad
  (null)

Important note: You might be tempted to decorate the EmployerNormalized class with [JsonConverter(typeof(EmployerNormalizedConverter))], but if you do that, then the converter, in its current form, will end up calling itself recursively until it errors out with a StackOverflowException. If you need/want to use the attribute, then the code of the ReadJson method in the converter will need to be changed so that it manually creates an instance of the EmployerNormalized class and populates all of its properties individually rather than calling token.ToObject<EmployerNormalized>(). Here is an alternate version of ReadJson that will avoid the recursion problem:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    JToken token = JToken.Load(reader);
    if (token.Type == JTokenType.Object)
    {
        EmployerNormalized employer = new EmployerNormalized();
        employer.company = token["company"].ToString();
        employer.code = token["code"].ToObject<List<int>>();
        return employer;
    }
    return null;
}
like image 108
Brian Rogers Avatar answered Sep 30 '22 15:09

Brian Rogers


I know this sucks, but you can first try to parse the JSON string to an Object with the structure of your Good JSON, and in case of parsing error, parse to the second case (Bad JSON).

like image 34
everton Avatar answered Sep 30 '22 16:09

everton