C# Json Serialization of Dictionary into {Key:Value, ...} Instead of {Key:Key, Value:Value, ...}

Serialize dictionary as array (of key value pairs)

Ah, it turns out this is as straightforward as I'd hoped. My Dictionary<k,v> is subclassed already and I found that I can annotate it with [JsonArrayAttribute]. That gives me exactly the format I need;

"MyDict": [
{
"Key": "Apples",
"Value": {
"Taste": 1341181398,
"Title": "Granny Smith",
}
},
{
"Key:": "Oranges",
"Value:": {
"Taste": 9999999999,
"Title": "Coxes Pippin",
}
},
]

Json.net deserialize dictionary and assign dictionary key to dictionary value's member variable

Immediately after typing the question, I figure out that the Json.net supports a few serialization callback attributes (from System.Runtime.Serialization), which is exactly what I need.

    [OnDeserialized]
internal void OnDeserializedMethod(StreamingContext context)
{
Member4 = "This value was set after deserialization.";
}

Alternatively, instead of using a Dictionary, there's also KeyedCollection<TKey,TItem>, which reduces the need for duplicated dictionary key. The downside is that it won't deserialize and serialize to the desired json format I want by default, and require some customization.

How do you make a tuple or keyvalue pair json serialize like a dictionary in C#

One way to solve this problem is via the usage of ILookup<,> and a custom JsonConverter.

  • You can think of the ILookup<T1, T2> as a Dictionary<T1, IEnumerable<T2>>
  • So, it is a Bag data structure.
var dataSource = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("test", "value1"),
new KeyValuePair<string, string>("test", "value2"),
new KeyValuePair<string, string>("test", "value3"),
new KeyValuePair<string, string>("test2", "somevalue"),
};
var toBeSerializedData = dataSource.ToLookup(pair => pair.Key, pair => pair.Value);
var serializedData =JsonConvert.SerializeObject(toBeSerializedData);

This will generate the following json:

[
[
"value1",
"value2",
"value3"
],
[
"somevalue"
]
]
  • As you see the values are grouped by the Keys.
  • But the keys are omitted and the values are in arrays.

In order to overcome of these we can define a custom JsonConverter:

public class LookupSerializer : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType.GetInterfaces()
.Any(a => a.IsGenericType && a.GetGenericTypeDefinition() == typeof(ILookup<,>));

public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
writer.WriteStartObject();
foreach (object values in (IEnumerable)value)
{
var keyProp = values.GetType().GetProperty("Key");
var keyValue = keyProp.GetValue(values, null);

foreach (var val in (IEnumerable)values)
{
writer.WritePropertyName(keyValue.ToString());
writer.WriteValue(val.ToString());
}
}

writer.WriteEndObject();
}

public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
=> throw new NotImplementedException();
}
  • CanConvert: restricts the usage only for ILookup.
  • WriteJson: iterates through the groups with the outer foreach and iterates through the values via the inner foreach.
  • Here we can't use JObject (and its Add method) to create the output because it would fail with an ArgumentException:

Can not add property test to Newtonsoft.Json.Linq.JObject.

Property with the same name already exists on object.

  • So, we have to use lower level JsonWriter to construct the output.

If you pass an instance of the LookupSerialzer to the SerializeObject (or register the converter application-wide):

var serializedData =JsonConvert.SerializeObject(toBeSerializedData, new LookupSerializer());

then the output will be the one as desired:

{
"test" : "value1",
"test" : "value2",
"test" : "value3",
"test2" : "somevalue"
}


Related Topics



Leave a reply



Submit