39

I'm using Json.NET for a project I'm working on. From an external API, I am receiving JSON with properties that are objects, but when they are empty 'false' is passed.

For example:

data: {
    supplier: {
        id: 15,
        name: 'TheOne'
    }
}

Could also be:

data: {
    supplier: false
}

How should I define the supplier property so that the supplier will be deserialized to a Supplier object or null.

Right now I have:

public class Data {
   [JsonProperty("supplier")]
   public SupplierData Supplier { get; set; }
}
public class SupplierData {
    [JsonProperty("id")]
    public int Id { get; set; }
    [JsonProperty("name")]
    public string Name { get; set; }
}

But now when trying to deserialize when supplier has a value of 'false' it fails. I would like the Supplier property to be null when the JSON value is 'false'.

I hope someone knows how to do this. Thanks.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
Marc Selman
  • 752
  • 1
  • 8
  • 14
  • You can create a custom JSON converter and read the JSON. See this link: http://stackoverflow.com/questions/19307752/deserializing-polymorphic-json-classes-without-type-information-using-json-net – Poornima Dec 06 '13 at 19:52

1 Answers1

55

This can be solved by making a custom JsonConverter for your SupplierData class. Here is what the converter might look like:

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

    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<SupplierData>();
        }
        return null;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

To use it, all you would need to do is add a [JsonConverter] attribute to the Supplier property in your Data class like this:

public class Data
{
    [JsonProperty("supplier")]
    [JsonConverter(typeof(SupplierDataConverter))]
    public SupplierData Supplier { get; set; }
}

Below is a demonstration of the converter in action. Note that the demo assumes you have some kind of containing object for the data property, since the JSON in your question can't stand on its own. I defined a class called RootObject for this purpose:

public class RootObject
{
    [JsonProperty("data")]
    public Data Data { get; set; }
}

The actual demo code follows:

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        {
            ""data"": 
            {
                ""supplier"": 
                {
                    ""id"": 15,
                    ""name"": ""TheOne""
                }
            }
        }";

        Console.WriteLine("--- first run ---");
        RootObject obj = JsonConvert.DeserializeObject<RootObject>(json);
        DumpSupplier(obj.Data.Supplier);

        json = @"
        {
            ""data"": 
            {
                ""supplier"": false
            }
        }";

        Console.WriteLine("--- second run ---");
        obj = JsonConvert.DeserializeObject<RootObject>(json);
        DumpSupplier(obj.Data.Supplier);
    }

    static void DumpSupplier(SupplierData supplier)
    {
        if (supplier != null)
        {
            Console.WriteLine("Id: " + supplier.Id);
            Console.WriteLine("Name: " + supplier.Name);
        }
        else
        {
            Console.WriteLine("(null)");
        }
        Console.WriteLine();
    }
}

And here is the output from the above:

--- first run ---
Id: 15
Name: TheOne

--- second run ---
(null)
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • 4
    I had a similar case where a property was either an object or an empty array. It's mostly a bad decision for an API to break like that without versioning, but this is a clean enough solution for it. Thanks! – Ran Sagy Jan 10 '17 at 10:16
  • How can I get both? I would like both to be converted ""supplier"": { ""id"": 15, ""name"": ""TheOne"" } And json = @" { ""data"": { ""supplier"": false } }"; – Dev Jun 18 '20 at 20:04
  • @Decoder94 Perhaps you can post a [new question](https://stackoverflow.com/questions/ask) and tell us what you are trying to do in more detail? You can link back to this question/answer to provide context if needed. – Brian Rogers Jun 18 '20 at 23:30
  • @BrianRogers https://stackoverflow.com/q/62462147/5326036 – Dev Jun 19 '20 at 02:06
  • I don't think this will work for array/list types. Because those are generic and used in cases where you don't need or want a conversion. How do you handle the ambiguity? – void.pointer Apr 09 '22 at 16:00
  • @void.pointer I don't understand what you mean. Maybe you could post a new question about it? – Brian Rogers Apr 10 '22 at 05:06
  • You are attaching the converter to a specific property and not globally via settings, so my concern is a non-issue. Sorry for the confusion. – void.pointer Apr 11 '22 at 14:38