Некорректное поведение при сериализации/дисериализации Dictionary в JSON
В классе Eating есть словарь Dictionary<Food,double>, под этот словарь я написал JsonConverter(ориентировался на всякие статьи и документацию), так как Json отказывался сериализовать объект класса в качестве ключа.
[DataContract]
public class Eating
{
[DataMember]
public DateTime Moment { get; }
[JsonConverter(typeof(DictionaryFoodDoubleJsonConverter))]
public Dictionary<Food, double> Foods { get; }
[DataMember]
public User User { get; }
[JsonConstructor]
public Eating(User user)
{
User = user ?? throw new ArgumentNullException("Пользователь не может быть пустым.",nameof(user));
Moment = DateTime.UtcNow;
Foods = new Dictionary<Food, double>();
}
public void Add(Food food, double weight)
{
var product = Foods.Keys.FirstOrDefault(x => x.Name.Equals(food.Name));
if (product == null)
{
Foods.Add(food, weight);
}
else
{
Foods[product] += weight;
}
}
}
public class DictionaryFoodDoubleJsonConverter: JsonConverter<Dictionary<Food, double>>
{
public override Dictionary<Food, double>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if(reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException($"JsonTokenType was of type {reader.TokenType}, only objects are supported");
}
var dictionary = new Dictionary<Food,double>();
while(reader.Read())
{
if(reader.TokenType == JsonTokenType.EndObject)
{
return dictionary;
}
if(reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException("JsonTokenType was not PropertyName");
}
var propertyName = reader.GetString();
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new JsonException("Failed to get property name");
}
reader.Read();
var food = JsonSerializer.Deserialize<Food>(reader.GetString(), options);
var value = reader.GetDouble();
dictionary.Add(food, value);
}
return dictionary;
}
public override void Write(Utf8JsonWriter writer, Dictionary<Food, double> value, JsonSerializerOptions options)
{
if(value is null)
{
writer.WriteNullValue();
return;
}
writer.WriteStartObject();
foreach(var pair in value)
{
writer.WritePropertyName(pair.Key.Name);
writer.WriteNumberValue(pair.Value);
}
writer.WriteEndObject();
}
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert == typeof(Dictionary<Food, double>);
}
}
}
Есть две проблемы:
- Когда я создаю Приём пищи через EatingController, то данные десериализуются частично, объекта класса получает все поля, кроме Foods, он всегда пустой.
- При попытке добавить новую новый Food,weight в Eating.Foods, json файл, где я храню это, перезаписывается.
Перепробовал варианты, пытался найти причину некорректного поведения, но ничего не получилось. Может некорректно реализовал JsonConverter, не знаю. Остался вариант попробовать поменять словарь на список классов, где буду хранить Еду и Вес, но надеюсь, что можно оставить так и решить вопрос с сериализацией словаря.
Прикрепляю класс EatingController и ControllerBase(где, собственно, хранятся методы серилазицаии/десериализации)
public abstract class ControllerBase
{
protected void Save(string fileName,object item)
{
using (var fs = new FileStream(fileName, FileMode.OpenOrCreate))
{
JsonSerializer.Serialize(fs, item);
}
}
protected void Save(string fileName, object item, JsonSerializerOptions options)
{
using (var fs = new FileStream(fileName, FileMode.OpenOrCreate))
{
JsonSerializer.Serialize(fs, item, options);
}
}
protected T Load<T>(string fileName)
{
using (var fs = new FileStream(fileName, FileMode.OpenOrCreate))
{
if (fs.Length > 0 && JsonSerializer.Deserialize<T>(fs) is T items)
{
return items;
}
else
{
return default(T);
}
}
}
protected T Load<T>(string fileName, JsonSerializerOptions options)
{
using (var fs = new FileStream(fileName, FileMode.OpenOrCreate))
{
if (fs.Length > 0 && JsonSerializer.Deserialize<T>(fs,options) is T items)
{
return items;
}
else
{
return default(T);
}
}
}
public class EatingController:ControllerBase
{
private const string FOODS_FILE_NAME = "foods.json";
private const string EATINGS_FILE_NAME = "eatings.json";
private JsonSerializerOptions options = new JsonSerializerOptions
{
Converters = { new DictionaryFoodDoubleJsonConverter() }
};
private readonly User user;
public List<Food> Foods { get; }
public Eating Eating { get; }
public EatingController(User user)
{
this.user = user ?? throw new ArgumentNullException("Пользователь не может быть пустым!",nameof(user));
Foods = GetAllFoods();
Eating = GetEatings();
}
public void Add(Food food, double weight)
{
var product = Foods.SingleOrDefault(x => x.Name == food.Name);
if(product == null)
{
Foods.Add(food);
Eating.Add(food, weight);
Save();
}
else
{
Eating.Add(product,weight);
Save();
}
}
//private Eating GetEatings()
//{
// return Load<Eating> (EATINGS_FILE_NAME) ?? new Eating(user);
//}
private Eating GetEatings()
{
var options = new JsonSerializerOptions
{
Converters = { new DictionaryFoodDoubleJsonConverter() }
};
return Load<Eating>(EATINGS_FILE_NAME, options) ?? new Eating(user);
}
private List<Food> GetAllFoods()
{
return Load<List<Food>>(FOODS_FILE_NAME) ?? new List<Food>();
}
public void Save()
{
Save(FOODS_FILE_NAME, Foods);
Save(EATINGS_FILE_NAME, Eating, options);
}
}