Json.Сетевая десериализация и сериализация свойств с изменяющимися именами

#c# #json #serialization #json.net

#c# #json #сериализация #json.net

Вопрос:

Я интегрируюсь с третьей стороной, и JSON, который они возвращают, запускает меня в цикл. Я хочу десериализовать его в класс. но большинство имен свойств могут меняться.

 {
  "transaction_id":1,
  "status":"Reviewing",
  "changelog":{
    "2016-Mar-15 10:28 AM":{
      "status":{
        "from":"Approved",
        "to":"Reviewing"
      },
      "total":{
        "from":123.45,
        "to":246.90
      },
      "shipping_address_1":{
        "from":"321 S Main St.",
        "to":"8355 NW 74th St"
      },
      "shipping_city":{
        "from":"New York",
        "to":"Medley"
      },
      "shipping_state":{
        "from":"NY",
        "to":"FL"
      },
      "shipping_postal":{
        "from":"10002",
        "to":"33166"
      }
    }
  }
}
  

Я хотел бы иметь класс, похожий на этот.

 public class TransactionChangeLog
{
    [JsonProperty(PropertyName = "transaction_id")]
    public int TransactionId { get; set; }

    [JsonProperty(PropertyName = "status")]
    public TransactionStatus Status { get; set; }

    [JsonProperty(PropertyName = "changelog")]
    public ICollection<TransactionChange> Changelog { get; set; }
}

public class TransactionChange
{
    // ?? What to do with this.
    public DateTime ChangeDate { get; set; }

    // ?? What to do with this.
    public string Field { get; set; }

    public string From { get; set; }

    public string To { get; set; }
}
  

Ответ №1:

Есть несколько подходов, которые вы могли бы предпринять, чтобы справиться с этим. Первый (и самый простой) подход заключается в использовании вложенных словарей в вашем TransactionChangeLog классе:

 public class TransactionChangeLog
{
    [JsonProperty(PropertyName = "transaction_id")]
    public int TransactionId { get; set; }

    [JsonProperty(PropertyName = "status")]
    public TransactionStatus Status { get; set; }

    [JsonProperty(PropertyName = "changelog")]
    public Dictionary<DateTime, Dictionary<string, TransactionChange>> Changelog { get; set; }
}

public class TransactionChange
{
    public string From { get; set; }
    public string To { get; set; }
}
  

Затем вы можете десериализовать и выгрузить данные следующим образом:

 TransactionChangeLog changeLog = JsonConvert.DeserializeObject<TransactionChangeLog>(json);

Console.WriteLine("TransactionId: "   changeLog.TransactionId);
Console.WriteLine("TransactionStatus: "   changeLog.Status);
foreach (var dateKvp in changeLog.Changelog)
{
    Console.WriteLine(dateKvp.Key);  // change date
    foreach (var fieldKvp in dateKvp.Value)
    {
        Console.WriteLine("   changed "   fieldKvp.Key   " from '"   fieldKvp.Value.From   "' to '"   fieldKvp.Value.To   "'");
    }
}
  

Скрипка:https://dotnetfiddle.net/vXNcKi


Хотя вышеуказанное будет работать, немного неудобно работать с вложенными словарями. Другой подход заключается в использовании JsonConverter для обработки десериализации изменяющегося JSON. Это позволит вам использовать классы так, как вы определили их в своем вопросе. Вот как вы могли бы написать конвертер:

 public class ChangeLogConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(ICollection<TransactionChange>);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        List<TransactionChange> changes = new List<TransactionChange>();
        JObject changelog = JObject.Load(reader);
        foreach (JProperty dateProp in changelog.Children<JProperty>())
        {
            DateTime changeDate = DateTime.ParseExact(dateProp.Name, "yyyy-MMM-dd hh:mm tt", CultureInfo.InvariantCulture);
            foreach (JProperty fieldProp in dateProp.Value.Children<JProperty>())
            {
                TransactionChange change = new TransactionChange();
                change.ChangeDate = changeDate;
                change.Field = fieldProp.Name;
                change.From = (string)fieldProp.Value["from"];
                change.To = (string)fieldProp.Value["to"];
                changes.Add(change);
            }
        }
        return changes;
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
  

Чтобы использовать конвертер, добавьте [JsonConverter] атрибут к Changelog свойству в вашем TransactionChangeLog классе:

 public class TransactionChangeLog
{
    [JsonProperty(PropertyName = "transaction_id")]
    public int TransactionId { get; set; }

    [JsonProperty(PropertyName = "status")]
    public TransactionStatus Status { get; set; }

    [JsonProperty(PropertyName = "changelog")]
    [JsonConverter(typeof(ChangeLogConverter))]
    public ICollection<TransactionChange> Changelog { get; set; }
}
  

Затем вы можете десериализовать и выгрузить данные, как обычно:

 TransactionChangeLog changeLog = JsonConvert.DeserializeObject<TransactionChangeLog>(json);
Console.WriteLine("TransactionId: "   changeLog.TransactionId);
Console.WriteLine("TransactionStatus: "   changeLog.Status);
foreach (TransactionChange change in changeLog.Changelog)
{
    Console.WriteLine(change.ChangeDate   " - changed "   change.Field   " from '"   change.From   "' to '"   change.To   "'");
}
  

Скрипка:https://dotnetfiddle.net/1d3pUa

Комментарии:

1. Брайан. Спасибо за подробный пример. это было то, что я искал.

2. Нет проблем; рад, что смог помочь.

Ответ №2:

Если я вас правильно понимаю, содержимое changelog может меняться, и вы не знаете возможных имен свойств во время компиляции.

Если это так, вам нужно десериализовать что-то, что не является статически типизированным, например JObject , которое может быть использовано для последующего запроса данных в стиле LINQ.