Having following classes:
public class Person
{
public int Age { get; set; }
}
using System.Collections;
using System.Text.Json.Serialization;
namespace SerializeCustomEnumerable;
public class Statistics<T> : IEnumerable<(int Year, T Value)> where T : new()
{
// Underlying structure where data are stored
protected Dictionary<int, T> Years { get; set; } = new Dictionary<int, T>();
public Statistics() { }
public Statistics(Dictionary<int, T> years)
{
Years = years ?? throw new ArgumentNullException(nameof(years));
}
public T this[int year]
{
get
{
return Years.TryGetValue(year, out var value) ? value : new T();
}
set
{
Years[year] = value;
}
}
public IEnumerator<(int Year, T Value)> GetEnumerator()
{
foreach (var element in Years)
{
yield return (element.Key, element.Value);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
I need somehow to be able to serialize/deserialize Statistics<T>
class using System.Text.Json
. This serialized object should look like serialized Dictionary in json.
Here is a sample code with comments of what I need
using System.Text.Json;
using SerializeCustomEnumerable;
JsonSerializerOptions jSerOptions = new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
IncludeFields = true
};
//populate our statistics object
var firstPerson = new Person() { Age = 5 };
var secondPerson = new Person() { Age = 10 };
var dict = new Dictionary<int, Person>()
{
[0] = firstPerson,
[1] = secondPerson
};
var simplestat = new Statistics<Person>(dict);
//this is what the json should look like
string expectedJson = JsonSerializer.Serialize(dict, jSerOptions);
//this is what I get
string serialized = JsonSerializer.Serialize(simplestat, jSerOptions);
//and also here deserialization fails... :(
var result = JsonSerializer.Deserialize<Statistics<Person>>(serialized, jSerOptions);
But there is a catch to it. I can’t use Converters
in JsonSerializerOptions
. (The reason is that this serialization is being done in different code base which is not managed by me. I know only that they call JsonSerializer.Serialize(dict, jSerOptions);
)
2
Answers
The easiest option would be to just implement
IDictionary<TKey, TValue>
:And direct all the interface members calls to
Years
. If you want you can makeIDictionary
interface implementation to be an explicit one.The second option would be to implement a custom converter.
It does seem like system.text.json will not serialize value tuples by default.
So you could try specify the serialization options to include fields:
Or you could use
Tuple<int , T >
instead of(int Year, T Value)
. Do note that the names in value tuples are compile time only. So in the json either option will result in"Item1": 1, "Item2": { "Age": 42 }
The second problem is that the library does not know how to create or initialize your custom
Statistics<T>
-class. So you probably need to deserialize to a collection of a known type:JsonSerializer.Deserialize<IEnumerable<(int Year, Person Value)>>(serialized, jSerOptions);
. Unless you write your own converter or find some other way to make the library aware of how to handle your custom collection.Note that it is generally a good idea to keep objects intended for serialization as simple as possible. It may also be a good idea to keep a separate set of types specifically for serialization, i.e. (DTOs). That way you have more flexibility to handle difficult to serialize data, or changes to the data structure. I would be somewhat concerned that your
Statistics<T>
does a bit much to be suitable for serialization.