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 :
.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-nSo 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
Is There a Performance Impact When Calling Tolist()
How to Get the Last Day of a Month
Reliably Stop System.Threading.Timer
Dynamically Access Table in Ef Core 2.0
Will Code in a Finally Statement Fire If I Return a Value in a Try Block
How to Do Template Specialization in C#
How to Quickly Check If Two Data Transfer Objects Have Equal Properties in C#
"Open/Close" SQLconnection or Keep Open
Differencebetween Directory.Enumeratefiles VS Directory.Getfiles
Regex to Strip Line Comments from C#
An Attempt Was Made to Access a Socket in a Way Forbidden by Its Access Permissions. Why
Searching for File in Directories Recursively
How to Mock Non Virtual Methods
How to Inject a Dbcontext Instance into an Ihostedservice
How to Serialize/Deserialize a Custom Collection with Additional Properties Using JSON.Net