Usually, for derived types deserialization, you have to define a custom converter. I mean when there is a base class - Base and others derived from the Base. For that case you have to define a custom convevrter and use some data (maybe an enum) from each derived class to know to which type to convert the data. The problem is when this convertor is called for reference values, for which representation looks like {"$ref":"1"} and the convertor can't obtain any info from this.
Any ideas how to handle this?
Here is the source for better understanding:
public enum ElementType
{
Invalid = 0,
FirstElement,
SecondElement
}
public class SubElement
{
[JsonConstructor]
private SubElement() { }
public SubElement(string name, Element parent)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("message", nameof(name));
}
Name = name;
Parent = parent ?? throw new ArgumentNullException(nameof(parent));
}
[JsonProperty]
public string Name { get; private set; }
[JsonProperty]
public Element Parent { get; private set; }
}
[JsonObject(IsReference = true)]
[JsonConverter(typeof(BaseConverter))]
public abstract class Element
{
[JsonConstructor]
protected Element() { }
public Element(string name, IList<SubElement> subelements)
{
Name = name;
Subelements = subelements;
}
[JsonProperty]
public string Name { get; private set; }
[JsonProperty]
public IList<SubElement> Subelements { get; private set; }
public abstract ElementType Type { get; }
}
public class FirstElement : Element
{
[JsonConstructor]
private FirstElement() { }
public FirstElement(string name, IList<SubElement> subelements) : base(name, subelements) { }
public override ElementType Type => ElementType.FirstElement;
}
public class BaseConverter : CustomCreationConverter<Element>
{
private ElementType elementType;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var obj = JObject.ReadFrom(reader);
elementType = obj["Type"].ToObject<ElementType>();
return base.ReadJson(obj.CreateReader(), objectType, existingValue, serializer);
}
public override Element Create(Type objectType)
{
switch (elementType)
{
case ElementType.FirstElement:
return Activator.CreateInstance(typeof(FirstElement), true) as Element;
default:
throw new NotImplementedException();
}
}
}
class Program
{
static void Main(string[] args)
{
Element firstElement = new FirstElement("FirstElement", new List<SubElement>());
firstElement.Subelements.Add(new SubElement("Subelement1", firstElement));
firstElement.Subelements.Add(new SubElement("Subelement2", firstElement));
string serialized = JsonConvert.SerializeObject(firstElement);
Console.WriteLine(serialized);
Element deserialized = JsonConvert.DeserializeObject<Element>(serialized);
}
}
Serialization output:
{
"$id": "1",
"Type": 1,
"Name": "FirstElement",
"Subelements": [
{
"Name": "Subelement1",
"Parent": {
"$ref": "1"
}
},
{
"Name": "Subelement2",
"Parent": {
"$ref": "1"
}
}
]
}
If someone facing same scenario, I found a workaround by adding a dictionary in BaseConverter class, which maps the elements by their $id when they are created in Create method and, when the current object is an $ref, ReadJson method will return the corresponding mapped element.
Related
I have to deserialize a JSON string to C# classes inorder to bind to a grid. I have implemented the respective classes in C#. But at a particular instance, this fails because the JSON property will be either an array or an object. Please check a part of the string.
I have created ItemList class with 3 properties IL1 , Name and another object class "Item". However, you can see that when the property "Name" is Rubber, I should have List of Item class as a property rather than Item object. When it is Rubber, it returns array of 2 items.
"ItemList": [
{
"#IL1": "Yes",
"#Name": "Pencil"
"Item": {
"#ItemType": "Pencil",
"#Item2": "1A7716744F7048ACA2549BE93F0A2BF1",
"aimLink": {
"#id": "1A7716744F7048ACA2549BE93F0A2BF1",
"#DisplayText": "P00001"
}
}
},
{
"#IL1": "Yes",
"#Name": "Pen",
"Item": {
"#ItemType": "Pen",
"#Item2": "AE067F7EDB6147C09AED243C1F7FAD25",
"aimLink": {
"#id": "AE067F7EDB6147C09AED243C1F7FAD25",
"#DisplayText": "5100010654120001
}
}
},
{
"#IL1": "Yes",
"#Name": "Rubber",
"Item": [
{
"#ItemType": "Rubber",
"#ItemGID": "622025629037499394DF092DA16BAB7F",
"aimLink": {
"#id": "622025629037499394DF092DA16BAB7F",
"#DisplayText": "12345678-1234-123456-7116#01"
}
},
{
"#ItemType": "Rubber",
"#ItemGID": "F336F65F8E014E80B84A2312F829493C"
"aimLink": {
"#id": "F336F65F8E014E80B84A2312F829493C",
"#DisplayText": "12345678-1234-123456-7116#14"
}
}
]
}
],
How can I parse this to a C# class effectively and the easiest way to get this done?
Thanks,
Divya
You can resolve this issue by making your custom JsonConverter and you can use below to convert single value to array then mark Item property with the JsonConverter attribute
public class SingleValueArrayConverter<T> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object retVal = new Object();
if (reader.TokenType == JsonToken.StartObject)
{
T instance = (T)serializer.Deserialize(reader, typeof(T));
retVal = new List<T>() { instance };
}
else if (reader.TokenType == JsonToken.StartArray)
{
retVal = serializer.Deserialize(reader, objectType);
}
return retVal;
}
public override bool CanConvert(Type objectType)
{
return false;
}
}
let's assume this your ItemList class
public class ItemList
{
public string #IL1 { get; set; }
public string #Name { get; set; }
[JsonConverter(typeof(SingleValueArrayConverter<Item>))]
public List<Item> Item { get; set; }
}
public class Item
{
public string #ItemType { get; set; }
public string #Item2 { get; set; }
public AimLink aimLink { get; set; }
}
public class AimLink
{
public string #id { get; set; }
public string #DisplayText { get; set; }
}
You can make your custom JsonConverter (e.g. ItemConverter) and mark Item property with the [JsonConverter(typeof(ItemConverter))] attribute
This question already has answers here:
How to handle both a single item and an array for the same property using JSON.net
(9 answers)
Closed 3 years ago.
I am trying to process an object that can be either an array of object or just the object. Using the code below only works when the naics is an object and not an array. What am I doing wrong?
Here is the shortest example I can come up with:
[{
"section": "52.219-1.b",
"naics": [{
"naicsName": "Engineering Services",
"isPrimary": true,
"ExcpCounter": 1,
"isSmallBusiness": "Y",
"naicsCode": 541330
},
{
"naicsName": "Military and Aerospace Equipment and Military Weapons",
"isPrimary": true,
"ExcpCounter": 2,
"isSmallBusiness": "Y",
"naicsCode": 541330
}
]
},
{
"section": "52.219-1.b",
"naics": {
"naicsName": "Janitorial Services",
"isPrimary": true,
"isSmallBusiness": "Y",
"naicsCode": 561720
}
}
]
I will only have one of the types but I forced two in an array to force it into Quick Type.
My classes are:
[JsonProperty("naics", NullValueHandling = NullValueHandling.Ignore)]
public AnswerNaics Naics { get; set; }
public partial struct AnswerNaics
{
public AnswerNaic Naic;
public AnswerNaic[] NaicArray;
public static implicit operator AnswerNaics(AnswerNaic Naic) => new AnswerNaics { Naic = Naic };
public static implicit operator AnswerNaics(AnswerNaic[] NaicArray) => new AnswerNaics { NaicArray = NaicArray };
}
public partial class AnswerNaic
{
[JsonProperty("naicsName")]
public string NaicsName { get; set; }
[JsonProperty("hasSizeChanged")]
public string HasSizeChanged { get; set; }
[JsonProperty("isPrimary")]
public bool IsPrimary { get; set; }
[JsonProperty("ExcpCounter", NullValueHandling = NullValueHandling.Ignore)]
public long? ExcpCounter { get; set; }
[JsonProperty("isSmallBusiness")]
public string IsSmallBusiness { get; set; }
[JsonProperty("naicsCode")]
public string NaicsCode { get; set; }
}
internal class NaicsConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(AnswerNaics) || t == typeof(AnswerNaics?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.StartObject:
var objectValue = serializer.Deserialize<AnswerNaic>(reader);
return new AnswerNaics { Naic = objectValue };
case JsonToken.StartArray:
var arrayValue = serializer.Deserialize<AnswerNaic[]>(reader);
return new AnswerNaics { NaicArray = arrayValue };
}
throw new Exception("Cannot unmarshal type AnswerNaics");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
var value = (AnswerNaics)untypedValue;
if (value.NaicArray != null)
{
serializer.Serialize(writer, value.NaicArray);
return;
}
if (value.Naic != null)
{
serializer.Serialize(writer, value.Naic);
return;
}
throw new Exception("Cannot marshal type Naics");
}
public static readonly NaicsConverter Singleton = new NaicsConverter();
}
I have more object or array nodes, but I am just trying to figure out one to be able to apply to all of them.
Since you cannot change the incoming JSON, you're going to need a custom converter instead. Something like this for example:
public class NaicsConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(Naics);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
var naics = new Naics();
switch (reader.TokenType)
{
case JsonToken.StartObject:
// We know this is an object, so serialise a single Naics
naics.Add(serializer.Deserialize<Naic>(reader));
break;
case JsonToken.StartArray:
// We know this is an object, so serialise multiple Naics
foreach(var naic in serializer.Deserialize<List<Naic>>(reader))
{
naics.Add(naic);
}
break;
}
return naics;
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And the supporting classes:
public class Root
{
public string Section { get; set; }
[JsonConverter(typeof(NaicsConverter))]
public Naics Naics { get; set; }
}
// This isn't ideal, but it's quick and dirty and should get you started
public class Naics : List<Naic>
{
}
public class Naic
{
public string NaicsName { get; set; }
public bool IsPrimary { get; set; }
public string IsSmallBusiness { get; set; }
public long NaicsCode { get; set; }
}
And finally, to deserialise:
var settings = new JsonSerializerSettings {Converters = {new NaicsConverter()}};
var root = JsonConvert.DeserializeObject<Root[]>(Json, settings);
Now your object will get serialised into the list, but as a single item.
You can solve this using a dynamic field in your class.
Consider this JSON:
[
{
"field1": "val1",
"nested": [
{
"nestedField": "val2"
},
{
"nestedField": "val3"
}
]
},
{
"field1": "val4",
"nested":
{
"nestedField": "val5"
}
}
]
nested field is first an array with 2 objects and in the second appearance a single object. (similar to the JSON you posted)
So the class representation would look like:
public class RootObject
{
public string field1 { get; set; }
public dynamic nested { get; set; }
public List<NestedObject> NestedObjects
{
get
{
if(nested is JArray)
{
return JsonConvert.DeserializeObject<List<NestedObject>>(nested.ToString());
}
var obj = JsonConvert.DeserializeObject<NestedObject>(nested.ToString());
return new List<NestedObject> { obj };
}
}
}
public class NestedObject
{
public string nestedField { get; set; }
}
The Deserialization code is trivial using Newtonsoft JSON:
var objectList = JsonConvert.DeserializeObject<List<RootObject>>("some_json");
foreach(var v in objectList)
{
foreach(var n in v.NestedObjects)
{
Console.WriteLine(n.nestedField);
}
}
The only change is an implementation of NestedObjects ready only property. It check if the dynamic object is JArray or object. In any case, it returns a List of nested objects.
I want to deserialize the following JSON into my C# Tree object. The tree structure cannot be edited.
JSON:
{
"Root": {
"Type": 0,
"children": [
{
"Type": 1,
"Name": " SERVICES",
"children": [
{
"Type": 2,
"Name": " SERVICES",
"Code": "S01",
"children": [],
"leaves": [
{
"Type": 6,
"Code": "H-L-CWP-50",
"Uom": "SQM",
"Measurements": "SQM",
"BaseRate": "€20"
},
{
"Type": 6,
"Code": "HMS-REM-001-03",
"Uom": "SQ.M",
"Measurements": "SQ.M",
"BaseRate": "€6.38"
}
]
}
]
}
]
}
}
C# Tree:
public class Tree
{
public Root Root { get; set; }
public Tree()
{}
}
public class Root : Node
{
public override NodeType Type => NodeType.Root;
}
public class Group : Node
{
public override NodeType Type => NodeType.Group;
}
public class Category : Node
{
public override NodeType Type => NodeType.Category;
}
public class Type : Node
{
public override NodeType Type => NodeType.Type;
}
public class SubType : Node
{
public override NodeType Type => NodeType.SubType;
}
public class SubSubType : Node
{
public override NodeType Type => NodeType.SubsubType;
}
}
public abstract class Node
{
public int? ID { get; set; }
public int? FK { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public List<Node> children { get; set; }
public List<Item> leaves { get; set; }
}
public class Item
{
public string Code { get; set; }
public string Desc { get; set; }
public string Uom { get; set; }
public float? Measurements { get; set; }
public int? FK { get; set; }
}
and the Tree is designed in a fashion where a Node can have a list of other Nodes as well as a list of Items; children and leaves respectively in JSON.
With the custom converter I built, I can deserialize the Nodes whenever no leaves exist, but populated leaves makes the JSON unrecognizable and an exception is thrown.
public class NodeConverter : JsonCreationConverter<Node> //notice the Node type here where in fact its a mixture of Nodes and Items.
{
protected override Node Create(Type objectType, JObject jObject)
{
switch ((Node.NodeType)jObject["Type"].Value<int>())
{
case Node.NodeType.Root:
return new Root();
case Node.NodeType.Group:
return new Group();
case Node.NodeType.Category:
return new Category();
case Node.NodeType.Type:
return new Type();
case Node.NodeType.SubType:
return new SubType();
case Node.NodeType.SubsubType:
return new SubSubType();
case Node.NodeType.Item: //I tried this but of course it won't work.
return new Item();
}
return null;
}
}
Any solutions/examples are highly appreciated!!!
Thanks guys.
For those encountering the same issues, I have solved my problem through a combination of different answers out there.
Classes Item and Node have been attributed with the JSON custom converter:
[JsonConverter(typeof(JSONTreeConverter))]
public abstract class Node
[JsonConverter(typeof(JSONTreeConverter))]
public class Item
this enables each class to use the custom converter before initializing.
Then the custom converter returns an object which is cast through reflection to the proper Node or Item type.
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object targetObj = null;
JObject jo = JObject.Load(reader);
try
{
targetObj = Activator.CreateInstance(objectType); //instantiating concrete and known types
}
catch (Exception exc)
{
switch ((Node.NodeType)jo["Type"].Value<int>())
{
case Node.NodeType.Root:
targetObj = new Root();
break;
case Node.NodeType.Group:
targetObj = new Group();
break;
case Node.NodeType.Category:
targetObj = new ValescoCategory();
break;
case Node.NodeType.Type:
targetObj = new Type();
break;
case Node.NodeType.SubType:
targetObj = new SubType();
break;
case Node.NodeType.SubsubType:
targetObj = new SubSubType();
break;
case Node.NodeType.Item:
targetObj = new Item(); //now this is possible ;)
break;
}
}
foreach (PropertyInfo prop in objectType.GetProperties().Where(p => p.CanRead && p.CanWrite))
{
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = (att != null ? att.PropertyName : prop.Name);
JToken token = jo.SelectToken(jsonPath);
if (token != null && token.Type != JTokenType.Null)
{
object value = token.ToObject(prop.PropertyType, serializer);
prop.SetValue(targetObj, value, null);
}
}
return targetObj;
}
Deserializing as such: JsonConvert.DeserializeObject<Tree>(tree);
That's it! The other overridden function of JSON all return false with Write being not implemented.
Hope this helps someone else because it has taken me 3 days to get to.
Lets say I have the below json.
{
"allitemscount":2,
"allitems":[
{"itemid":"1","itemname":"one"},
{"itemid":"2","itemname":"two"}],
"customitems":[
{"itemid":"3","itemname":"three"},
{"itemid":"4","itemname":"four"}]
}
and when deserializing this json, it should go to the below C# model.
public class response
{
public int allitemscount;
public List<item> items;
}
public class item
{
public string itemid;
public string itemname;
}
Question:
How to switch between allitems and customitems based on a condition? For eg., if useAllitems is true, allitems from the json to be filled in items and if useCustomItems is true, customitems to be filled in items property. Please help on how to do this.
Using JsonProperty on public List<item> items is allowing to switch between either allitems, but is there a way to deserialize based on the above mentioned condition.
I made it with writing our own ItemConverter which inherited from JsonConverter,
Sample model json that I use in my trying:
{
"allitemscount": 2,
"UseCustomItems": true,
"allitems": [
{
"itemid": "1",
"itemname": "one"
},
{
"itemid": "2",
"itemname": "two"
}
],
"customitems": [
{
"itemid": "3",
"itemname": "three"
},
{
"itemid": "4",
"itemname": "four"
}
]
}
Console application's main method:
static void Main(string[] args)
{
using (StreamReader r = new StreamReader(#"\model.json")) // json path
{
string json = r.ReadToEnd();
var deserializedJson = JsonConvert.DeserializeObject<Result>(json, new ItemConverter());
}
}
Models:
public class Result // main object
{
[JsonProperty("allitemscount")]
public long Allitemscount { get; set; }
public bool UseCustomItems { get; set; }
}
public class ResultA : Result // CustomItems Model
{
[JsonProperty("customitems")]
private List<Item> Items { get; set; }
}
public class ResultB : Result // AllItems Model
{
[JsonProperty("allitems")]
private List<Item> Items { get; set; }
}
public class Item
{
[JsonProperty("itemid")]
public string Itemid { get; set; }
[JsonProperty("itemname")]
public string Itemname { get; set; }
}
And the ItemConverter that we used while deserializing to object:
internal class ItemConverter : JsonConverter
{
private Type currentType;
public override bool CanConvert(Type objectType)
{
return typeof(Item).IsAssignableFrom(objectType) || objectType == typeof(Result);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject item = JObject.Load(reader);
if (item["UseCustomItems"] != null)
{
// save the type for later.
switch (item["UseCustomItems"].Value<bool>())
{
case true:
currentType = typeof(ResultA);
return item.ToObject<ResultA>(); // return result as customitems result
case false:
currentType = typeof(ResultB);
return item.ToObject<ResultB>(); // return result as allitems result
}
return item.ToObject<Result>();
}
// use the last type you read to serialise.
return item.ToObject(currentType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
The result should be like bellowing image
I hope this solution help to you
You can deserialize above json by changing your response class as follows,
public class response{
public int allitemscount;
public List<item> allitems;
public List<item> customitems;
}
then use the following code,
var jsonData = "{\"allitemscount\":2, \"allitems\":[{\"itemid\":\"1\",\"itemname\":\"one\"}, {\"itemid\":\"2\",\"itemname\":\"two\"}],\"customitems\":[{\"itemid\":\"3\",\"itemname\":\"three\"},{\"itemid\":\"4\",\"itemname\":\"four\"}]}";
var data = JsonConvert.DeserializeObject<response>(jsonData);
foreach(var str in data.allitems) {
Console.WriteLine(str.itemid +'-'+str.itemname);
}
foreach(var str in data.customitems) {
Console.WriteLine(str.itemid +'-'+str.itemname);
}
Actually I receive Json like this:
{
"id" : 1,
"items": [
{
"media": {
"id": 1,
}
},
{
"media": {
"id": 2,
},
}
],
"status": "ok"
}
I created class like this to map on entity
public class Message
{
[JsonProperty("items")]
public List<Item> Items { get; set; }
}
public class Item
{
[JsonProperty("media")]
public Media Media { get; set; }
}
public class Media
{
[JsonProperty("id")]
public string Id { get; set; }
}
This work perfectly but my question is:
Is it possible to remove Item class and directly cast in List of Media to simplify ?
Update:
I'm pretty sure it is possible, the first way I found before post is JSonConverter, but I'm not sure is the best way to do that, i would like to know if there are any easier solution, with attribute for example.
Using a private Dictionary
Yes, you can, for example using a (private) Dictionary
public class Message
{
public List<Media> Items { get; set; }
}
public class Media
{
[JsonProperty("media")]
private Dictionary<string, string> IdDict;
public string Id
{
get { return IdDict["id"]; }
set { IdDict["id"] = value; }
}
}
Usage
var result = JsonConvert.DeserializeObject<Message>(json);
var test = result.Items[1].Id;
Implementing a Converter
Alternatively you can achieve your result by implementing a Converter
public class MediaConverter : JsonCreationConverter<Message>
{
protected override Message Create(Type objectType, JObject jObject)
{
var msg = new Message();
msg.Items = new List<Media>();
foreach (var item in jObject["items"])
{
Media media = new Media();
media.Id = item["media"]["id"].Value<string>();
msg.Items.Add(media);
}
return msg;
}
}
public abstract class JsonCreationConverter<T> : JsonConverter
{
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
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
T target = Create(objectType, jObject);
return target;
}
public override void WriteJson(JsonWriter writer,
object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Usage
var result = JsonConvert.DeserializeObject<Message>(json, new MediaConverter());
var test = result.Items[1].Id;