简体   繁体   中英

Deserializing JSON, nested object properties to be in parent object. C#

I have following JSON that I'm writing object model to deserialize into:

{
  "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
  }
}

Here is what I have:

[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; }
}

Obviously, when I deserialize JSON I don't get KeepTruckinCompanyWebHook properties because the way they send collection is "nested". I almost have to create another object inside KeepTruckinCompanyWebHook with properties. But I'd like to keep my object model as it is. Is it possible with .NET serializer?

We use DataContractJsonSerializer like so:

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

At this point we don't want to use NewtonSoft.Json

Yes, this is possible, but you will need some custom code to do it.

It's a little ugly, but you can create a custom IDataContractSurrogate class to deserialize each JSON object inside the company_webhooks array into a Dictionary<string, Dictionary<string, object>> , and then copy the values from the nested dictionary structure into an instance of your KeepTruckinCompanyWebHook class. Here's the code you would need for the surrogate:

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();
    }
}

To use the surrogate, you'll need to create an instance of DataContractJsonSerializerSettings and pass it to the DataContractJsonSerializer with the following properties set. Note that since we require the UseSimpleDictionaryFormat setting, this solution will only work with .Net 4.5 or later.

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

Here is a demo:

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; }
}

Output:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM