Newtonsoft Json Deserialize Dictionary as Key/Value list from DataContractJsonSerializer
You could use a custom converter for this, depending on what token the dictionary starts with, deserialize it JSON.NET's default way, or deserialize it into an array and then turn that array into a Dictionary
:
public class DictionaryConverter : JsonConverter
{
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
IDictionary<string, string> result;
if (reader.TokenType == JsonToken.StartArray)
{
JArray legacyArray = (JArray)JArray.ReadFrom(reader);
result = legacyArray.ToDictionary(
el => el["Key"].ToString(),
el => el["Value"].ToString());
}
else
{
result =
(IDictionary<string, string>)
serializer.Deserialize(reader, typeof(IDictionary<string, string>));
}
return result;
}
public override void WriteJson(
JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return typeof(IDictionary<string, string>).IsAssignableFrom(objectType);
}
public override bool CanWrite
{
get { return false; }
}
}
Then, you can decorate the Dict
property in the Data
class with a JsonConverter
attribute:
public sealed class Data
{
[JsonConverter(typeof(DictionaryConverter))]
public IDictionary<string, string> Dict { get; set; }
}
Then deserializing both strings should work as expected.
How can I deserialize JSON to a simple Dictionarystring,string in ASP.NET?
Json.NET does this...
string json = @"{""key1"":""value1"",""key2"":""value2""}";
var values = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
More examples: Serializing Collections with Json.NET
Deserialize Dictionary with JSON.NET
It should work if you declare Attributes
as List<KeyValuePair<string, string>>
Can I set my own default ListT converter in JSON.net (without attributes)
You can take advantage of the fact that List<T>
implements the non-generic interface IList
to create a non-generic JsonConverter
for all List<T>
types:
public class ArrayObjectConverter : JsonConverter
{
public override bool CanConvert(Type t) => t.GetListItemType() != null;
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) => throw new NotImplementedException();
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
System.Diagnostics.Debug.Assert(objectType.GetListItemType() != null);
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null;
IList value = existingValue as IList ?? (IList)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator!();
if (reader.TokenType == JsonToken.StartArray)
{
serializer.Populate(reader, value);
}
else if (reader.TokenType == JsonToken.StartObject)
{
var itemType = objectType.GetListItemType().ThrowOnNull();
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject)
{
// Eat the property name
reader.AssertTokenType(JsonToken.PropertyName).ReadToContentAndAssert();
// Deserialize the property value and add it to the list.
value.Add(serializer.Deserialize(reader, itemType));
}
}
else
{
throw new JsonSerializationException(string.Format("Unknown token type {0}", reader.TokenType));
}
return value;
}
}
public static partial class JsonExtensions
{
public static JsonReader AssertTokenType(this JsonReader reader, JsonToken tokenType) =>
reader.TokenType == tokenType ? reader : throw new JsonSerializationException(string.Format("Unexpected token {0}, expected {1}", reader.TokenType, tokenType));
public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
reader.ReadAndAssert().MoveToContentAndAssert();
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
public static JsonReader ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
public 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;
}
}
public static partial class ObjectExtensions
{
public static T ThrowOnNull<T>(this T? value) where T : class => value ?? throw new ArgumentNullException(nameof(value));
}
Notes:
Your question mentions The solution should be as optimized as possible so the converter deserializes directly from the
JsonReader
without needing to pre-load anything into intermediateJArray
orJObject
instances.The converter should work for subclasses of
List<T>
as well.If you need to support types that implement
ICollection<T>
types but do not also implement the non-genericIList
interface (such asHashSet<T>
), you will need to use reflection to invoke a generic method from the non-genericReadJson()
e.g. as shown in this answer to Newtonsoft Json Deserialize Dictionary as Key/Value list from DataContractJsonSerializer.
Demo fiddle here.
How to deserialize a dictionary using DataContractJsonSerializer?
Since there isn't a dictionary type in javascript it's rather difficult to have JSON deparse into one. What you're going to have to do is write a converter yourself.
However, that's also true on most custom serialization objects, so hopefully that comes as no big surprise.
Now it should, however, read in as a KeyValuePair so you can try that, to see if it's at least deserializing for you. Rather, you would need a List<KeyValuePair<>>
What a Dictionary<string,string>
translates into for JSON:
var dict = new Dictionary<string,string>;
dict["Red"] = "Rosso";
dict["Blue"] = "Blu";
dict["Green"] = "Verde";
[{"Key":"Red","Value":"Rosso"},
{"Key":"Blue","Value":"Blu"},
{"Key":"Green","Value":"Verde"}]
The same associative from javascript into JSON:
var a = {};
a["Red"] = "Rosso";
a["Blue"] = "Blu";
a["Green"] = "Verde";
{"Red":"Rosso","Blue":"Blu","Green":"Verde"}
So there's the problem in a nutshell.
A few followup links for usefulness
http://my6solutions.com/post/2009/06/17/The-serialization-and-deserialization-of-the-generic-Dictionary-via-the-DataContractJsonSerializer.aspx
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.collectiondatacontractattribute.aspx
Related Topics
Set Item Focus in Listview Wpf
How to Change Currentculture at Runtime
How to Access Gmail API Using Service Account
How to Check How Many Messages Are in a Msmq Queue
Concurrentdictionary Getoradd Async
Openxml Sdk Having Borders for Cell
Multiple Identities in ASP.NET Core 2.0
Use a Custom Thousand Separator in C#
Synchronizationlockexception on Monitor.Exit When Using Await
Add Parameter to Button Click Event
Top Level Domain from Url in C#
How to Register Windows Forms with Simple Injector
How to Have an Optional Parameter for an ASP.NET Soap Web Service
Print HTML Document from Windows Service Without Print Dialog