Created
August 2, 2023 09:45
-
-
Save MrBogomips/a72c002291732101941ac52c58575288 to your computer and use it in GitHub Desktop.
.NET JsonConverterFactory for Dictionary<string, T>.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* JsonConverterFactory to serialize Dictionary with string as keys. | |
* This serializer will produce a json where the key becomes the property of the json item. | |
*/ | |
using System.Text.Json; | |
using System.Text.Json.Serialization; | |
namespace Bogoware.Common.JsonConverters; | |
/// <summary> | |
/// Json converter for <see cref="Dictionary{TKey,TValue}"/> where keys are strings and values are objects. | |
/// </summary> | |
public class DictionaryKeyValueJsonConverterFactory: JsonConverterFactory | |
{ | |
public override bool CanConvert(Type typeToConvert) | |
{ | |
if (!typeToConvert.IsGenericType) return false; | |
if (typeToConvert.GetGenericTypeDefinition() != typeof(Dictionary<,>)) return false; | |
return typeToConvert.GetGenericArguments()[0] == typeof(string); | |
} | |
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) | |
{ | |
var valueType = typeToConvert.GetGenericArguments()[1]; | |
var converterType = typeof(DictionaryKeyValueJsonConverter<>).MakeGenericType(valueType); | |
return (JsonConverter?)Activator.CreateInstance(converterType); | |
} | |
private class DictionaryKeyValueJsonConverter<TValue>: JsonConverter<Dictionary<string, TValue>> | |
{ | |
public override Dictionary<string, TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | |
{ | |
var dictionary = new Dictionary<string, TValue>(); | |
while (reader.Read()) | |
{ | |
if (reader.TokenType == JsonTokenType.EndObject) break; | |
if (reader.TokenType != JsonTokenType.PropertyName) throw new JsonException("Expected property name."); | |
var key = reader.GetString(); | |
reader.Read(); | |
var value = JsonSerializer.Deserialize<TValue>(ref reader, options); | |
dictionary.Add(key, value); | |
} | |
return dictionary; | |
} | |
public override void Write(Utf8JsonWriter writer, Dictionary<string, TValue> dictionary, JsonSerializerOptions options) | |
{ | |
writer.WriteStartObject(); | |
foreach (var (key, value) in dictionary) | |
{ | |
writer.WritePropertyName(key); | |
JsonSerializer.Serialize(writer, value, options); | |
} | |
writer.WriteEndObject(); | |
} | |
} | |
} | |
/************** Test Class base on XUnit/FluentAsserion **********************/ | |
using System.Text.Json; | |
using FluentAssertions; | |
using Bogoware.Common.JsonConverters; | |
using Xunit.Abstractions; | |
namespace Bogoware.Common.Test.Unit; | |
public class DictionaryKeyValueJsonConverterFactoryTests | |
{ | |
private readonly ITestOutputHelper _output; | |
private class Person | |
{ | |
public string Name { get; set; } = null!; | |
public int Age { get; set; } | |
public bool LikesChocolate { get; set; } | |
} | |
public DictionaryKeyValueJsonConverterFactoryTests(ITestOutputHelper output) | |
{ | |
_output = output; | |
} | |
[Fact] | |
public void Sut_CanSerialize_DictionaryOfStringString() | |
{ | |
// Arrange | |
var jsonOptions = new JsonSerializerOptions() | |
{ | |
Converters = { new DictionaryKeyValueJsonConverterFactory() } | |
}; | |
var dictionary = new Dictionary<string, string>(); | |
dictionary.Add("key1", "value1"); | |
dictionary.Add("key2", "value2"); | |
dictionary.Add("key3", "value3"); | |
// Act | |
var json = JsonSerializer.Serialize(dictionary, jsonOptions); | |
// Assert | |
_output.WriteLine(json); | |
json.Should().Be("{\"key1\":\"value1\",\"key2\":\"value2\",\"key3\":\"value3\"}"); | |
} | |
[Fact] | |
public void Sut_CanSerialize_DictionaryOfStringDecimal() | |
{ | |
// Arrange | |
var jsonOptions = new JsonSerializerOptions() | |
{ | |
Converters = { new DictionaryKeyValueJsonConverterFactory() } | |
}; | |
var dictionary = new Dictionary<string, decimal>(); | |
dictionary.Add("key1", 1m); | |
dictionary.Add("key2", 2m); | |
dictionary.Add("key3", 3m); | |
// Act | |
var json = JsonSerializer.Serialize(dictionary, jsonOptions); | |
// Assert | |
_output.WriteLine(json); | |
json.Should().Be("{\"key1\":1,\"key2\":2,\"key3\":3}"); | |
} | |
[Fact] | |
public void Sut_CanSerialize_DictionaryOfStringPerson() | |
{ | |
// Arrange | |
var jsonOptions = new JsonSerializerOptions() | |
{ | |
Converters = { new DictionaryKeyValueJsonConverterFactory() }, | |
}; | |
var dictionary = new Dictionary<string, Person>(); | |
dictionary.Add("John", new Person { Name = "John", Age = 42, LikesChocolate = true }); | |
dictionary.Add("Jane", new Person { Name = "Jane", Age = 39, LikesChocolate = false }); | |
dictionary.Add("Jack", new Person { Name = "Jack", Age = 12, LikesChocolate = true }); | |
// Act | |
var json = JsonSerializer.Serialize(dictionary, jsonOptions); | |
var prettyJson = JsonSerializer.Serialize(dictionary, new JsonSerializerOptions { WriteIndented = true }); | |
// Assert | |
_output.WriteLine(prettyJson); | |
json.Should().Be("{\"John\":{\"Name\":\"John\",\"Age\":42,\"LikesChocolate\":true},\"Jane\":{\"Name\":\"Jane\",\"Age\":39,\"LikesChocolate\":false},\"Jack\":{\"Name\":\"Jack\",\"Age\":12,\"LikesChocolate\":true}}"); | |
} | |
[Fact] | |
public void Sut_CanDeserialize_DictionaryOfStringString() | |
{ | |
// Arrange | |
var jsonOptions = new JsonSerializerOptions() | |
{ | |
Converters = { new DictionaryKeyValueJsonConverterFactory() } | |
}; | |
var json = "{\"key1\":\"value1\",\"key2\":\"value2\",\"key3\":\"value3\"}"; | |
// Act | |
var dictionary = JsonSerializer.Deserialize<Dictionary<string, string>>(json, jsonOptions); | |
// Assert | |
dictionary.Should().NotBeNull(); | |
dictionary!.Count.Should().Be(3); | |
dictionary["key1"].Should().Be("value1"); | |
dictionary["key2"].Should().Be("value2"); | |
dictionary["key3"].Should().Be("value3"); | |
} | |
[Fact] | |
public void Sut_CanDeserialize_DictionaryOfStringDecimal() | |
{ | |
// Arrange | |
var jsonOptions = new JsonSerializerOptions() | |
{ | |
Converters = { new DictionaryKeyValueJsonConverterFactory() } | |
}; | |
var json = "{\"key1\":1,\"key2\":2,\"key3\":3}"; | |
// Act | |
var dictionary = JsonSerializer.Deserialize<Dictionary<string, decimal>>(json, jsonOptions); | |
// Assert | |
dictionary.Should().NotBeNull(); | |
dictionary!.Count.Should().Be(3); | |
dictionary["key1"].Should().Be(1m); | |
dictionary["key2"].Should().Be(2m); | |
dictionary["key3"].Should().Be(3m); | |
} | |
[Fact] | |
public void Sut_CanDeserialize_DictionaryOfStringPerson() | |
{ | |
// Arrange | |
var jsonOptions = new JsonSerializerOptions() | |
{ | |
Converters = { new DictionaryKeyValueJsonConverterFactory() }, | |
}; | |
var json = "{\"John\":{\"Name\":\"John\",\"Age\":42,\"LikesChocolate\":true},\"Jane\":{\"Name\":\"Jane\",\"Age\":39,\"LikesChocolate\":false},\"Jack\":{\"Name\":\"Jack\",\"Age\":12,\"LikesChocolate\":true}}"; | |
// Act | |
var dictionary = JsonSerializer.Deserialize<Dictionary<string, Person>>(json, jsonOptions); | |
// Assert | |
dictionary.Should().NotBeNull(); | |
dictionary!.Count.Should().Be(3); | |
dictionary["John"].Name.Should().Be("John"); | |
dictionary["John"].Age.Should().Be(42); | |
dictionary["John"].LikesChocolate.Should().BeTrue(); | |
dictionary["Jane"].Name.Should().Be("Jane"); | |
dictionary["Jane"].Age.Should().Be(39); | |
dictionary["Jane"].LikesChocolate.Should().BeFalse(); | |
dictionary["Jack"].Name.Should().Be("Jack"); | |
dictionary["Jack"].Age.Should().Be(12); | |
dictionary["Jack"].LikesChocolate.Should().BeTrue(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment