Дерериализация json с неизвестными полями
У меня есть json файлы которые нужно дерериализировать. Проблема в том что в них есть постоянные поля( есть во всех) и есть поля которые меняются(могут быть а могут и не быть). Вот мой код:
Log MyLog = new Log();
public void json (string file)
{
if (file != null)
{
MyLog = JsonSerializer.Deserialize<Log>(File.ReadAllText(file));
}
}
public class Log
{
public DateTime Timestamp { get; set; }
public string Level { get; set; }
public string MessageTemplate { get; set; }
public string Exception { get; set; }
}
И как потом мне в ДатаГрид WPF вывести все поля в таблице? Заранее спасибо
Ответы (2 шт):
К примеру, у вас есть Json
{
"field1": "111",
"field2": 123
}
и такой
{
"field1": "111",
"field3": 555,
"field4": true
}
Но на момент чтения данных, вы не знаете, что там внутри.
Это всего-лишь пример, таких полей может быть полсотни, и это объект с полями, а не массив.
Вариант 1. Обобщенный класс
public class MyJsonData
{
[JsonPropertyName("field1")]
public string Field1 { get; set; }
[JsonPropertyName("field2")]
public int Field2 { get; set; }
[JsonPropertyName("field3")]
public int Field3 { get; set; }
[JsonPropertyName("field4")]
public bool Field4 { get; set; }
}
Десереализация здесь простая
MyJsonData data = JsonSerializer.Deserialize<MyJsonData>(File.ReadAllText(file));
Debug.WriteLine(data.Field2);
Вариант 2. Словарь объектов
А есть случаи, когда в этот json могут добавляться незнакомые поля, которые не хочется хардкодить, и тип данных в будущих полях неизвестен. На помощь приходят словарть и конвертер.
При этом, с использованием словаря создание класса для этой части json'a не требуется, но потребуется конвертер, потому что для обобщения типов значений для каждого поля я буду использовать тип object, то есть словами дилетанта (то есть меня), способ обобщения значимых типов под названием boxing.
Конвертер у меня есть вот такой. Он поддерживает все основные типы чисел, bool и строки. Из коробки JsonSerializer такие преобразования делать не умеет, поэтому приходится выкручиваться.
Конвертер
public class TokenToObjectConverter : JsonConverter<object>
{
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
string stringValue = reader.GetString();
if (int.TryParse(stringValue, out int intVal)) return intVal;
if (long.TryParse(stringValue, out long longVal)) return longVal;
if (double.TryParse(stringValue, out double doubleVal)) return doubleVal;
if (decimal.TryParse(stringValue, out decimal decimalVal)) return decimalVal;
return stringValue;
}
else
if (reader.TokenType == JsonTokenType.Number)
{
if (reader.TryGetInt32(out int intVal)) return intVal;
if (reader.TryGetInt64(out long longVal)) return longVal;
if (reader.TryGetDouble(out double doubleVal)) return doubleVal;
if (reader.TryGetDecimal(out decimal decimalVal)) return decimalVal;
}
else
if (reader.TokenType == JsonTokenType.False || reader.TokenType == JsonTokenType.True) return reader.GetBoolean();
return null;
}
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) => throw new NotImplementedException();
}
Здесь я сообщаю сериализатору, что если принимающий тип object, то json токен надо прогнать через этот конвертер.
На самом деле, я сам написал этот конвертер под свои нужды, быстро, и "на коленке". Вы можете его дописать, чтобы он поддерживал даты, или еще что-нибудь нужное.
Процесс десериализации будет немного сложнее.
JsonSerializerOptions options = new JsonSerializerOptions { ReadCommentHandling = JsonCommentHandling.Skip };
options.Converters.Add(new TokenToObjectConverter());
Dictionary<string, object> data = JsonSerializer.Deserialize<Dictionary<string, object>>(File.ReadAllText(file), options);
// определять тип значения, который вернул конвертер, можно с помощью паттерн-матчинга с присваиванием
if (data.TryGetValue("field2", out object val) && val is int number)
Debug.WriteLine(number);
Кстати, если не подключать конвертер, а просто оставить все как есть, все равно можно будет получить эти данные в виде строк, вызывая object.ToString(), но внутри object при этом будет скрываться JsonToken, и его нельзя будет сматчить как string или int напрямую, а только с помощью методов класса JsonToken.
Но я в итоге советую там где возможно придерживаться первого варианта, а второй вариант оставить для сложных случаев, где есть линейная куча разнотипных значимых данных, и нет вложенных объектов.
А еще если вы просто хотите засунуть эти данные в интерфейс, и не особо разбираться, что именно за тип данных приехал из json'а, то второй способ вам тоже подойдет, потому что в интерфейсе везде, где можно - object.
Извините, но это звучит как ересь. "постоянные поля( есть во всех) и есть поля которые меняются".
На самом деле у вас всегда один класс Log с разными версиями. И если вы придерживаетесь принципов SOLID то у вас не должно возникнуть проблем с десериализацией. Если же нет то советую переделать так что ваш последний Log отвечал этим принципам, а точнее в данном случае O - open closed. Ну и как частный случай это Вариант 1. Обобщенный класс предыдушего ответа