How to Deserialize a JSON Property That Can Be Two Different Data Types Using JSON.Net

How to deserialize a JSON property that can be two different data types using Json.NET

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)

How to handle a json field, which can be one of two different types?

In case future readers are interested, here is how I solved it at the moment:

In my Annotation model class I have these three properties for the segmentation:

[JsonProperty("segmentation")]
public JToken? Segmentation { get; set; }

[JsonIgnore]
public List<List<float>>? Polygons { get; set; }

[JsonIgnore]
public RunLengthEncoding? RLE { get; set; }

I then use the OnDeserialized callback to map the Segmentation to the correct property. In my case this is pretty easy, since according to the MSCOCO documentation RLE is used when IsCrowd is true, Polygons are used otherwise:

[OnDeserialized]
internal void OnDeserialized(StreamingContext context)
{
if (Segmentation == null)
return;

if(IsCrowd)
{
RLE = Segmentation.ToObject<RunLengthEncoding>();
}
else
{
Polygons = Segmentation.ToObject<List<List<float>>>();
}
}

Thanks again to @Heinzi for the suggestion!

Deserialize JSON-File with multiple datatypes for a key

as your property is complex, you'll need to write your own de-serialization logic.

Here's mine, but it's just an example :

  • First of all, your text property seems to be

    • A single value
    • Or an array of values

In this case, I'll go for an "always list" result, the case with a single value will just be a list with one entry.

public List<TextProperty> text;
  • The value can also be

    • A single string value
    • An object with the string value and a meta datum (text type)

Again, I'll go for an "always object" with no type if it's string only

public class TextProperty
{
public string text { get; set; }
public string type { get; set; }
}

Then you have to make your own Converter to handle this, you just have to inherit from JsonConverter and implement the logic

public class TextPropertyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException(); // not covered here
}

// A value can be either single string or object
// Return a TextProperty in both cases
private TextProperty ParseValue(JToken value)
{
switch(value.Type)
{
case JTokenType.String:
return new TextProperty { text = value.ToObject<string>() };

case JTokenType.Object:
return value.ToObject<TextProperty>();

default:
return null;
}
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// You'll start either with a single value (we'll convert to list of one value) or an array (list of several values then)
switch(reader.TokenType)
{
case JsonToken.String:
case JsonToken.StartObject:
return new List<TextProperty> { ParseValue(JToken.Load(reader)) };

case JsonToken.StartArray:
var a = JArray.Load(reader);
var l = new List<TextProperty>();
foreach(var v in a)
l.Add(ParseValue(v));
return l;

default:
return null;
}
}

public override bool CanConvert(Type objectType) => false;
}

I think all cases should be covered

To use it, simply add the JsonConverter attribute to the target property

public class JSONObject
{
public int id;
public string type;
public string date;
public string edited;
public string from;
public int from_id;
public string photo;
public int width;
public int height;

[JsonConverter(typeof(TextPropertyConverter))]
public List<TextProperty> text;
}

And then test it :

static void Main(string[] args)
{
string json = @"
[
{
""id"": 397910,
""type"": ""message"",
""date"": ""2018-02-21T10:27:59"",
""edited"": ""1970-01-01T01:00:00"",
""from"": ""Username"",
""from_id"": 39033284,
""text"": ""Some Text""
},

{
""id"": 397911,
""type"": ""message"",
""date"": ""2018-02-21T10:31:47"",
""edited"": ""1970-01-01T01:00:00"",
""from"": ""Username"",
""from_id"": 272964614,
""text"": [
""Some Text "",
{
""type"": ""mention"",
""text"": ""@school""
},
"" Some Text""
]
}
]";

List<JSONObject> jsonObjects = JsonConvert.DeserializeObject<List<JSONObject>>(json);

Console.Read();
}

Here's the results :

Tada

.NET Deserializing JSON to multiple types

I think it's likely you'll need to deserialize the Json then construct the objects from there. Deserializing directly to Cat or Dog won't be possible as the deserializer won't know how to construct these objects specifically.

Edit: borrowing heavily from Deserializing heterogenous JSON array into covariant List<> using JSON.NET

Something like this would work:

interface IAnimal
{
string Type { get; set; }
}

class Cat : IAnimal
{
public string CatName { get; set; }
public string Type { get; set; }
}

class Dog : IAnimal
{
public string DogName { get; set; }
public string Type { get; set; }
}

class AnimalJson
{
public IEnumerable<IAnimal> Items { get; set; }
}

class Animal
{
public string Type { get; set; }
public string Name { get; set; }
}

class AnimalItemConverter : Newtonsoft.Json.Converters.CustomCreationConverter<IAnimal>
{
public override IAnimal Create(Type objectType)
{
throw new NotImplementedException();
}

public IAnimal Create(Type objectType, JObject jObject)
{
var type = (string)jObject.Property("type");

switch (type)
{
case "cat":
return new Cat();
case "dog":
return new Dog();
}

throw new ApplicationException(String.Format("The animal type {0} is not supported!", type));
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load JObject from stream
JObject jObject = JObject.Load(reader);

// Create target object based on JObject
var target = Create(objectType, jObject);

// Populate the object properties
serializer.Populate(jObject.CreateReader(), target);

return target;
}
}

string json = "{ items: [{ type: \"cat\", catName: \"tom\" }, { type: \"dog\", dogName: \"fluffy\" }] }";
object obj = JsonConvert.DeserializeObject<AnimalJson>(json, new AnimalItemConverter());

Deserialize a json object with different structure and same name

Thank you @Piotr. It completely worked. because your first part of the answer was not correct for me, I rewrite your response as an answer.

as you said the correct answer was in this link.

https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n

So I made this class.

class JsonConverter<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();
}
}

and changed my Movie Class to this.

internal class ImdbJsonMovie
{
public string Url { get; set; }

[JsonProperty("name")]
public string Name { get; set; }

[JsonProperty("image")]
public string Image { get; set; }

[JsonProperty("genre")]
[JsonConverter(typeof(JsonConverter<string>))]
public List<string> Genre { get; set; }

[JsonProperty("contentRating")]
public string ContentRating { get; set; }

[JsonProperty("actor")]
[JsonConverter(typeof(JsonConverter<ImdbJsonTypeEnum>))]
public List<ImdbJsonTypeEnum> Actor { get; set; }

[JsonProperty("director")]
[JsonConverter(typeof(JsonConverter<ImdbJsonTypeEnum>))]
public List<ImdbJsonTypeEnum> Director { get; set; }

[JsonProperty("creator")]
[JsonConverter(typeof(JsonConverter<ImdbJsonTypeEnum>))]
public List<ImdbJsonTypeEnum> Creator { get; set; }
}

and this Enum

public class ImdbJsonTypeEnum
{
[JsonProperty("@type")]
public TypeEnum Type { get; set; }

[JsonProperty("url")]
public string Url { get; set; }

[JsonProperty("name")]
public string Name { get; set; }

public enum TypeEnum
{
Organization,
Person
};
}

It worked for one director and multi director movies.

Thank you



Related Topics



Leave a reply



Submit