WebAPI JSON Serialization not serializing any children of a composite object - c#

So I am needing to serialize a composite to JSON (with JSON.NET) and was hoping that coming here with this problem would be a quick win.
I have a very basic composite implementation that I am just trying to use to scaffold my services and the data structure but the JSONSerializer is only serializing the root node.
Code:
namespace Data
{
public abstract class Element
{
protected string _name;
public Element(string name)
{
_name = name;
}
public abstract void Add(Element element);
public string Name { get { return _name; } }
}
public class ConcreteElement : Element
{
public ConcreteElement(string name) : base(name) { }
public override void Add(Element element)
{
throw new InvalidOperationException("ConcreteElements may not contain Child nodes. Perhaps you intended to add this to a Composite");
}
}
public class Composite: Element
{
public Composite(string name) : base(name) { Elements = new List<Element>(); }
private List<Element> Elements { get; set; }
public override void Add(Element element)
{
Elements.Add(element);
}
}
}
In my Controller's HttpGet method,
Composite root = new Composite("Root");
Composite branch = new Composite("Branch");
branch.Add(new ConcreteElement("Leaf1"));
branch.Add(new ConcreteElement("Leaf2"));
root.Add(branch);
return JsonConvert.SerializeObject(root);
And the only thing that is being serialized is
{"Name\":\"Root\"}"
Can anyone see a reason that this is not serializing child elements?
I'm hoping it's something stupid.
Edit1
I've never tried to Serialize a graph to JSON with WebAPI before. Do I need to write a custom MediaTypeFormatter for serializing this?
Edit2 (to add desired output)
Leaf1 and Leaf2 are just markers at the moment. They will themselves be complex objects once I can get this to serialize.
So, at the moment...
{
"Name" : "Root"
,"Branch":
[
{"Name":"Leaf1"}
,{"Name":"Leaf2"}
]
]
}
and eventually
{
"Name" : "Root"
,"Branch1":
[
{"Name":"Leaf1", "Foo":"Bar"}
{"Name":"Leaf2", "Foo":"Baz"}
]
,"Branch2":
[
"Branch3":[
{"Name":"Leaf3", "Foo":"Quux"}
]
]
}

The children are not being serialized because the list of Elements in your Composite is private. Json.Net will not serialize private members by default. If you mark the list with [JsonProperty("Elements")] then the children will be serialized.
public class Composite: Element
{
...
[JsonProperty("Elements")]
private List<Element> Elements { get; set; }
...
}
If you run your example code with this change, you should get the following JSON:
{
"Elements": [
{
"Elements": [
{
"Name": "Leaf1"
},
{
"Name": "Leaf2"
}
],
"Name": "Branch"
}
],
"Name": "Root"
}
EDIT
OK, here is an example converter for your composite:
class CompositeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Composite));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Composite composite = (Composite)value;
// Need to use reflection here because Elements is private
PropertyInfo prop = typeof(Composite).GetProperty("Elements", BindingFlags.NonPublic | BindingFlags.Instance);
List<Element> children = (List<Element>)prop.GetValue(composite);
JArray array = new JArray();
foreach (Element e in children)
{
array.Add(JToken.FromObject(e, serializer));
}
JObject obj = new JObject();
obj.Add(composite.Name, array);
obj.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is a demo:
class Program
{
static void Main(string[] args)
{
Composite root = new Composite("Root");
Composite branch1 = new Composite("Branch1");
branch1.Add(new ConcreteElement("Leaf1", "Bar"));
branch1.Add(new ConcreteElement("Leaf2", "Baz"));
root.Add(branch1);
Composite branch2 = new Composite("Branch2");
branch2.Add(new ConcreteElement("Leaf3", "Quux"));
Composite branch3 = new Composite("Branch3");
branch3.Add(new ConcreteElement("Leaf4", "Fizz"));
branch2.Add(branch3);
root.Add(branch2);
string json = JsonConvert.SerializeObject(root, Formatting.Indented, new CompositeConverter());
Console.WriteLine(json);
}
}
public abstract class Element
{
protected string _name;
public Element(string name)
{
_name = name;
}
public abstract void Add(Element element);
public string Name { get { return _name; } }
}
public class ConcreteElement : Element
{
public ConcreteElement(string name, string foo) : base(name)
{
Foo = foo;
}
public string Foo { get; set; }
public override void Add(Element element)
{
throw new InvalidOperationException("ConcreteElements may not contain Child nodes. Perhaps you intended to add this to a Composite");
}
}
public class Composite : Element
{
public Composite(string name) : base(name) { Elements = new List<Element>(); }
private List<Element> Elements { get; set; }
public override void Add(Element element)
{
Elements.Add(element);
}
}
Here is the resulting JSON output:
{
"Root": [
{
"Branch1": [
{
"Foo": "Bar",
"Name": "Leaf1"
},
{
"Foo": "Baz",
"Name": "Leaf2"
}
]
},
{
"Branch2": [
{
"Foo": "Quux",
"Name": "Leaf3"
},
{
"Branch3": [
{
"Foo": "Fizz",
"Name": "Leaf4"
}
]
}
]
}
]
}
I realize that this is not exactly the same JSON that you asked for, but it should get you going in the right direction. One problem with the "desired" JSON you specified in your question is that it is not entirely valid. Named properties can only be inside an object, not directly inside an array. In your second example, you have a named "Branch3" property directly inside the array for "Branch2". This won't work. So, you would need to make Branch2 an object instead. But if you do this, then you have an inconsistent representation for your composite: if it contains only leaves, then it is an array, otherwise it is an object. It is possible to make a converter to change the representation of the composite based on the contents (in fact I managed to create such a beast), but that makes the JSON more difficult to consume, and in the end I don't think you'll want to use it. In case you're curious, I've included this alternate converter below, along with its output.
class CompositeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Composite));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Composite composite = (Composite)value;
// Need to use reflection here because Elements is private
PropertyInfo prop = typeof(Composite).GetProperty("Elements", BindingFlags.NonPublic | BindingFlags.Instance);
List<Element> children = (List<Element>)prop.GetValue(composite);
// if all children are leaves, output as an array
if (children.All(el => el.GetType() != typeof(Composite)))
{
JArray array = new JArray();
foreach (Element e in children)
{
array.Add(JToken.FromObject(e, serializer));
}
array.WriteTo(writer);
}
else
{
// otherwise use an object
JObject obj = new JObject();
if (composite.Name == "Root")
{
obj.Add("Name", composite.Name);
}
foreach (Element e in children)
{
obj.Add(e.Name, JToken.FromObject(e, serializer));
}
obj.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Output using the same data:
{
"Name": "Root",
"Branch1": [
{
"Foo": "Bar",
"Name": "Leaf1"
},
{
"Foo": "Baz",
"Name": "Leaf2"
}
],
"Branch2": {
"Leaf3": {
"Foo": "Quux",
"Name": "Leaf3"
},
"Branch3": [
{
"Foo": "Fizz",
"Name": "Leaf4"
}
]
}
}

If you don't want to use Datacontract , i think you have to implement JsonConverter with some the methodes that you need.
namespace JsonOutil
{
public class TestConverter<T> : JsonConverter
{
public override bool CanConvert(System.Type objectType)
{
return objectType == typeof(yourClasse);
}
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 object ReadJson(JsonReader reader, System.Type objectType, object existingValue, JsonSerializer serializer)
{
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new System.NotImplementedException();
}
public string GetValueWhenReading(Dictionary<string, object> values, string key)
{
return !values.ContainsKey(key) || values[key] == null
? null
: values[key].ToString();
}
}
}

Related

Deserialise JSON to C# Objects [duplicate]

I'm trying to fix my SendGridPlus library to deal with SendGrid events, but I'm having some trouble with the inconsistent treatment of categories in the API.
In the following example payload taken from the SendGrid API reference, you'll notice that the category property for each item can either be a single string or an array of strings.
[
{
"email": "john.doe#sendgrid.com",
"timestamp": 1337966815,
"category": [
"newuser",
"transactional"
],
"event": "open"
},
{
"email": "jane.doe#sendgrid.com",
"timestamp": 1337966815,
"category": "olduser",
"event": "open"
}
]
It seems my options to make JSON.NET like this are fixing the string before it comes in, or configuring JSON.NET to accept the incorrect data. I'd rather not do any string parsing if I can get away with it.
Is there any other way I can handle this using Json.Net?
The best way to handle this situation is to use a custom JsonConverter.
Before we get to the converter, we'll need to define a class to deserialize the data into. For the Categories property that can vary between a single item and an array, define it as a List<string> and mark it with a [JsonConverter] attribute so that JSON.Net will know to use the custom converter for that property. I would also recommend using [JsonProperty] attributes so that the member properties can be given meaningful names independent of what is defined in the JSON.
class Item
{
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("timestamp")]
public int Timestamp { get; set; }
[JsonProperty("event")]
public string Event { get; set; }
[JsonProperty("category")]
[JsonConverter(typeof(SingleOrArrayConverter<string>))]
public List<string> Categories { get; set; }
}
Here is how I would implement the converter. Notice I've made the converter generic so that it can be used with strings or other types of objects as needed.
class SingleOrArrayConverter<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();
}
}
Here is an short program demonstrating the converter in action with your sample data:
class Program
{
static void Main(string[] args)
{
string json = #"
[
{
""email"": ""john.doe#sendgrid.com"",
""timestamp"": 1337966815,
""category"": [
""newuser"",
""transactional""
],
""event"": ""open""
},
{
""email"": ""jane.doe#sendgrid.com"",
""timestamp"": 1337966815,
""category"": ""olduser"",
""event"": ""open""
}
]";
List<Item> list = JsonConvert.DeserializeObject<List<Item>>(json);
foreach (Item obj in list)
{
Console.WriteLine("email: " + obj.Email);
Console.WriteLine("timestamp: " + obj.Timestamp);
Console.WriteLine("event: " + obj.Event);
Console.WriteLine("categories: " + string.Join(", ", obj.Categories));
Console.WriteLine();
}
}
}
And finally, here is the output of the above:
email: john.doe#sendgrid.com
timestamp: 1337966815
event: open
categories: newuser, transactional
email: jane.doe#sendgrid.com
timestamp: 1337966815
event: open
categories: olduser
Fiddle: https://dotnetfiddle.net/lERrmu
EDIT
If you need to go the other way, i.e. serialize, while keeping the same format, you can implement the WriteJson() method of the converter as shown below. (Be sure to remove the CanWrite override or change it to return true, or else WriteJson() will never be called.)
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
List<T> list = (List<T>)value;
if (list.Count == 1)
{
value = list[0];
}
serializer.Serialize(writer, value);
}
Fiddle: https://dotnetfiddle.net/XG3eRy
I was working on this for ages, and thanks to Brian for his answer.
All I am adding is the vb.net answer!:
Public Class SingleValueArrayConverter(Of T)
sometimes-array-and-sometimes-object
Inherits JsonConverter
Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Throw New NotImplementedException()
End Sub
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim retVal As Object = New [Object]()
If reader.TokenType = JsonToken.StartObject Then
Dim instance As T = DirectCast(serializer.Deserialize(reader, GetType(T)), T)
retVal = New List(Of T)() From { _
instance _
}
ElseIf reader.TokenType = JsonToken.StartArray Then
retVal = serializer.Deserialize(reader, objectType)
End If
Return retVal
End Function
Public Overrides Function CanConvert(objectType As Type) As Boolean
Return False
End Function
End Class
then in your class:
<JsonProperty(PropertyName:="JsonName)> _
<JsonConverter(GetType(SingleValueArrayConverter(Of YourObject)))> _
Public Property YourLocalName As List(Of YourObject)
Hope this saves you some time
As a minor variation to the great answer by Brian Rogers, here are two tweaked versions of SingleOrArrayConverter<T>.
Firstly, here is a version that works for all List<T> for every type T that is not itself a collection:
public class SingleOrArrayListConverter : JsonConverter
{
// Adapted from this answer https://stackoverflow.com/a/18997172
// to https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
// by Brian Rogers https://stackoverflow.com/users/10263/brian-rogers
readonly bool canWrite;
readonly IContractResolver resolver;
public SingleOrArrayListConverter() : this(false) { }
public SingleOrArrayListConverter(bool canWrite) : this(canWrite, null) { }
public SingleOrArrayListConverter(bool canWrite, IContractResolver resolver)
{
this.canWrite = canWrite;
// Use the global default resolver if none is passed in.
this.resolver = resolver ?? new JsonSerializer().ContractResolver;
}
static bool CanConvert(Type objectType, IContractResolver resolver)
{
Type itemType;
JsonArrayContract contract;
return CanConvert(objectType, resolver, out itemType, out contract);
}
static bool CanConvert(Type objectType, IContractResolver resolver, out Type itemType, out JsonArrayContract contract)
{
if ((itemType = objectType.GetListItemType()) == null)
{
itemType = null;
contract = null;
return false;
}
// Ensure that [JsonObject] is not applied to the type.
if ((contract = resolver.ResolveContract(objectType) as JsonArrayContract) == null)
return false;
var itemContract = resolver.ResolveContract(itemType);
// Not implemented for jagged arrays.
if (itemContract is JsonArrayContract)
return false;
return true;
}
public override bool CanConvert(Type objectType) { return CanConvert(objectType, resolver); }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
Type itemType;
JsonArrayContract contract;
if (!CanConvert(objectType, serializer.ContractResolver, out itemType, out contract))
throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), objectType));
if (reader.MoveToContent().TokenType == JsonToken.Null)
return null;
var list = (IList)(existingValue ?? contract.DefaultCreator());
if (reader.TokenType == JsonToken.StartArray)
serializer.Populate(reader, list);
else
// Here we take advantage of the fact that List<T> implements IList to avoid having to use reflection to call the generic Add<T> method.
list.Add(serializer.Deserialize(reader, itemType));
return list;
}
public override bool CanWrite { get { return canWrite; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var list = value as ICollection;
if (list == null)
throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), value.GetType()));
// Here we take advantage of the fact that List<T> implements IList to avoid having to use reflection to call the generic Count method.
if (list.Count == 1)
{
foreach (var item in list)
{
serializer.Serialize(writer, item);
break;
}
}
else
{
writer.WriteStartArray();
foreach (var item in list)
serializer.Serialize(writer, item);
writer.WriteEndArray();
}
}
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContent(this JsonReader reader)
{
while ((reader.TokenType == JsonToken.Comment || reader.TokenType == JsonToken.None) && reader.Read())
;
return reader;
}
internal static Type GetListItemType(this Type type)
{
// Quick reject for performance
if (type.IsPrimitive || type.IsArray || type == typeof(string))
return null;
while (type != null)
{
if (type.IsGenericType)
{
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(List<>))
return type.GetGenericArguments()[0];
}
type = type.BaseType;
}
return null;
}
}
It can be used as follows:
var settings = new JsonSerializerSettings
{
// Pass true if you want single-item lists to be reserialized as single items
Converters = { new SingleOrArrayListConverter(true) },
};
var list = JsonConvert.DeserializeObject<List<Item>>(json, settings);
Notes:
The converter avoids the need to pre-load the entire JSON value into memory as a JToken hierarchy.
The converter does not apply to lists whose items are also serialized as collections, e.g. List<string []>
The Boolean canWrite argument passed to the constructor controls whether to re-serialize single-element lists as JSON values or as JSON arrays.
The converter's ReadJson() uses the existingValue if pre-allocated so as to support populating of get-only list members.
Secondly, here is a version that works with other generic collections such as ObservableCollection<T>:
public class SingleOrArrayCollectionConverter<TCollection, TItem> : JsonConverter
where TCollection : ICollection<TItem>
{
// Adapted from this answer https://stackoverflow.com/a/18997172
// to https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
// by Brian Rogers https://stackoverflow.com/users/10263/brian-rogers
readonly bool canWrite;
public SingleOrArrayCollectionConverter() : this(false) { }
public SingleOrArrayCollectionConverter(bool canWrite) { this.canWrite = canWrite; }
public override bool CanConvert(Type objectType)
{
return typeof(TCollection).IsAssignableFrom(objectType);
}
static void ValidateItemContract(IContractResolver resolver)
{
var itemContract = resolver.ResolveContract(typeof(TItem));
if (itemContract is JsonArrayContract)
throw new JsonSerializationException(string.Format("Item contract type {0} not supported.", itemContract));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
ValidateItemContract(serializer.ContractResolver);
if (reader.MoveToContent().TokenType == JsonToken.Null)
return null;
var list = (ICollection<TItem>)(existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
if (reader.TokenType == JsonToken.StartArray)
serializer.Populate(reader, list);
else
list.Add(serializer.Deserialize<TItem>(reader));
return list;
}
public override bool CanWrite { get { return canWrite; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
ValidateItemContract(serializer.ContractResolver);
var list = value as ICollection<TItem>;
if (list == null)
throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), value.GetType()));
if (list.Count == 1)
{
foreach (var item in list)
{
serializer.Serialize(writer, item);
break;
}
}
else
{
writer.WriteStartArray();
foreach (var item in list)
serializer.Serialize(writer, item);
writer.WriteEndArray();
}
}
}
Then, if your model is using, say, an ObservableCollection<T> for some T, you could apply it as follows:
class Item
{
public string Email { get; set; }
public int Timestamp { get; set; }
public string Event { get; set; }
[JsonConverter(typeof(SingleOrArrayCollectionConverter<ObservableCollection<string>, string>))]
public ObservableCollection<string> Category { get; set; }
}
Notes:
In addition to the notes and restrictions for SingleOrArrayListConverter, the TCollection type must be read/write and have a parameterless constructor.
Demo fiddle with basic unit tests here.
To handle this you have to use a custom JsonConverter. But you probably already had that in mind.
You are just looking for a converter that you can use immediately. And this offers more than just a solution for the situation described.
I give an example with the question asked.
How to use my converter:
Place a JsonConverter Attribute above the property. JsonConverter(typeof(SafeCollectionConverter))
public class SendGridEvent
{
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("timestamp")]
public long Timestamp { get; set; }
[JsonProperty("category"), JsonConverter(typeof(SafeCollectionConverter))]
public string[] Category { get; set; }
[JsonProperty("event")]
public string Event { get; set; }
}
And this is my converter:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
namespace stackoverflow.question18994685
{
public class SafeCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//This not works for Populate (on existingValue)
return serializer.Deserialize<JToken>(reader).ToObjectCollectionSafe(objectType, serializer);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
And this converter uses the following class:
using System;
namespace Newtonsoft.Json.Linq
{
public static class SafeJsonConvertExtensions
{
public static object ToObjectCollectionSafe(this JToken jToken, Type objectType)
{
return ToObjectCollectionSafe(jToken, objectType, JsonSerializer.CreateDefault());
}
public static object ToObjectCollectionSafe(this JToken jToken, Type objectType, JsonSerializer jsonSerializer)
{
var expectArray = typeof(System.Collections.IEnumerable).IsAssignableFrom(objectType);
if (jToken is JArray jArray)
{
if (!expectArray)
{
//to object via singel
if (jArray.Count == 0)
return JValue.CreateNull().ToObject(objectType, jsonSerializer);
if (jArray.Count == 1)
return jArray.First.ToObject(objectType, jsonSerializer);
}
}
else if (expectArray)
{
//to object via JArray
return new JArray(jToken).ToObject(objectType, jsonSerializer);
}
return jToken.ToObject(objectType, jsonSerializer);
}
public static T ToObjectCollectionSafe<T>(this JToken jToken)
{
return (T)ToObjectCollectionSafe(jToken, typeof(T));
}
public static T ToObjectCollectionSafe<T>(this JToken jToken, JsonSerializer jsonSerializer)
{
return (T)ToObjectCollectionSafe(jToken, typeof(T), jsonSerializer);
}
}
}
What does it do exactly?
If you place the converter attribute the converter will be used for this property. You can use it on a normal object if you expect a json array with 1 or no result. Or you use it on an IEnumerable where you expect a json object or json array. (Know that an array -object[]- is an IEnumerable)
A disadvantage is that this converter can only be placed above a property because he thinks he can convert everything. And be warned. A string is also an IEnumerable.
And it offers more than an answer to the question:
If you search for something by id you know that you will get an array back with one or no result.
The ToObjectCollectionSafe<TResult>() method can handle that for you.
This is usable for Single Result vs Array using JSON.net
and handle both a single item and an array for the same property
and can convert an array to a single object.
I made this for REST requests on a server with a filter that returned one result in an array but wanted to get the result back as a single object in my code. And also for a OData result response with expanded result with one item in an array.
Have fun with it.
Just wanted to add to #dbc excellent response above on the SingleOrArrayCollectionConverter. I was able to modify it to use with a stream from an HTTP client. Here is a snippet (you will have to set up the requestUrl (string) and the httpClient (using System.Net.Http;).
public async Task<IList<T>> HttpRequest<T>(HttpClient httpClient, string requestedUrl, CancellationToken cancellationToken)
{
using (var request = new HttpRequestMessage(HttpMethod.Get, requestedUrl))
using (var httpResponseMessage = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
{
if (httpResponseMessage.IsSuccessStatusCode)
{
using var stream = await httpResponseMessage.Content.ReadAsStreamAsync();
using var streamReader = new StreamReader(stream);
using var jsonTextReader = new JsonTextReader(streamReader );
var settings = new JsonSerializerSettings
{
// Pass true if you want single-item lists to be reserialized as single items
Converters = { new SingleOrArrayCollectionConverter(true) },
};
var jsonSerializer = JsonSerializer.Create(settings);
return jsonSerializer.Deserialize<List<T>>(jsonTextReader);
}
I apologize if there are missing brackets or misspellings, it was not easy to paste code in here.
I had a very similar Problem.
My Json Request was completly unknown for me.
I only knew.
There will be an objectId in it and some anonym key value pairs AND arrays.
I used it for an EAV Model i did:
My JSON Request:
{objectId": 2,
"firstName": "Hans",
"email" :[ "a#b.de","a#c.de"],
"name": "Andre",
"something" :["232","123"]
}
My Class i defined:
[JsonConverter(typeof(AnonyObjectConverter))]
public class AnonymObject
{
public AnonymObject()
{
fields = new Dictionary<string, string>();
list = new List<string>();
}
public string objectid { get; set; }
public Dictionary<string, string> fields { get; set; }
public List<string> list { get; set; }
}
and now that i want to deserialize unknown attributes with its value and arrays in it my Converter looks like that:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
AnonymObject anonym = existingValue as AnonymObject ?? new AnonymObject();
bool isList = false;
StringBuilder listValues = new StringBuilder();
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndObject) continue;
if (isList)
{
while (reader.TokenType != JsonToken.EndArray)
{
listValues.Append(reader.Value.ToString() + ", ");
reader.Read();
}
anonym.list.Add(listValues.ToString());
isList = false;
continue;
}
var value = reader.Value.ToString();
switch (value.ToLower())
{
case "objectid":
anonym.objectid = reader.ReadAsString();
break;
default:
string val;
reader.Read();
if(reader.TokenType == JsonToken.StartArray)
{
isList = true;
val = "ValueDummyForEAV";
}
else
{
val = reader.Value.ToString();
}
try
{
anonym.fields.Add(value, val);
}
catch(ArgumentException e)
{
throw new ArgumentException("Multiple Attribute found");
}
break;
}
}
return anonym;
}
So now everytime i get an AnonymObject i can iterate through the Dictionary and everytime there is my Flag "ValueDummyForEAV" i switch to the list, read the first line and split the values. After that i delete the first entry from the list and go on with iteration from the Dictionary.
Maybe someone has the same problem and can use this :)
Regards
Andre
You can use a JSONConverterAttribute as found here: http://james.newtonking.com/projects/json/help/
Presuming you have a class that looks like
public class RootObject
{
public string email { get; set; }
public int timestamp { get; set; }
public string smtpid { get; set; }
public string #event { get; set; }
public string category[] { get; set; }
}
You'd decorate the category property as seen here:
[JsonConverter(typeof(SendGridCategoryConverter))]
public string category { get; set; }
public class SendGridCategoryConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true; // add your own logic
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// do work here to handle returning the array regardless of the number of objects in
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Left as an exercise to the reader :)
throw new NotImplementedException();
}
}
You don't need any custom converters, in this case I am usually creating a very simple JsonConstructor
public partial class Item
{
// ... all class properties
[JsonConstructor]
public Item(JToken category)
{
if (category.GetType().Name == "JArray")
Category = category.ToObject<List<string>>();
else
Category = new List<string> { category.ToString() };
}
public Item() { }
}
after this you can deserialize your json using common code
List<Item> items = JsonConvert.DeserializeObject<List<Item>>(json);
I found another solution that can handle the category as string or array by using object. This way I don´t need to mess up with the json serializer.
Please give it a look if you have the time and tell me what you think. https://github.com/MarcelloCarreira/sendgrid-csharp-eventwebhook
It´s based on the solution at https://sendgrid.com/blog/tracking-email-using-azure-sendgrid-event-webhook-part-1/ but I also added date conversion from timestamp, upgraded the variables to reflect current SendGrid model (and made categories work).
I also created a handler with basic auth as option. See the ashx files and the examples.
Thank you!

Deserialise Json when an array can contain either string or complex object?

I'm trying to read a json fragment in a C# application.
In the fragment, there's an array that can contains either a simple string or a complex object. This is because the contained object has only one mandatory string, other fields are optional. Thus, to simplify the json, the array can contain a string (= default parameters) or the complex object.
Here is a sample json :
{
"Config1": [
"Simple string 1",
"Simple string 2",
{
"Data": "Complex object",
"OptionalField": "some option",
"AnotherOption": 42
}
],
"Config3": [
"Simple string 3",
"Simple string 4",
{
"Data": "Complex object 2",
"OptionalField": "some option",
"AnotherOption": 12
}
]
}
The corresponding C# model :
public class Config : Dictionary<string, ConfigItem[]>
{
}
public class ConfigItem
{
public ConfigItem()
{
}
public ConfigItem(string data)
{
this.Data = data;
}
public string Data { get; set; }
public string OptionalField { get; set; }
public int AnotherOption { get; set; }
}
In my sample, only the Data field is mandatory. When a string is supplied, the constructor with a single string parameter must be called. When a complex json object is provided, the standard deserialization must run.
For example, these two json fragments are equivalent (within the array):
"Config4": [
"This is a string",
{
"Data": "This is a string",
"OptionalField": null,
"AnotherOption": 0
}
]
How to reach my goal ?
Right now, I tried to implement a custom converter :
[JsonConverter(typeof(StringToComplexConverter))]
public class ConfigItem
{
// Properties omiited
}
public class StringToComplexConverter : JsonConverter<ConfigItem>
{
public override bool CanWrite => false;
public override ConfigItem ReadJson(JsonReader reader, Type objectType, ConfigItem existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if(reader.TokenType == JsonToken.String)
{
var ctor = objectType.GetConstructor(new[] { typeof(string) });
return (ConfigItem)ctor.Invoke(new[] { (string)reader.Value });
}
else
{
// What to put in the else ?
}
}
public override void WriteJson(JsonWriter writer, ConfigItem value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This is actually working for strings. However, I didn't find what to put in the else statement. Is there a way to forward to standard deserialization from the ReadJson method ?
If I put return serializer.Deserialize<ConfigItem>(reader); in my else statement, it ends in a infinite loop.
Also tried return serializer.Deserialize<JObject>(reader).ToObject<ConfigItem>();, with no success.
Finally found a way. Here's my else statement:
else
{
var result = new ConfigItem();
serializer.Populate(reader, result);
return result;
}
Bonus: here's a generic variant of the converter that can be applied to any class that have a constructor with no parameter and another with a string parameter:
public class StringToComplexConverter<TObject> : JsonConverter<TObject>
{
public override bool CanWrite => false;
public override TObject ReadJson(
JsonReader reader,
Type objectType,
TObject existingValue,
bool hasExistingValue,
JsonSerializer serializer
)
{
if (reader.TokenType == JsonToken.String)
{
var ctor = objectType.GetConstructor(new[] { typeof(string) });
return (TObject)ctor.Invoke(new[] { (string)reader.Value });
}
else
{
var result = (TObject)Activator.CreateInstance(objectType);
serializer.Populate(reader, result);
return result;
}
}
public override void WriteJson(JsonWriter writer, TObject value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

Custom JsonConverter WriteJson Does Not Alter Serialization of Sub-properties

I always had the impression that the JSON serializer actually traverses your entire object's tree, and executes the custom JsonConverter's WriteJson function on each interface-typed object that it comes across - not so.
I have the following classes and interfaces:
public interface IAnimal
{
string Name { get; set; }
string Speak();
List<IAnimal> Children { get; set; }
}
public class Cat : IAnimal
{
public string Name { get; set; }
public List<IAnimal> Children { get; set; }
public Cat()
{
Children = new List<IAnimal>();
}
public Cat(string name="") : this()
{
Name = name;
}
public string Speak()
{
return "Meow";
}
}
public class Dog : IAnimal
{
public string Name { get; set; }
public List<IAnimal> Children { get; set; }
public Dog()
{
Children = new List<IAnimal>();
}
public Dog(string name="") : this()
{
Name = name;
}
public string Speak()
{
return "Arf";
}
}
To avoid the $type property in the JSON, I've written a custom JsonConverter class, whose WriteJson is
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
IAnimal animal = value as IAnimal;
JObject o = (JObject)t;
if (animal != null)
{
if (animal is Dog)
{
o.AddFirst(new JProperty("type", "Dog"));
//o.Find
}
else if (animal is Cat)
{
o.AddFirst(new JProperty("type", "Cat"));
}
foreach(IAnimal childAnimal in animal.Children)
{
// ???
}
o.WriteTo(writer);
}
}
}
In this example, yes, a dog can have cats for children and vice-versa. In the converter, I want to insert the "type" property so that it saves that to the serialization. I have the following setup. (Zoo has only a name and a list of IAnimals. I didn't include it here for brevity and laziness ;))
Zoo hardcodedZoo = new Zoo()
{ Name = "My Zoo",
Animals = new List<IAnimal> { new Dog("Ruff"), new Cat("Cleo"),
new Dog("Rover"){
Children = new List<IAnimal>{ new Dog("Fido"), new Dog("Fluffy")}
} }
};
JsonSerializerSettings settings = new JsonSerializerSettings(){
ContractResolver = new CamelCasePropertyNamesContractResolver() ,
Formatting = Formatting.Indented
};
settings.Converters.Add(new AnimalsConverter());
string serializedHardCodedZoo = JsonConvert.SerializeObject(hardcodedZoo, settings);
serializedHardCodedZoo has the following output after serialization:
{
"name": "My Zoo",
"animals": [
{
"type": "Dog",
"Name": "Ruff",
"Children": []
},
{
"type": "Cat",
"Name": "Cleo",
"Children": []
},
{
"type": "Dog",
"Name": "Rover",
"Children": [
{
"Name": "Fido",
"Children": []
},
{
"Name": "Fluffy",
"Children": []
}
]
}
]
}
The type property shows up on Ruff, Cleo, and Rover, but not for Fido and Fluffy. I guess the WriteJson isn't called recursively. How do I get that type property there?
As an aside, why does it not camel-case IAnimals like I expect it to?
The reason that your converter is not getting applied to your child objects is because JToken.FromObject() uses a new instance of the serializer internally, which does not know about your converter. There is an overload that allows you to pass in the serializer, but if you do so here you will have another problem: since you are inside a converter and you are using JToken.FromObject() to try to serialize the parent object, you will get into an infinite recursive loop. (JToken.FromObject() calls the serializer, which calls your converter, which calls JToken.FromObject(), etc.)
To get around this problem, you must handle the parent object manually. You can do this without much trouble using a bit of reflection to enumerate the parent properties:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject jo = new JObject();
Type type = value.GetType();
jo.Add("type", type.Name);
foreach (PropertyInfo prop in type.GetProperties())
{
if (prop.CanRead)
{
object propVal = prop.GetValue(value, null);
if (propVal != null)
{
jo.Add(prop.Name, JToken.FromObject(propVal, serializer));
}
}
}
jo.WriteTo(writer);
}
Fiddle: https://dotnetfiddle.net/sVWsE4
Here's an idea, instead of doing the reflection on every property, iterate through the normally serialized JObject and then changed the token of properties you're interested in.
That way you can still leverage all the ''JsonIgnore'' attributes and other attractive features built-in.
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken jToken = JToken.FromObject(value);
if (jToken.Type == JTokenType.Object)
{
JObject jObject = (JObject)jToken;
...
AddRemoveSerializedProperties(jObject, val);
...
}
...
}
And then
private void AddRemoveSerializedProperties(JObject jObject, MahMan baseContract)
{
jObject.AddFirst(....);
foreach (KeyValuePair<string, JToken> propertyJToken in jObject)
{
if (propertyJToken.Value.Type != JTokenType.Object)
continue;
JToken nestedJObject = propertyJToken.Value;
PropertyInfo clrProperty = baseContract.GetType().GetProperty(propertyJToken.Key);
MahMan nestedObjectValue = clrProperty.GetValue(baseContract) as MahMan;
if(nestedObj != null)
AddRemoveSerializedProperties((JObject)nestedJObject, nestedObjectValue);
}
}
I had this issue using two custom converters for a parent and child type. A simpler method I found is that since an overload of JToken.FromObject() takes a serializer as a parameter, you can pass along the serializer you were given in WriteJson(). However you need to remove your converter from the serializer to avoid a recursive call to it (but add it back in after):
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Converters.Remove(this);
JToken jToken = JToken.FromObject(value, serializer);
serializer.Converters.Add(this);
// Perform any necessary conversions on the object returned
}
Here is a hacky solution to your problem that gets the work done and looks tidy.
public class MyJsonConverter : JsonConverter
{
public const string TypePropertyName = "type";
private bool _dormant = false;
/// <summary>
/// A hack is involved:
/// " JToken.FromObject(value, serializer); " creates amn infinite loop in normal circumstances
/// for that reason before calling it "_dormant = true;" is called.
/// the result is that this JsonConverter will reply false to exactly one "CanConvert()" call.
/// this gap will allow to generate a a basic version without any extra properties, and then add them on the call with " JToken.FromObject(value, serializer); ".
/// </summary>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
_dormant = true;
JToken t = JToken.FromObject(value, serializer);
if (t.Type == JTokenType.Object && value is IContent)
{
JObject o = (JObject)t;
o.AddFirst(new JProperty(TypePropertyName, value.GetType().Name));
o.WriteTo(writer);
}
else
{
t.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanRead => false;
public override bool CanConvert(Type objectType)
{
if (_dormant)
{
_dormant = false;
return false;
}
return true;
}
}

How to serialize a collection (with an indexer property) as a dictionary

I have a class, FooCollection let's say, which implements IEnumerable<Foo>, and also provides an indexer by which one can look up a Foo by its name. Functionally it's read-only as a dictionary. But it's not really a dictionary because users do not get to decide on the keys.
Anyway, I want JSON.NET to serialize this object as a JSON dictionary, instead of as an array, which it's doing now. Sticking JsonDictionaryAttribute on it doesn't work: then it does nothing.
Clues?
You're probably going to need to create a custom JsonConverter for your FooCollection class in order to serialize it the way you want. Since you haven't posted any code at all, I'll just make something up for sake of example. Let's say your Foo and FooCollection classes look like this:
class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
class FooCollection : IEnumerable<Foo>
{
private List<Foo> list = new List<Foo>();
public void Add(Foo foo)
{
list.Add(foo);
}
public Foo this[string name]
{
get { return list.Find(f => f.Name == name); }
}
public IEnumerator<Foo> GetEnumerator()
{
return list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)list).GetEnumerator();
}
}
The following converter would serialize the FooCollection as if it were a dictionary. I'm assuming that you would want the converter to use the value of the Name property as the key for each Foo for purposes of serialization (to match the indexer on the collection), so that is how I implemented it. You can change it to something else by modifying the GetFooKey() method.
class FooCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(FooCollection));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
foreach (Foo foo in (FooCollection)value)
{
writer.WritePropertyName(GetFooKey(foo));
serializer.Serialize(writer, foo);
}
writer.WriteEndObject();
}
// Given a Foo, return its unique key to be used during serialization
private string GetFooKey(Foo foo)
{
return foo.Name;
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is an example program that demonstrates how to use the converter.
class Program
{
static void Main(string[] args)
{
FooCollection coll = new FooCollection();
coll.Add(new Foo { Id = 1, Name = "Moe" });
coll.Add(new Foo { Id = 2, Name = "Larry" });
coll.Add(new Foo { Id = 3, Name = "Curly" });
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new FooCollectionConverter());
settings.Formatting = Newtonsoft.Json.Formatting.Indented;
string json = JsonConvert.SerializeObject(coll, settings);
Console.WriteLine(json);
}
}
And here is the output of the above program:
{
"Moe": {
"Id": 1,
"Name": "Moe"
},
"Larry": {
"Id": 2,
"Name": "Larry"
},
"Curly": {
"Id": 3,
"Name": "Curly"
}
}
Fiddle: https://dotnetfiddle.net/wI2iQG

Parse non-array JSON object as array with Json.net

I am working with an external API that returns a property either as an array or as an object, depending on the count. What is a good way to handle this?
Returning as array:
{
"contacts": {
"address": [
{
"id": "47602070",
"type": "Work",
"street": "MyStreet",
"city": "MyCity",
"zip": "12345",
"country": "USA"
},
{
"id": "47732816",
"type": "GPS",
"street": "50.0,30.0"
}
]
}
}
Returning as object:
{
"contacts": {
"address": {
"id": "47602070",
"type": "Work",
"street": "MyStreet",
"city": "MyCity",
"zip": "12345",
"country": "USA"
}
}
}
I'm thinking a workaround would be to use a custom deserializer and return an array of length 1 for the object case, and default deserialization for the array case, but I don't know how to do that yet.
I tried deserializing the object to an array and hoping Json.net would handle this case for me, but no dice.
Based on Christophe Geers' answer, here is what I ended up doing.
Create a custom JSON converter for always parsing the JSON as an array. If the JSON is a non-array object, then deserialize the object and wrap it in an array.
Mark the corresponding properties with a custom converter attribute.
Custom converter
public class JsonToArrayConverter<T> : CustomCreationConverter<T[]>
{
public override T[] Create(Type objectType)
{
// Default value is an empty array.
return new T[0];
}
public override object ReadJson(JsonReader reader, Type objectType, object
existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
// JSON object was an array, so just deserialize it as usual.
object result = serializer.Deserialize(reader, objectType);
return result;
}
else
{
// JSON object was not an arry, so deserialize the object
// and wrap it in an array.
var resultObject = serializer.Deserialize<T>(reader);
return new T[] {resultObject};
}
}
}
Data structures for the question example
public class Organisation
{
public Contacts contacts;
}
public class Address
{
public string id;
public string street;
public string city;
public string type;
public string zip;
public string country;
}
public class Contacts
{
// Tell JSON.net to use the custom converter for this property.
[JsonConverter(typeof(JsonToArrayConverter<Address>))]
public Address[] address;
}
A custom JSON.NET converter might do the trick here. It's not that hard.
For a DateTime property you might do it as follows. Just decorate the property in question with the custom converter.
[JsonObject(MemberSerialization.OptIn)]
public class MyClass
{
[JsonProperty(PropertyName = "creation_date")]
[JsonConverter(typeof(UnixDateTimeConverter))]
public DateTime CreationDate { get; set; }
}
JSON.NET provides most of the plumbing. Just derive from a base converter.
public class UnixDateTimeConverter : DateTimeConverterBase
{
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{ ...}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{ ... }
}
All you have to do is implement the ReadJson (deserialization) and WriteJson (serialization) methods.
You can find a complete example here:
Writing a custom Json.NET DateTime Converter
For your particular problem you need a bit more control. Try the following type of converter:
public class Contact
{
private List<Address> _addresses = new List<Address>();
public IEnumerable<Address> Addresses { get { return _addresses; }
}
public class ContactConverter : CustomCreationConverter<Contact>
{
public override Contact Create(Type objectType)
{
return new Contact();
}
public override object ReadJson(JsonReader reader, Type objectType, object
existingValue, JsonSerializer serializer)
{
var mappedObj = new Contact();
// Parse JSON data here
// ...
return mappedObj;
}
}
Using a custom converter like the one above you can parse the JSON data yourself and compose the Contact object(s) as you please.
I modified an example I found here:
JSON.NET Custom Converters–A Quick Tour
In this case you need to pass the custom converter when desearilizing.
Contact contact =
JsonConvert.DeserializeObject<Contact>(json, new ContactConverter());
Note: Instead of using a CustomCreationConverter, you can just use an ordinary converter. For example I use something like this:
public class SingleToArrayConverter<T> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var items = (IEnumerable<T>)value;
if (value == null || !items.Any())
{
writer.WriteNull();
}
else if (items.Count() == 1)
{
serializer.Serialize(writer, items.ElementAt(0));
}
else
{
serializer.Serialize(writer, items);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (!CanConvert(objectType))
{
throw new NotSupportedException();
}
if (reader.TokenType == JsonToken.Null)
{
reader.Skip();
return null;
}
else if (reader.TokenType == JsonToken.StartObject)
{
return new T[] { serializer.Deserialize<T>(reader) };
}
else
{
return serializer.Deserialize<T[]>(reader);
}
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(IEnumerable<T>);
}
}

Categories

Resources