ArgumentNullException in Json.NET 6.0.7 when deserializing into NameValueCollection - c#

I've written some custom JsonConverters to deserialize json text into System.Net.Mail.MailMessage objects. Here's the complete code, which can be run in LINQPad. Interestingly, this code runs as expected in Json.NET 4.5.11:
void Main()
{
const string JsonMessage = #"{
""From"": {
""Address"": ""askywalker#theEmpire.gov"",
""DisplayName"": ""Darth Vader""
},
""Sender"": null,
""ReplyTo"": null,
""ReplyToList"": [],
""To"": [
{
""Address"": ""lskywalker#theRebellion.org"",
""DisplayName"": ""Luke Skywalker""
}
],
""Bcc"": [],
""CC"": [
{
""Address"": ""lorgana#alderaan.gov"",
""DisplayName"": ""Princess Leia""
}
],
""Priority"": 0,
""DeliveryNotificationOptions"": 0,
""Subject"": ""Family tree"",
""SubjectEncoding"": null,
""Headers"": [],
""HeadersEncoding"": null,
""Body"": ""<strong>I am your father!</strong>"",
""BodyEncoding"": ""US-ASCII"",
""BodyTransferEncoding"": -1,
""IsBodyHtml"": true,
""Attachments"": [
{
""FileName"": ""skywalker family tree.jpg"",
""ContentBase64"": ""AQIDBAU=""
}
],
""AlternateViews"": []
}";
JsonConvert.DeserializeObject<MailMessage>(JsonMessage,
new MailAddressReadConverter(), new AttachmentReadConverter(), new EncodingReadConverter()).Dump();
}
public class MailAddressReadConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(MailAddress);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var messageJObject = serializer.Deserialize<JObject>(reader);
if (messageJObject == null)
{
return null;
}
var address = messageJObject.GetValue("Address", StringComparison.OrdinalIgnoreCase).ToObject<string>();
JToken displayNameToken;
string displayName;
if (messageJObject.TryGetValue("DisplayName", StringComparison.OrdinalIgnoreCase, out displayNameToken)
&& !string.IsNullOrEmpty(displayName = displayNameToken.ToObject<string>()))
{
return new MailAddress(address, displayName);
}
return new MailAddress(address);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public class AttachmentReadConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Attachment);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var info = serializer.Deserialize<AttachmentInfo>(reader);
var attachment = info != null
? new Attachment(new MemoryStream(Convert.FromBase64String(info.ContentBase64)), "application/octet-stream")
{
ContentDisposition = { FileName = info.FileName }
}
: null;
return attachment;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
private class AttachmentInfo
{
[JsonProperty(Required = Required.Always)]
public string FileName { get; set; }
[JsonProperty(Required = Required.Always)]
public string ContentBase64 { get; set; }
}
}
public class EncodingReadConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Encoding).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var encodingName = serializer.Deserialize<string>(reader);
return encodingName.NullSafe(s => Encoding.GetEncoding(s));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
The exception hit is:
System.ArgumentNullException : Value cannot be null.
at System.RuntimeType.MakeGenericType(Type[] instantiation)
at Newtonsoft.Json.Serialization.JsonArrayContract.CreateWrapper(Object list)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonConverter[] converters)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, JsonConverter[] converters)
Is this a bug in JSON 6? Am I doing something wrong?
EDIT: through further debugging, I've determined that the issue is the Headers property.

The basic problem here is that MailMessage.Headers returns a NameValueCollection, which is sort of like a dictionary, but doesn't implement IDictionary<TKey, TValue> or even the non-generic IDictionary. Instead it implements the non-generic interfaces ICollection and IEnumerable. What these interfaces actually do is to loop through the keys of the collection only, completely ignoring the values.
Thus, if I create a NameValueCollection like so:
public static NameValueCollection CreateCollection()
{
NameValueCollection collection = new NameValueCollection();
FillCollection(collection);
return collection;
}
private static void FillCollection(NameValueCollection collection)
{
collection.Add("Sam", "Dot Net Perls");
collection.Add("Bill", "Microsoft");
collection.Add("Bill", "White House");
collection.Add("Sam", "IBM");
}
and serialize it with Json.NET 6.0.7, it sees the incoming class is a non-generic collection and serializes it as an array:
var collection = CreateCollection();
var json = JsonConvert.SerializeObject(collection);
Debug.WriteLine(json);
producing:
["Sam","Bill"]
As you can see, the values have been stripped.
Then upon deserialization, Json.NET attempts to convert the array of strings back to a NameValueCollection, but has no way to do so. In particular, it tries to construct a temporary list to hold the data being read, but gets confused over the base type of the list, and throws an exception. This is possibly a bug in Json.NET, but even if it didn't throw the exception, data was already lost on storage. This can be reproduced with a simple test class like the following:
public class NameValueCollectionWrapper
{
public NameValueCollectionWrapper()
{
this.Collection = new NameValueCollection();
}
public NameValueCollection Collection { get; private set; }
}
So, the question is, do you want to read the headers, or do you want to ignore them? And if you want to read them, in what format will you receive them? If you want to send and receive them successfully, you will need to write a custom JsonConverter. Doing this is a little tricky because NameValueCollection is almost like a Dictionary<string, string []>, but it preserves the order in which keys are added, which Dictionary does not. Ideally, serialization should preserve that order. This can be accomplished by creating and serializing a adapter pattern wrapper IDictionary<string, string []> such as the one from this answer to how to convert NameValueCollection to JSON string?:
public class NameValueCollectionDictionaryAdapter<TNameValueCollection> : IDictionary<string, string[]>
where TNameValueCollection : NameValueCollection, new()
{
readonly TNameValueCollection collection;
public NameValueCollectionDictionaryAdapter() : this(new TNameValueCollection()) { }
public NameValueCollectionDictionaryAdapter(TNameValueCollection collection)
{
this.collection = collection;
}
// Method instead of a property to guarantee that nobody tries to serialize it.
public TNameValueCollection GetCollection() { return collection; }
#region IDictionary<string,string[]> Members
public void Add(string key, string[] value)
{
if (collection.GetValues(key) != null)
throw new ArgumentException("Duplicate key " + key);
if (value == null)
collection.Add(key, null);
else
foreach (var str in value)
collection.Add(key, str);
}
public bool ContainsKey(string key) { return collection.GetValues(key) != null; }
public ICollection<string> Keys { get { return collection.AllKeys; } }
public bool Remove(string key)
{
bool found = ContainsKey(key);
if (found)
collection.Remove(key);
return found;
}
public bool TryGetValue(string key, out string[] value)
{
return (value = collection.GetValues(key)) != null;
}
public ICollection<string[]> Values
{
get
{
return new ReadOnlyCollectionAdapter<KeyValuePair<string, string[]>, string[]>(this, p => p.Value);
}
}
public string[] this[string key]
{
get
{
var value = collection.GetValues(key);
if (value == null)
throw new KeyNotFoundException(key);
return value;
}
set
{
Remove(key);
Add(key, value);
}
}
#endregion
#region ICollection<KeyValuePair<string,string[]>> Members
public void Add(KeyValuePair<string, string[]> item) { Add(item.Key, item.Value); }
public void Clear() { collection.Clear(); }
public bool Contains(KeyValuePair<string, string[]> item)
{
string[] value;
if (!TryGetValue(item.Key, out value))
return false;
return EqualityComparer<string[]>.Default.Equals(item.Value, value); // Consistent with Dictionary<TKey, TValue>
}
public void CopyTo(KeyValuePair<string, string[]>[] array, int arrayIndex)
{
foreach (var item in this)
array[arrayIndex++] = item;
}
public int Count { get { return collection.Count; } }
public bool IsReadOnly { get { return false; } }
public bool Remove(KeyValuePair<string, string[]> item)
{
if (Contains(item))
return Remove(item.Key);
return false;
}
#endregion
#region IEnumerable<KeyValuePair<string,string[]>> Members
public IEnumerator<KeyValuePair<string, string[]>> GetEnumerator()
{
foreach (string key in collection)
yield return new KeyValuePair<string, string[]>(key, collection.GetValues(key));
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
#endregion
}
public static class NameValueCollectionExtensions
{
public static NameValueCollectionDictionaryAdapter<TNameValueCollection> ToDictionaryAdapter<TNameValueCollection>(this TNameValueCollection collection)
where TNameValueCollection : NameValueCollection, new()
{
if (collection == null)
throw new ArgumentNullException();
return new NameValueCollectionDictionaryAdapter<TNameValueCollection>(collection);
}
}
public class ReadOnlyCollectionAdapter<TIn, TOut> : CollectionAdapterBase<TIn, TOut, ICollection<TIn>>
{
public ReadOnlyCollectionAdapter(ICollection<TIn> collection, Func<TIn, TOut> toOuter)
: base(() => collection, toOuter)
{
}
public override void Add(TOut item) { throw new NotImplementedException(); }
public override void Clear() { throw new NotImplementedException(); }
public override bool IsReadOnly { get { return true; } }
public override bool Remove(TOut item) { throw new NotImplementedException(); }
}
public abstract class CollectionAdapterBase<TIn, TOut, TCollection> : ICollection<TOut>
where TCollection : ICollection<TIn>
{
readonly Func<TCollection> getCollection;
readonly Func<TIn, TOut> toOuter;
public CollectionAdapterBase(Func<TCollection> getCollection, Func<TIn, TOut> toOuter)
{
if (getCollection == null || toOuter == null)
throw new ArgumentNullException();
this.getCollection = getCollection;
this.toOuter = toOuter;
}
protected TCollection Collection { get { return getCollection(); } }
protected TOut ToOuter(TIn inner) { return toOuter(inner); }
#region ICollection<TOut> Members
public abstract void Add(TOut item);
public abstract void Clear();
public virtual bool Contains(TOut item)
{
var comparer = EqualityComparer<TOut>.Default;
foreach (var member in Collection)
if (comparer.Equals(item, ToOuter(member)))
return true;
return false;
}
public void CopyTo(TOut[] array, int arrayIndex)
{
foreach (var item in this)
array[arrayIndex++] = item;
}
public int Count { get { return Collection.Count; } }
public abstract bool IsReadOnly { get; }
public abstract bool Remove(TOut item);
#endregion
#region IEnumerable<TOut> Members
public IEnumerator<TOut> GetEnumerator()
{
foreach (var item in Collection)
yield return ToOuter(item);
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
#endregion
}
Next, create the following JsonConverter which both serializes and deserializes a NameValueCollection and skips values in the broken, old format:
public class NameValueJsonConverter<TNameValueCollection> : JsonConverter
where TNameValueCollection : NameValueCollection, new()
{
public override bool CanConvert(Type objectType)
{
return typeof(TNameValueCollection).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.SkipComments().TokenType == JsonToken.Null)
return null;
var collection = (TNameValueCollection)existingValue ?? new TNameValueCollection();
var dictionaryWrapper = collection.ToDictionaryAdapter();
if (reader.TokenType != JsonToken.StartObject)
{
// Old buggy name value collection format in which the values were not written and so cannot be recovered.
// Skip the token and all its children.
reader.Skip();
}
else
{
serializer.Populate(reader, dictionaryWrapper);
}
return collection;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var collection = (TNameValueCollection)value;
var dictionaryWrapper = new NameValueCollectionDictionaryAdapter<TNameValueCollection>(collection);
serializer.Serialize(writer, dictionaryWrapper);
}
}
public static partial class JsonExtensions
{
public static JsonReader SkipComments(this JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
}
Finally, apply NameValueJsonConverter<NameValueCollection> as you do your other converters. This produces output in Json dictionary style while preserving order, for instance:
{"Sam":["Dot Net Perls","IBM"],"Bill":["Microsoft","White House"]}
I don't have Json.NET 4.x available to test, but I doubt it correctly serialized both the keys and values of a NameValueCollection. You may want to install that version to doublecheck what it did.
Update
Just checked Json.NET 4.5.11. In that version the NameValueCollection property in my NameValueCollectionWrapper test class is serialized as an array of key strings, which is then ignored on deserialization (the collection comes back empty). So it's probably a regression that Json.NET version 6 throws an exception rather than ignoring the property.

Related

Change Change JSON from T to List<T> [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!

Dealing with JSON field that holds different types in C# [duplicate]

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 8 years ago.
I have to read a JSON document, which has a field that can contain different types.
For example can be either a long or an array of integers. I know I will need to use a custom deserializer, but am not sure how.
In the example below the xx field sometimes is a long, otherwise an array of ints.
Any help on how to deal with this is appreciated.
static void JsonTest() {
const string json = #"
{
'Code': 'XYZ',
'Response': {
'Type' : 'S',
'Docs': [
{
'id' : 'test1',
'xx' : 1
},
{
'id' : 'test2',
'xx' : [1, 2, 4, 8]
},
]
}
}";
A a;
try {
a = JsonConvert.DeserializeObject<A>(json);
}
catch( Exception ex ) {
Console.Error.WriteLine(ex.Message);
}
}
public class A {
public string Code;
public TResponse Response;
}
public class TResponse {
public string Type;
public List<Doc> Docs;
}
public class Doc {
public string id;
public int[] xx;
}
My implementation based on the suggestion below (changed array to long from int):
[JsonConverter(typeof(DocConverter))]
public class Doc {
public string id;
public long[] xx;
}
public class DocConverter : JsonConverter {
public override bool CanWrite { get { return false; } }
public override bool CanConvert( Type objectType ) {
return typeof(Doc).IsAssignableFrom(objectType);
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) {
JObject item = JObject.Load(reader);
Doc doc = new Doc();
doc.id = item["id"].ToObject<string>();
if( item["xx"].Type == JTokenType.Long )
doc.xx = new [] { item["xx"].ToObject<long>() };
else
doc.xx = item["xx"].ToObject<long[]>();
return doc;
}
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer ) {
throw new NotImplementedException();
}
}
Since xx can either be a long or an array of ints, it makes sense to turn Doc into a class hierarchy. (If it were a single long or an array of longs, it would make sense to read them all into a single class.)
You can do this by using a JsonConverter, like so:
[JsonConverter(typeof(DocConverter))]
public abstract class Doc
{
public string id;
}
[JsonConverter(typeof(NoConverter))] // Prevents infinite recursion when converting a class instance known to be of type DocSingle
public class DocSingle : Doc
{
public long xx;
}
[JsonConverter(typeof(NoConverter))] // Prevents infinite recursion when converting a class instance known to be of type DocList
public class DocList : Doc
{
public int[] xx;
}
public class DocConverter : JsonConverter
{
public override bool CanWrite { get { return false; } }
public override bool CanConvert(Type objectType)
{
return typeof(Doc).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
JObject item = JObject.Load(reader);
if (item["xx"].Type == JTokenType.Integer)
{
return item.ToObject<DocSingle>();
}
else
{
return item.ToObject<DocList>();
}
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public class NoConverter : JsonConverter
{
public override bool CanRead { get { return false; } }
public override bool CanWrite { get { return false; } }
public override bool CanConvert(Type objectType)
{
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Update
incidentally, if you're willing to simplify your data model to say that xx can either be a single long or an array of longs, you can simplify the code as follows:
[JsonConverter(typeof(DocConverter))]
public sealed class Doc
{
public string id;
public long[] xx;
}
public class DocConverter : JsonConverter
{
public override bool CanWrite { get { return true; } }
public override bool CanConvert(Type objectType)
{
return typeof(Doc).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
JObject item = JObject.Load(reader);
var doc = new Doc();
JToken id = item["id"];
if (id != null)
doc.id = id.ToString();
JToken xx = item["xx"];
if (xx != null)
{
if (xx.Type == JTokenType.Integer)
{
var val = (long)xx;
doc.xx = new long[] { val };
}
else if (xx.Type == JTokenType.Array)
{
var val = xx.ToObject<long[]>();
doc.xx = val;
}
else
{
Debug.WriteLine("Unknown type of JToken for \"xx\": " + xx.ToString());
}
}
return doc;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var doc = (Doc)value;
writer.WriteStartObject();
writer.WritePropertyName("id");
writer.WriteValue(doc.id);
var xx = doc.xx;
if (xx != null)
{
writer.WritePropertyName("xx");
if (xx.Length == 1)
{
writer.WriteValue(xx[0]);
}
else
{
writer.WriteStartArray();
foreach (var x in xx)
{
writer.WriteValue(x);
}
writer.WriteEndArray();
}
}
writer.WriteEndObject();
}
}
You have a string, try json.Contains("'Type':'S'").
Then deserialize it to the proper model.

Serialise custom collection With additional properties and on Add to hookup item propertChanged events

I have a custom collection which I would like to serialise with JSON.NET:
I need it to serialise the child collections within this custom collection.
On deserialisation I need to hook up PropertyChanged event for items within the collection.
If I pass my collection as is, Json sees IEnumerable and serialises the items in the collection ok, but ignores the other collections within.
If I attribute the collection with [JsonObject] it will serialise all the internal collections but not the internal _list;
if I add [JsonProperty] to the internal _list it will serialise all collections.
But since it sets the _list as a property during deserialization The Add Method of my custom collection is not called and as a therefore the propertyChanged events of the items within _list never get hooked up.
I tried hiding the internal _list and wrapping it with a public getter setter, I thought if during deserialization it used the public setter to set the internal _list I could attach to the item events there, but that does not work either.
Is there anything I can do to during deserialization to get the notifyproperty changed events of the items in the internal _list hooked up?
Edit: I tried a converter:
public class TrackableCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TrackableCollectionCollection<ITrackableEntity>);
}
public override object ReadJson(
JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
// N.B. null handling is missing
var surrogate = serializer.Deserialize<TrackableCollectionCollection<ITrackableEntity>>(reader);
var trackableCollection = new TrackableCollectionCollection<ITrackableEntity>();
foreach (var el in surrogate)
trackableCollection.Add(el);
foreach (var el in surrogate.NewItems)
trackableCollection.NewItems.Add(el);
foreach (var el in surrogate.ModifiedItems)
trackableCollection.ModifiedItems.Add(el);
foreach (var el in surrogate.DeletedItems)
trackableCollection.DeletedItems.Add(el);
return trackableCollection;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
serializer.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
serializer.Serialize(writer, value);
}
}
Gives error:
{"Message":"An error has occurred.","ExceptionMessage":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.","ExceptionType":"System.InvalidOperationException","StackTrace":null,"InnerException":{"Message":"An error has occurred.","ExceptionMessage":"Token PropertyName in state Property would result in an invalid JSON object. Path '[0]'.","ExceptionType":"Newtonsoft.Json.JsonWriterException","StackTrace":" at Newtonsoft.Json.JsonWriter.AutoComplete(JsonToken tokenBeingWritten)\r\n at Newtonsoft.Json.JsonWriter.InternalWritePropertyName(String name)\r\n at Newtonsoft.Json.JsonTextWriter.WritePropertyName(String name, Boolean escape)\r\n at Newtonsoft.Json.Serialization.JsonProperty.WritePropertyName(JsonWriter writer)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(JsonWriter writer, Object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)\r\n at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)\r\n at Newtonsoft.Json.JsonSerializer.Serialize(JsonWriter jsonWriter, Object value)\r\n at System.Net.Http.Formatting.BaseJsonMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, Encoding effectiveEncoding)\r\n at System.Net.Http.Formatting.JsonMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, Encoding effectiveEncoding)\r\n at System.Net.Http.Formatting.BaseJsonMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content)\r\n at System.Net.Http.Formatting.BaseJsonMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.GetResult()\r\n at System.Web.Http.WebHost.HttpControllerHandler.d__1b.MoveNext()"}}
here is the collection as I have it so far.
[Serializable]
[JsonObject]
[JsonConverter(typeof(TrackableCollectionConverter))]
public class TrackableCollectionCollection<T> : IList<T> where T : ITrackableEntity
{
[JsonIgnore]
IList<T> _list = new List<T>();
[JsonProperty]
public IList<T> List
{
get { return _list; }
set
{
_list = value;
foreach(var item in _list)
item.PropertyChanged += item_PropertyChanged;
}
}
[DataMember]
public IList<T> NewItems
{
get { return _newItems; }
}
IList<T> _newItems = new List<T>();
[DataMember]
public IList<T> ModifiedItems
{
get { return _modifiedChildren; }
}
IList<T> _modifiedChildren = new List<T>();
[DataMember]
public IList<T> DeletedItems
{
get { return _deletedItems; }
}
IList<T> _deletedItems = new List<T>();
#region Implementation of IEnumerable
public IEnumerator<T> GetEnumerator()
{
return _list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region Implementation of ICollection<T>
public void Add(T item)
{
if (item.Id.Equals(default(Guid)))
_newItems.Add(item);
else
{
// I thought about doing this but that would screw the EF object generation.
// throw new NotSupportedException("");
}
item.PropertyChanged += item_PropertyChanged;
_list.Add(item);
}
public void Clear()
{
NewItems.Clear();
ModifiedItems.Clear();
foreach(var item in _list)
{
item.PropertyChanged -= item_PropertyChanged;
DeletedItems.Add(item);
}
_list.Clear();
}
public bool Contains(T item)
{
return _list.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_list.CopyTo(array, arrayIndex);
}
public bool Remove(T item)
{
if (NewItems.Contains(item))
NewItems.Remove(item);
if (ModifiedItems.Contains(item))
ModifiedItems.Remove(item);
if (!DeletedItems.Contains(item))
DeletedItems.Add(item);
return _list.Remove(item);
}
public int Count
{
get { return _list.Count; }
}
public bool IsReadOnly
{
get { return _list.IsReadOnly; }
}
#endregion
#region Implementation of IList<T>
public int IndexOf(T item)
{
return _list.IndexOf(item);
}
public void Insert(int index, T item)
{
if (item.Id.Equals(default(Guid)))
_newItems.Add(item);
else
{
// I thought about doing this but that would screw the EF object generation.
// throw new NotSupportedException("");
}
item.PropertyChanged += item_PropertyChanged;
_list.Insert(index, item);
}
public void RemoveAt(int index)
{
var item = this[index];
if (NewItems.Contains(item))
NewItems.Remove(item);
if (ModifiedItems.Contains(item))
ModifiedItems.Remove(item);
if (!DeletedItems.Contains(item))
DeletedItems.Add(item);
_list.RemoveAt(index);
}
public T this[int index]
{
get { return _list[index]; }
set { _list[index] = value; }
}
#endregion
void item_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (((T)sender).Id.Equals(default(Guid)))
return; // The Item is already in the newItems collection
if (ModifiedItems.Contains((T)sender))
return;
ModifiedItems.Add((T)sender);
}
}
You could serialize your custom container as a JsonObject, as you are doing now, and serialize the embedded list as a proxy ObservableCollection<T>. You can then listen in for additions and removals to the proxy and handle them accordingly. Note -- no custom JsonConverter required. Since I don't have your definition for ITrackableEntity, here's a quick prototype wrapper IList<T> for a List<T>:
[Serializable]
[JsonObject]
public class ListContainer<T> : IList<T>
{
[JsonIgnore]
readonly List<T> _list = new List<T>();
[JsonProperty("List")]
private IList<T> SerializableList
{
get
{
var proxy = new ObservableCollection<T>(_list);
proxy.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(proxy_CollectionChanged);
return proxy;
}
set
{
_list.Clear();
_list.AddRange(value);
}
}
void proxy_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
foreach (var item in e.NewItems.Cast<T>())
Add(item);
}
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
foreach (var item in e.NewItems.Cast<T>())
Remove(item);
}
else
{
Debug.Assert(false);
throw new NotImplementedException();
}
}
[JsonIgnore]
public int Count
{
get { return _list.Count; }
}
[JsonIgnore]
public bool IsReadOnly
{
get { return ((IList<T>)_list).IsReadOnly; }
}
// Everything beyond here is boilerplate.
#region IList<T> Members
public int IndexOf(T item)
{
return _list.IndexOf(item);
}
public void Insert(int index, T item)
{
_list.Insert(index, item);
}
public void RemoveAt(int index)
{
_list.RemoveAt(index);
}
public T this[int index]
{
get
{
return _list[index];
}
set
{
_list[index] = value;
}
}
#endregion
#region ICollection<T> Members
public void Add(T item)
{
_list.Add(item);
}
public void Clear()
{
_list.Clear();
}
public bool Contains(T item)
{
return _list.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_list.CopyTo(array, arrayIndex);
}
public bool Remove(T item)
{
return _list.Remove(item);
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return _list.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
And then, to test:
public static void TestListContainerJson()
{
var list = new ListContainer<int>();
list.Add(101);
list.Add(102);
list.Add(103);
var json = JsonConvert.SerializeObject(list);
var newList = JsonConvert.DeserializeObject<ListContainer<int>>(json);
Debug.Assert(list.SequenceEqual(newList)); // No assert.
}
Update
It turns out that Json.NET follows the same pattern as XmlSerializer: if you serialize the proxy list as an array, the setter will be called with the fully populated array after being read, and you can add them as required:
[Serializable]
[JsonObject]
public class ListContainer<T> : IList<T>
{
[JsonIgnore]
readonly List<T> _list = new List<T>();
[JsonProperty("List")]
private T [] SerializableList
{
get
{
return _list.ToArray();
}
set
{
Clear();
foreach (var item in value)
Add(item);
}
}
[JsonIgnore]
public int Count
{
get { return _list.Count; }
}
[JsonIgnore]
public bool IsReadOnly
{
get { return ((IList<T>)_list).IsReadOnly; }
}
// Everything beyond here is boilerplate.
}
This is much cleaner than my first solution.
Also, I suspect that your NewItems and ModifiedItems list contain references to items in the main _list. By default Json.NET will effectively clone these during serialization & deserialization. To avoid this, look into the PreserveReferencesHandling functionality. More here.
I solved my issue.
First:[JsonConverter(typeof(TrackableCollectionConverter))] should not be on the class definition. other than that TrackableCollection remains untouched.
I have modified my converter thus:
public class TrackableCollectionConverter<TEntity, TDeserialiseType> : JsonConverter where TEntity: ITrackableEntity
{
public override bool CanConvert(Type objectType)
{
return true;
//return objectType == typeof(TrackableCollectionCollection<ITrackableEntity>);
}
public override object ReadJson(
JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
// N.B. null handling is missing
var surrogate = serializer.Deserialize<TDeserialiseType>(reader) as TrackableCollectionCollection<TEntity>;
var trackablecollection = new TrackableCollectionCollection<TEntity>();
foreach (var el in surrogate)
trackablecollection.Add(el);
foreach (var el in surrogate.NewItems)
trackablecollection.NewItems.Add(el);
foreach (var el in surrogate.ModifiedItems)
trackablecollection.ModifiedItems.Add(el);
foreach (var el in surrogate.DeletedItems)
trackablecollection.DeletedItems.Add(el);
return trackablecollection;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
serializer.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
serializer.Serialize(writer, value);
}
}
And finally put the CustomConverter attribute in the correct place. on the Entity Property. In my case I have an Entity called Parent:
[JsonObject(IsReference = true)]
[DataContract(IsReference = true)]
public class Parent : TrackableEntityBase
{
[DataMember]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ParentId
{
get { return base.Id ; }
set
{
if (base.Id.Equals(default(Guid)))
base.Id = value;
if (base.Id.Equals(value))
return;
throw new InvalidOperationException("Primary Keys cannot be changed once set.");
}
}
[DataMember]
public String Name
{
get { return _name; }
set
{
if (!String.IsNullOrWhiteSpace(_name) && _name.Equals(value, StringComparison.Ordinal))
{
return;
}
_name = value;
OnPropertyChanged("Name");
}
}
String _name;
[DataMember]
[JsonConverter(typeof(TrackableCollectionConverter<Child, TrackableCollectionCollection<Child>>))]
public virtual TrackableCollectionCollection<Child> Children { get; set; }
}
This works well producing json like this:
{"$id":"1","ParentId":"6d884973-5060-e411-8265-cffad877042b","Name":"Parent1","Children":{"List":[{"$id":"2","ChildId":"5bd66353-3f61-e411-8265-cffad877042b","ParentId":"6d884973-5060-e411-8265-cffad877042b","Name":"Billy","Parent":{"$ref":"1"},"Id":"5bd66353-3f61-e411-8265-cffad877042b","IsModified":true}],"NewItems":[],"ModifiedItems":[{"$ref":"2"}],"DeletedItems":[],"Count":1,"IsReadOnly":false},"Id":"6d884973-5060-e411-8265-cffad877042b","IsModified":true}

how to convert NameValueCollection to JSON string?

I tried:
NameValueCollection Data = new NameValueCollection();
Data.Add("foo","baa");
string json = new JavaScriptSerializer().Serialize(Data);
it returns: ["foo"] I expected {"foo" : "baa"}
How do I to do this?
One way to serialize NameValueCollection is by first converting it to Dictionary and then serialize the Dictionary. To convert to dictionary:
thenvc.AllKeys.ToDictionary(k => k, k => thenvc[k]);
If you need to do the conversion frequently, you can also create an extension method to NameValueCollection:
public static class NVCExtender
{
public static IDictionary<string, string> ToDictionary(
this NameValueCollection source)
{
return source.AllKeys.ToDictionary(k => k, k => source[k]);
}
}
so you can do the conversion in one line like this:
NameValueCollection Data = new NameValueCollection();
Data.Add("Foo", "baa");
var dict = Data.ToDictionary();
Then you can serialize the dictionary:
var json = new JavaScriptSerializer().Serialize(dict);
// you get {"Foo":"baa"}
But NameValueCollection can have multiple values for one key, for example:
NameValueCollection Data = new NameValueCollection();
Data.Add("Foo", "baa");
Data.Add("Foo", "again?");
If you serialize this you will get {"Foo":"baa,again?"}.
You can modify the converter to produce IDictionary<string, string[]> instead:
public static IDictionary<string, string[]> ToDictionary(
this NameValueCollection source)
{
return source.AllKeys.ToDictionary(k => k, k => source.GetValues(k));
}
So you can get serialized value like this: {"Foo":["baa","again?"]}.
NameValueCollection isn't an IDictionary, so the JavaScriptSerializer cannot serialize it as you expect directly. You'll need to first convert it into a dictionary, then serialize it.
Update: following questions regarding multiple values per key, the call to nvc[key] will simply return them separated by a comma, which may be ok. If not, one can always call GetValues and decide what to do with the values appropriately. Updated the code below to show one possible way.
public class StackOverflow_7003740
{
static Dictionary<string, object> NvcToDictionary(NameValueCollection nvc, bool handleMultipleValuesPerKey)
{
var result = new Dictionary<string, object>();
foreach (string key in nvc.Keys)
{
if (handleMultipleValuesPerKey)
{
string[] values = nvc.GetValues(key);
if (values.Length == 1)
{
result.Add(key, values[0]);
}
else
{
result.Add(key, values);
}
}
else
{
result.Add(key, nvc[key]);
}
}
return result;
}
public static void Test()
{
NameValueCollection nvc = new NameValueCollection();
nvc.Add("foo", "bar");
nvc.Add("multiple", "first");
nvc.Add("multiple", "second");
foreach (var handleMultipleValuesPerKey in new bool[] { false, true })
{
if (handleMultipleValuesPerKey)
{
Console.WriteLine("Using special handling for multiple values per key");
}
var dict = NvcToDictionary(nvc, handleMultipleValuesPerKey);
string json = new JavaScriptSerializer().Serialize(dict);
Console.WriteLine(json);
Console.WriteLine();
}
}
}
If your dictionary is not intended to contain many entries, you can use the class:
System.Collections.Specialized.ListDictionary
For completeness' sake, and because the question continues to get asked (e.g. here), as long as you are using Json.NET or DataContractJsonSerializer (but not JavaScriptSerializer), you could use the adapter pattern and wrap the NameValueCollection in an IDictionary<string, string[]> adapter, and serialize that using any serializer that fully supports serializing arbitrary dictionaries.
Once such adapter is as follows:
public class NameValueCollectionDictionaryAdapter<TNameValueCollection> : IDictionary<string, string[]>
where TNameValueCollection : NameValueCollection, new()
{
readonly TNameValueCollection collection;
public NameValueCollectionDictionaryAdapter() : this(new TNameValueCollection()) { }
public NameValueCollectionDictionaryAdapter(TNameValueCollection collection)
{
this.collection = collection;
}
// Method instead of a property to guarantee that nobody tries to serialize it.
public TNameValueCollection GetCollection() { return collection; }
#region IDictionary<string,string[]> Members
public void Add(string key, string[] value)
{
if (collection.GetValues(key) != null)
throw new ArgumentException("Duplicate key " + key);
if (value == null)
collection.Add(key, null);
else
foreach (var str in value)
collection.Add(key, str);
}
public bool ContainsKey(string key) { return collection.GetValues(key) != null; }
public ICollection<string> Keys { get { return collection.AllKeys; } }
public bool Remove(string key)
{
bool found = ContainsKey(key);
if (found)
collection.Remove(key);
return found;
}
public bool TryGetValue(string key, out string[] value)
{
return (value = collection.GetValues(key)) != null;
}
public ICollection<string[]> Values
{
get
{
return new ReadOnlyCollectionAdapter<KeyValuePair<string, string[]>, string[]>(this, p => p.Value);
}
}
public string[] this[string key]
{
get
{
var value = collection.GetValues(key);
if (value == null)
throw new KeyNotFoundException(key);
return value;
}
set
{
Remove(key);
Add(key, value);
}
}
#endregion
#region ICollection<KeyValuePair<string,string[]>> Members
public void Add(KeyValuePair<string, string[]> item) { Add(item.Key, item.Value); }
public void Clear() { collection.Clear(); }
public bool Contains(KeyValuePair<string, string[]> item)
{
string[] value;
if (!TryGetValue(item.Key, out value))
return false;
return EqualityComparer<string[]>.Default.Equals(item.Value, value); // Consistent with Dictionary<TKey, TValue>
}
public void CopyTo(KeyValuePair<string, string[]>[] array, int arrayIndex)
{
foreach (var item in this)
array[arrayIndex++] = item;
}
public int Count { get { return collection.Count; } }
public bool IsReadOnly { get { return false; } }
public bool Remove(KeyValuePair<string, string[]> item)
{
if (Contains(item))
return Remove(item.Key);
return false;
}
#endregion
#region IEnumerable<KeyValuePair<string,string[]>> Members
public IEnumerator<KeyValuePair<string, string[]>> GetEnumerator()
{
foreach (string key in collection)
yield return new KeyValuePair<string, string[]>(key, collection.GetValues(key));
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
#endregion
}
public static class NameValueCollectionExtensions
{
public static NameValueCollectionDictionaryAdapter<TNameValueCollection> ToDictionaryAdapter<TNameValueCollection>(this TNameValueCollection collection)
where TNameValueCollection : NameValueCollection, new()
{
if (collection == null)
throw new ArgumentNullException();
return new NameValueCollectionDictionaryAdapter<TNameValueCollection>(collection);
}
}
public class ReadOnlyCollectionAdapter<TIn, TOut> : CollectionAdapterBase<TIn, TOut, ICollection<TIn>>
{
public ReadOnlyCollectionAdapter(ICollection<TIn> collection, Func<TIn, TOut> toOuter)
: base(() => collection, toOuter)
{
}
public override void Add(TOut item) { throw new NotImplementedException(); }
public override void Clear() { throw new NotImplementedException(); }
public override bool IsReadOnly { get { return true; } }
public override bool Remove(TOut item) { throw new NotImplementedException(); }
}
public abstract class CollectionAdapterBase<TIn, TOut, TCollection> : ICollection<TOut>
where TCollection : ICollection<TIn>
{
readonly Func<TCollection> getCollection;
readonly Func<TIn, TOut> toOuter;
public CollectionAdapterBase(Func<TCollection> getCollection, Func<TIn, TOut> toOuter)
{
if (getCollection == null || toOuter == null)
throw new ArgumentNullException();
this.getCollection = getCollection;
this.toOuter = toOuter;
}
protected TCollection Collection { get { return getCollection(); } }
protected TOut ToOuter(TIn inner) { return toOuter(inner); }
#region ICollection<TOut> Members
public abstract void Add(TOut item);
public abstract void Clear();
public virtual bool Contains(TOut item)
{
var comparer = EqualityComparer<TOut>.Default;
foreach (var member in Collection)
if (comparer.Equals(item, ToOuter(member)))
return true;
return false;
}
public void CopyTo(TOut[] array, int arrayIndex)
{
foreach (var item in this)
array[arrayIndex++] = item;
}
public int Count { get { return Collection.Count; } }
public abstract bool IsReadOnly { get; }
public abstract bool Remove(TOut item);
#endregion
#region IEnumerable<TOut> Members
public IEnumerator<TOut> GetEnumerator()
{
foreach (var item in Collection)
yield return ToOuter(item);
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
#endregion
}
Then an adapted can be constructed for a given NameValueCollection Data simply by doing:
var adapter = Data.ToDictionaryAdapter();
Notes:
Using the adapter may be be more performant than simply creating a copied dictionary, and should work well with any serializer that fully supports dictionary serialization.
The adapter might also be useful in using a NameValueCollection with any other code that expects an IDictionary of some sort - this is the fundamental advantage of the adapter pattern.
That being said, JavaScriptSerializer cannot be used with the adapter because this serializer cannot serialize an arbitrary type implementing IDictionary<TKey, TValue> that does not also inherit from Dictionary<TKey, TValue>. For details see Serializing dictionaries with JavaScriptSerializer.
When using DataContractJsonSerializer, a NameValueCollection can be replaced with an adapter in the serialization graph by using the data contract surrogate mechanism.
When using Json.NET a NameValueCollection can be replaced with an adapter using a custom JsonConverter such as the following:
public class NameValueJsonConverter<TNameValueCollection> : JsonConverter
where TNameValueCollection : NameValueCollection, new()
{
public override bool CanConvert(Type objectType)
{
return typeof(TNameValueCollection).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.SkipComments().TokenType == JsonToken.Null)
return null;
// Reuse the existing NameValueCollection if present
var collection = (TNameValueCollection)existingValue ?? new TNameValueCollection();
var dictionaryWrapper = collection.ToDictionaryAdapter();
serializer.Populate(reader, dictionaryWrapper);
return collection;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var collection = (TNameValueCollection)value;
var dictionaryWrapper = new NameValueCollectionDictionaryAdapter<TNameValueCollection>(collection);
serializer.Serialize(writer, dictionaryWrapper);
}
}
public static partial class JsonExtensions
{
public static JsonReader SkipComments(this JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
}
Which could be used e.g. as follows:
string json = JsonConvert.SerializeObject(Data, Formatting.Indented, new NameValueJsonConverter<NameValueCollection>());
NameValueCollection supports all of the following
A null value for a given key;
Multiple values for a given key (in which case NameValueCollection.Item[String] returns a comma-separated list of values);
A single value containing an embedded comma (which cannot be distinguished from the case of multiple values when using NameValueCollection.Item[String]).
Thus the adapter must implement IDictionary<string, string[]> rather than IDictionary<string, string> and also take care to handle a null value array.
Sample fiddle (including some basic unit testing) here: https://dotnetfiddle.net/gVPSi7

json.net: specify converter for dictionary keys

I have a JSON:
{
"data": { "A": 5, "B": 6 },
"foo": "foo",
"bar": "bar"
}
I need to deserialize data into a class:
public Dictionary<MyEnum, int> Data { get; set; }
public string Foo { get; set; }
public string Bar { get; set; }
But MyEnum values are CodeA, and CodeB instead of simply A and B respectively.
I have a custom Converter that can handle conversion. But how do I specify a JsonConverter to use with Dictionary keys?
I believe the only way is to make a JsonConverter for the whole Dictionary<MyEnum, int> type, or Dictionary<MyEnum, T>.
Dictionary keys are not regarded as values and will not be run through the JsonConverters. TypeConverters would have been a solution, but the default string to enum conversion will enter before it looks at the TypeConverters.
So... I don't think it can be done any other way.
EDIT:
Not fully tested, but I use something like this in a project of mine:
public class DictionaryWithSpecialEnumKeyConverter : JsonConverter
{
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var valueType = objectType.GetGenericArguments()[1];
var intermediateDictionaryType = typeof(Dictionary<,>).MakeGenericType(typeof(string), valueType);
var intermediateDictionary = (IDictionary)Activator.CreateInstance(intermediateDictionaryType);
serializer.Populate(reader, intermediateDictionary);
var finalDictionary = (IDictionary)Activator.CreateInstance(objectType);
foreach (DictionaryEntry pair in intermediateDictionary)
finalDictionary.Add(Enum.Parse(MyEnum, "Code" + pair.Key, false), pair.Value);
return finalDictionary;
}
public override bool CanConvert(Type objectType)
{
return objectType.IsA(typeof(IDictionary<,>)) &&
objectType.GetGenericArguments()[0].IsA<MyEnum>();
}
}
You will need this little helper:
public static bool IsA(this Type type, Type typeToBe)
{
if (!typeToBe.IsGenericTypeDefinition)
return typeToBe.IsAssignableFrom(type);
var toCheckTypes = new List<Type> { type };
if (typeToBe.IsInterface)
toCheckTypes.AddRange(type.GetInterfaces());
var basedOn = type;
while (basedOn.BaseType != null)
{
toCheckTypes.Add(basedOn.BaseType);
basedOn = basedOn.BaseType;
}
return toCheckTypes.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeToBe);
}
Hope it works out for you.
Here is a generic solution for the problem of using json with Dictionary with any type of key:
[JsonObject]
public class MyKeyValuePair<TKey, TValue>
{
public TKey MyKey;
public TValue MyValue;
[JsonConstructor]
public MyKeyValuePair()
{
}
public MyKeyValuePair(TKey t1, TValue t2)
{
MyKey = t1;
MyValue = t2;
}
}
[JsonObject]
public class MyDictionaty<TKey, TValue>
{
public ICollection<MyKeyValuePair<TKey, TValue>> Collection;
[JsonConstructor]
public MyDictionaty()
{
}
public MyDictionaty(Dictionary<TKey, TValue> refund)
{
Collection = BuildMyKeyValuePairCollection(refund);
}
internal Dictionary<TKey, TValue> ToDictionary()
{
return Collection.ToDictionary(pair => pair.MyKey, pair => pair.MyValue);
}
private ICollection<MyKeyValuePair<TKey, TValue>> BuildMyKeyValuePairCollection(Dictionary<TKey, TValue> refund)
{
return refund.Select(o => new MyKeyValuePair<TKey, TValue>(o.Key, o.Value)).ToList();
}
}
[JsonObject]
public class ClassWithDictionary
{
[JsonProperty]
private readonly MyDictionary<AnyKey, AnyValue> _myDictionary;
private Dictionary<AnyKey, AnyValue> _dic;
[JsonConstructor]
public ClassWithDictionary()
{
}
public ClassWithDictionary(Dictionary<AnyKey, AnyValue> dic)
{
_dic= dic;
_myDictionary = new MyDictionaty<AnyKey, AnyValue>(dic);
}
public Dictionary<AnyKey, AnyValue> GetTheDictionary()
{
_dic = _dic??_myDictionary.ToDictionary();
return _dic;
}
}
I couldn't get any TypeConverter solution working and didn't want to have a JsonConverter that builds a string-key dictionary and then copies everything into a new dictionary, so I went with something like this:
public sealed class MyEnumKeyDictionary<TValue> : IReadOnlyDictionary<MyEnum, TValue>, IDictionary<string, TValue>
{
private readonly Dictionary<MyEnum, TValue> actual = new Dictionary<MyEnum, TValue>();
// implement IReadOnlyDictionary implicitly, passing everything from `actual`
// implement IDictionary explicitly, passing everything into/from `actual` after doing Enum.Parse/Enum.ToString
}

Categories

Resources