У меня есть сторонний поставщик данных с такой вложенной структурой данных
{
"response": {
"...": "...",
"transactions": [
{
"well-known-field": 123,
"new-unknown-field": 456,
}
]
}
}
Итак, у меня есть соответствующая структура классов для десериализации. На уровне транзакции у класса есть только хорошо известные поля. Но для каждой транзакции есть несколько динамических полей или просто новые поля, которые я предпочитаю иметь хотя бы в журналах или даже в документе mongodb. Я знал, что могу поставить JRaw для любого из этих полей, но я не знаю имен будущих полей и просто предпочитаю иметь полную необработанную транзакцию. Можно ли иметь дополнительный JObject самой транзакции внутри класса Transaction?
Полагаю, я могу сделать это путем десериализации через вторую структуру данных, где список транзакций - это просто List<JObject>, а затем просто перейти к каждой транзакции в первом результате и установить поле JObject Raw из второго результата, но это выглядит глупо. Может быть, есть какое-то расширение или встроенная возможность добиться этого в Newtonsoft JSON.Net за один проход?
public class Transaction
{
public decimal WellKnownField { get; set; }
public JObject RawTransaction { get; set; }
}
UPD: Думаю, лучше лучше string RawTransaction. Это было бы идеально для диагностики сериализации, для регистрации исходного необработанного json и для попыток повторно применить саму сериализацию в случае, если мы изменим некоторые атрибуты или настройки или значения по умолчанию.





Я бы порекомендовал вам сделать так, чтобы ваш класс Transaction выглядел так:
public class Transaction {
[JsonProperty("id")]
public String Id {get; set;}
[JsonProperty("amount")]
public decimal Amount {get; set;}
...
[JsonExtensionData]
public Dictionary<String,Object> AdditionalData {get; set;}
}
Таким образом, вы можете извлечь выгоду как из информации Strongly Typed, которая известна во время компиляции, так и из той, которая является динамической.
Я думаю, вы забыли атрибут [JsonExtensionData]. Да, это действительно очень близко ... За исключением того, что мне лучше иметь полный объект. Это связано с эволюцией класса транзакции. Прежде чем я добавлю новое поле в класс, у меня будет «новое-неизвестное-поле» в дополнительных данных, но после того, как я добавлю его, оно не будет отображаться в новых входящих транзакциях. И это усложняет логику обновления. Также - я понял, что могу поместить этот атрибут в поле JObject (потому что это IDictionary <string, JToken>)
@DmitryGusarov: 'Я думаю, ты забыл атрибут [JsonExtensionData].': Да, верно, я забыл об этом: P!
В конце концов я вернулся к [JsonExtensionData]. Я сделал это, когда понял, что могу сделать то же самое при вставке в mongodb с помощью [BsonExtraElements]. Итак, все мои необъявленные столбцы попадают в поле [JsonExtensionData] [BsonIgnore], а затем конвертируются в получатель поля [BsonExtraElements] [JsonIgnore]. Это полностью решает мою первоначальную задачу - у меня есть весь динамический объект в базе данных, и я могу постепенно объявлять известные поля в C# без обновлений базы данных.
@DmitryGusarov: Mongo - отличный выбор, вы также научили меня кое-чему, так как я не знал о [BsonExtraElements]. Спасибо тебе за это :)
Вот что я в итоге придумал:
public class TransactionResponse
{
[JsonExtensionData]
[JsonIgnore]
public JObject AdditionalData { get; set; }
[JsonIgnore]
public JValue RawTransaction { get; set; }
[OnDeserialized]
internal void OnDeserialized(StreamingContext ctx)
{
var ser = (JValue)JsonConvert.SerializeObject(this);
AdditionalData.Merge(ser);
RawTransaction = ser;
}
public decimal Amount { get; set; }
Это дает полное представление об исходном объекте, и я даже могу поместить его в mongo в виде строки, используя RawTransaction.ToString() для любого будущего анализа. Не идеально, потому что он токенизирован, и я не вижу исходных пробелов и cr lf в json, но это, вероятно, слишком ...
Также мне не нравится, что я получаю целое число 0 и логическое значение false даже для тех полей, которые никогда не были в исходном json ... :(
Вы можете создавать типы динамически в C# (через класс TypeBuilder), но это не рекомендуется, поэтому в этом случае вам лучше придерживаться пары ключ-значение. Это было бы более производительно и менее сложно.