Skip to content

Instantly share code, notes, and snippets.

@Arakade
Created March 5, 2020 11:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Arakade/d05575965a16bef96d67cf82335831e8 to your computer and use it in GitHub Desktop.
Save Arakade/d05575965a16bef96d67cf82335831e8 to your computer and use it in GitHub Desktop.
Custom JSON.Net Dictionary serialization for when value object contains the key the dictionary uses to map to it.
// See https://unreasonablygoodsoftware.wordpress.com/2020/03/05/json-net-custom-dictionary-serialization/
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Newtonsoft.Json;
using NUnit.Framework;
using UnityEngine.Scripting;
namespace packageNameRemoved {
public class CustomJsonSerializationTestSimple {
[JsonObject(MemberSerialization.OptIn)]
private class HasCustomDict {
[JsonProperty]
public Dictionary<string, ClassHasString> dict;
public HasCustomDict(IList<ClassHasString> list) {
dict = new Dictionary<string, ClassHasString>();
foreach (var classHasString in list) {
dict[classHasString.id] = classHasString;
}
}
[JsonConstructor]
public HasCustomDict(Dictionary<string, ClassHasString> dict) {
this.dict = dict;
}
public override string ToString() {
return $"{GetType().Name}({nameof(dict)}:[\n {(null != dict ? string.Join("\n ", dict) : "null")}])";
}
}
[JsonObject(MemberSerialization.OptIn)]
private class ClassHasString {
[JsonProperty]
public string id;
[JsonProperty]
public int num;
[JsonConstructor]
public ClassHasString(string id, int num) {
this.id = id;
this.num = num;
}
public override string ToString() {
return $"({nameof(id)}:{id}, {nameof(num)}:{num})";
}
}
[Test]
public void simpleSerializationWorks() {
var hasCustomDict = new HasCustomDict(new []{
new ClassHasString("one", 1),
new ClassHasString("two", 2),
new ClassHasString("three", 3),
});
var beforeStr = hasCustomDict.ToString();
log("before: {0}", beforeStr);
var json = JsonConvert.SerializeObject(hasCustomDict);
log("JSON: {0}", json);
var deserialized = JsonConvert.DeserializeObject<HasCustomDict>(json);
var afterStr = deserialized.ToString();
log("After: {0}", afterStr);
Assert.AreEqual(beforeStr, afterStr);
}
[StringFormatMethod("formatStr")]
private static void log([NotNull] string formatStr, params object[] args) {
Console.Out.WriteLine(formatStr, args);
}
}
public class CustomJsonSerializationWithListOfValues {
[JsonObject(MemberSerialization.OptIn), Preserve]
private class HasCustomDict {
[JsonProperty]
public Dictionary<string, ClassHasString> dict;
public HasCustomDict(IList<ClassHasString> list) {
dict = new Dictionary<string, ClassHasString>();
foreach (var classHasString in list) {
dict[classHasString.id] = classHasString;
}
}
[JsonConstructor]
public HasCustomDict(Dictionary<string, ClassHasString> dict) {
this.dict = dict;
}
public override string ToString() {
return $"{GetType().Name}({nameof(dict)}:[\n {(null != dict ? string.Join("\n ", dict) : "null")}])";
}
}
[JsonObject(MemberSerialization.OptIn), Preserve]
private class ClassHasString {
[JsonProperty]
public string id;
[JsonProperty]
public int num;
[JsonConstructor]
public ClassHasString(string id, int num) {
this.id = id;
this.num = num;
}
public override string ToString() {
return $"({nameof(id)}:{id}, {nameof(num)}:{num})";
}
}
private class CustomDictFormatterAsListOfValues<TKey, TValue> : JsonConverter {
public CustomDictFormatterAsListOfValues(Func<TValue, TKey> getKeyFromValue) {
this.getKeyFromValue = getKeyFromValue;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
log("writer:{0}, value:{1} (type:{2}), serializer:{3}\n", writer, value, value.GetType(), serializer);
writer.WriteStartArray();
foreach (var kvp in (IDictionary<TKey, TValue>) value) {
serializer.Serialize(writer, kvp.Value);
}
writer.WriteEndArray();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
log("reader:{0}, objectType:{1}, existingValue:{2}, serializer:{3}\n", reader, objectType, existingValue, serializer);
var list = serializer.Deserialize<IList<TValue>>(reader);
var dict = new Dictionary<TKey, TValue>();
foreach (var t in list) {
dict[getKeyFromValue(t)] = t;
}
return dict;
}
public override bool CanWrite => true;
public override bool CanRead => true;
public override bool CanConvert(Type objectType) {
var canConvert = typeof(IDictionary<TKey, TValue>).IsAssignableFrom(objectType);
log("convert? {0} = {1}{2}", objectType, canConvert, canConvert ? "\n" : "");
return canConvert;
}
private readonly Func<TValue, TKey> getKeyFromValue;
}
[Test]
public void customSerializationWorksWithListOfValues() {
var hasCustomDict = new HasCustomDict(new []{
new ClassHasString("one", 1),
new ClassHasString("two", 2),
new ClassHasString("three", 3),
});
var beforeStr = hasCustomDict.ToString();
log("before: {0}\n", beforeStr);
var customDictFormatterAsListOfValues = new CustomDictFormatterAsListOfValues<string, ClassHasString>(c => c.id);
var json = JsonConvert.SerializeObject(hasCustomDict, customDictFormatterAsListOfValues);
log("\nJSON: {0}\n", json);
var deserialized = JsonConvert.DeserializeObject<HasCustomDict>(json, customDictFormatterAsListOfValues);
var afterStr = deserialized.ToString();
log("\nAfter: {0}\n", afterStr);
Assert.AreEqual(beforeStr, afterStr);
}
[StringFormatMethod("formatStr")]
private static void log([NotNull] string formatStr, params object[] args) {
Console.Out.WriteLine(formatStr, args);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment