Десериализация JSON, вложенные свойства объекта должны быть в родительском объекте. C#

У меня есть следующий JSON, который я пишу объектную модель для десериализации в:

{
  "company_webhooks": [
    {
      "company_webhook": {
        "id": 42,
        "url": "https://keeptruckin.com/callbacktest/842b02",
        "secret": "fe8b75de0a4e5898f0011faeb8c93654",
        "format": "json",
        "actions": [
          "vehicle_location_received",
          "vehicle_location_updated"
        ],
        "enabled": false
      }
    },
    {
      "company_webhook": {
        "id": 43,
        "url": "https://keeptruckin.com/callbacktest/a6a783",
        "secret": "66a7368063cb21887f546c7af91be59c",
        "format": "json",
        "actions": [
          "vehicle_location_received",
          "vehicle_location_updated"
        ],
        "enabled": false
      }
    },
    {
      "company_webhook": {
        "id": 44,
        "url": "https://keeptruckin.com/callbacktest/53a52c",
        "secret": "4451dc96513b3a67107466dd2c4d9589",
        "format": "json",
        "actions": [
          "vehicle_location_received",
          "vehicle_location_updated"
        ],
        "enabled": false
      }
    },
    {
      "company_webhook": {
        "id": 45,
        "url": "https://keeptruckin.com/callbacktest/6fb337",
        "secret": "4177fbd88c30faaee03a4362648bd663",
        "format": "json",
        "actions": [
          "vehicle_location_received",
          "vehicle_location_updated"
        ],
        "enabled": false
      }
    },
    {
      "company_webhook": {
        "id": 46,
        "url": "https://keeptruckin.com/callbacktest/8cd6da",
        "secret": "6e41817a048b009435e5102fca17db55",
        "format": "json",
        "actions": [
          "vehicle_location_received",
          "vehicle_location_updated"
        ],
        "enabled": false
      }
    }
  ],
  "pagination": {
    "per_page": 25,
    "page_no": 1,
    "total": 5
  }
}

Вот что у меня есть:

[DataContract]
public class KeepTruckinResponse
{
    [DataMember(Name = "company_webhooks", EmitDefaultValue = false)]
    public KeepTruckinCompanyWebHook[] WebHooks { get; set; }

    [DataMember(Name = "pagination", EmitDefaultValue = false)]
    public KeepTruckinPagination Pagination { get; set; }

    public string RawJSON { get; set; }
}

[DataContract]
public class KeepTruckinPagination
{
    [DataMember(Name = "per_page", EmitDefaultValue = false)]
    public int PerPage { get; set; }

    [DataMember(Name = "page_no", EmitDefaultValue = false)]
    public int PageNumber { get; set; }

    [DataMember(Name = "total", EmitDefaultValue = false)]
    public int Total { get; set; }
}

[DataContract(Name = "company_webhook")]
public class KeepTruckinCompanyWebHook
{
    [DataMember(Name = "id", EmitDefaultValue = false)]
    public int Id { get; set; }

    [DataMember(Name = "url", EmitDefaultValue = false)]
    public string Url { get; set; }
}

Очевидно, что при десериализации JSON я не получаю KeepTruckinCompanyWebHook свойства, потому что способ отправки коллекции "вложенный". Мне почти нужно создать еще один объект внутри KeepTruckinCompanyWebHook со свойствами. Но я бы хотел сохранить мою объектную модель такой, какая она есть. Возможно ли это с.NET сериализатором?

Мы используем DataContractJsonSerializer вот так:

var ser = new DataContractJsonSerializer(typeof(KeepTruckinResponse));
response = ser.ReadObject(ms) as KeepTruckinResponse;

На данный момент мы не хотим использовать NewtonSoft.Json

1 ответ

Да, это возможно, но для этого вам понадобится специальный код.

Это немного уродливо, но вы можете создать кастом IDataContractSurrogate класс для десериализации каждого объекта JSON внутри company_webhooks массив в Dictionary<string, Dictionary<string, object>> и затем скопируйте значения из вложенной словарной структуры в экземпляр вашего KeepTruckinCompanyWebHook учебный класс. Вот код, который вам понадобится для суррогата:

class MyDataContractSurrogate : IDataContractSurrogate
{
    public Type GetDataContractType(Type type)
    {
        if (type == typeof(KeepTruckinCompanyWebHook))
        {
            return typeof(Dictionary<string, Dictionary<string, object>>);
        }
        return type;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        if (obj.GetType() == typeof(Dictionary<string, Dictionary<string, object>>) &&
            targetType == typeof(KeepTruckinCompanyWebHook))
        {
            var webHook = new KeepTruckinCompanyWebHook();
            var outerDict = (Dictionary<string, Dictionary<string, object>>)obj;
            var innerDict = outerDict["company_webhook"];

            foreach (PropertyInfo prop in GetDataMemberProperties(typeof(KeepTruckinCompanyWebHook)))
            {
                DataMemberAttribute att = prop.GetCustomAttribute<DataMemberAttribute>();

                object value;
                if (innerDict.TryGetValue(att.Name, out value))
                {
                    prop.SetValue(webHook, value);
                }
            }

            return webHook;
        }
        return obj;
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        if (obj.GetType() == typeof(KeepTruckinCompanyWebHook) &&
            targetType == typeof(Dictionary<string, Dictionary<string, object>>))
        {
            var webHook = (KeepTruckinCompanyWebHook)obj;
            var outerDict = new Dictionary<string, Dictionary<string, object>>();
            var innerDict = new Dictionary<string, object>();
            outerDict.Add("company_webhook", innerDict);

            foreach (PropertyInfo prop in GetDataMemberProperties(typeof(KeepTruckinCompanyWebHook)))
            {
                DataMemberAttribute att = prop.GetCustomAttribute<DataMemberAttribute>();
                innerDict.Add(att.Name, prop.GetValue(webHook));
            }

            return outerDict;
        }
        return obj;
    }

    private IEnumerable<PropertyInfo> GetDataMemberProperties(Type type)
    {
        return type.GetProperties().Where(p => p.CanRead && p.CanWrite && p.GetCustomAttribute<DataMemberAttribute>() != null);
    }

    // ------- The rest of these methods do not need to be implemented -------
    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        throw new NotImplementedException();
    }

    public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
    {
        throw new NotImplementedException();
    }

    public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
    {
        throw new NotImplementedException();
    }

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        throw new NotImplementedException();
    }

    public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
    {
        throw new NotImplementedException();
    }
}

Чтобы использовать суррогат, вам нужно создать экземпляр DataContractJsonSerializerSettings и передать его DataContractJsonSerializer со следующими установленными свойствами. Обратите внимание, что так как мы требуем UseSimpleDictionaryFormat настройка, это решение будет работать только с. Net 4.5 или более поздней.

var settings = new DataContractJsonSerializerSettings();
settings.DataContractSurrogate = new MyDataContractSurrogate();
settings.KnownTypes = new List<Type> { typeof(Dictionary<string, Dictionary<string, object>>) };
settings.UseSimpleDictionaryFormat = true;

Вот демо:

public class Program
{
    public static void Main(string[] args)
    {
        string json = @"
        {
          ""company_webhooks"": [
            {
              ""company_webhook"": {
                ""id"": 42,
                ""url"": ""https://keeptruckin.com/callbacktest/842b02"",
                ""secret"": ""fe8b75de0a4e5898f0011faeb8c93654"",
                ""format"": ""json"",
                ""actions"": [
                  ""vehicle_location_received"",
                  ""vehicle_location_updated""
                ],
                ""enabled"": false
              }
            },
            {
              ""company_webhook"": {
                ""id"": 43,
                ""url"": ""https://keeptruckin.com/callbacktest/a6a783"",
                ""secret"": ""66a7368063cb21887f546c7af91be59c"",
                ""format"": ""json"",
                ""actions"": [
                  ""vehicle_location_received"",
                  ""vehicle_location_updated""
                ],
                ""enabled"": false
              }
            },
            {
              ""company_webhook"": {
                ""id"": 44,
                ""url"": ""https://keeptruckin.com/callbacktest/53a52c"",
                ""secret"": ""4451dc96513b3a67107466dd2c4d9589"",
                ""format"": ""json"",
                ""actions"": [
                  ""vehicle_location_received"",
                  ""vehicle_location_updated""
                ],
                ""enabled"": false
              }
            },
            {
              ""company_webhook"": {
                ""id"": 45,
                ""url"": ""https://keeptruckin.com/callbacktest/6fb337"",
                ""secret"": ""4177fbd88c30faaee03a4362648bd663"",
                ""format"": ""json"",
                ""actions"": [
                  ""vehicle_location_received"",
                  ""vehicle_location_updated""
                ],
                ""enabled"": false
              }
            },
            {
              ""company_webhook"": {
                ""id"": 46,
                ""url"": ""https://keeptruckin.com/callbacktest/8cd6da"",
                ""secret"": ""6e41817a048b009435e5102fca17db55"",
                ""format"": ""json"",
                ""actions"": [
                  ""vehicle_location_received"",
                  ""vehicle_location_updated""
                ],
                ""enabled"": false
              }
            }
          ],
          ""pagination"": {
            ""per_page"": 25,
            ""page_no"": 1,
            ""total"": 5
          }
        }";

        var settings = new DataContractJsonSerializerSettings();
        settings.DataContractSurrogate = new MyDataContractSurrogate();
        settings.KnownTypes = new List<Type> { typeof(Dictionary<string, Dictionary<string, object>>) };
        settings.UseSimpleDictionaryFormat = true;

        KeepTruckinResponse response = Deserialize<KeepTruckinResponse>(json, settings);

        foreach (KeepTruckinCompanyWebHook wh in response.WebHooks)
        {
            Console.WriteLine("Id: " + wh.Id + ", Url: " + wh.Url);
        }
    }

    public static T Deserialize<T>(string json, DataContractJsonSerializerSettings settings)
    {
        using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
        {
            var ser = new DataContractJsonSerializer(typeof(T), settings);
            return (T)ser.ReadObject(ms);
        }
    }

    public static string Serialize(object obj, DataContractJsonSerializerSettings settings)
    {
        using (MemoryStream ms = new MemoryStream())
        {
            var ser = new DataContractJsonSerializer(obj.GetType(), settings);
            ser.WriteObject(ms, obj);
            return Encoding.UTF8.GetString(ms.ToArray());
        }
    }
}

[DataContract]
public class KeepTruckinResponse
{
    [DataMember(Name = "company_webhooks", EmitDefaultValue = false)]
    public KeepTruckinCompanyWebHook[] WebHooks { get; set; }

    [DataMember(Name = "pagination", EmitDefaultValue = false)]
    public KeepTruckinPagination Pagination { get; set; }

    public string RawJSON { get; set; }
}

[DataContract]
public class KeepTruckinPagination
{
    [DataMember(Name = "per_page", EmitDefaultValue = false)]
    public int PerPage { get; set; }

    [DataMember(Name = "page_no", EmitDefaultValue = false)]
    public int PageNumber { get; set; }

    [DataMember(Name = "total", EmitDefaultValue = false)]
    public int Total { get; set; }
}

[DataContract(Name = "company_webhook")]
public class KeepTruckinCompanyWebHook
{
    [DataMember(Name = "id", EmitDefaultValue = false)]
    public int Id { get; set; }

    [DataMember(Name = "url", EmitDefaultValue = false)]
    public string Url { get; set; }
}

Выход:

Id: 42, Url: https://keeptruckin.com/callbacktest/842b02
Id: 43, Url: https://keeptruckin.com/callbacktest/a6a783
Id: 44, Url: https://keeptruckin.com/callbacktest/53a52c
Id: 45, Url: https://keeptruckin.com/callbacktest/6fb337
Id: 46, Url: https://keeptruckin.com/callbacktest/8cd6da
Другие вопросы по тегам