将JSON对象反序列化为嵌套的C#对象

5

编辑:我想我应该提一下,我无法控制JSON,并且我知道通常我的C#对象应该与JSON匹配。我的问题不是“为什么它不能反序列化?”。我知道为什么会出现这种情况。我想问的是,是否有一种方法可以按照我所描述的方式反序列化JSON。

我正在使用Newtonsoft.Json。

我有一个包含一个对象的JSON字符串。我需要将该对象反序列化为具有嵌套对象的C#对象。

所以假设我的JSON如下所示:

{
    "id": 123,
    "userName": "fflintstone",
    "address": "345 Cave Stone Road",
    "address2": "",
    "city": "Bedrock",
    "state": "AZ",
    "zip": "",   
}

这是我的C#对象

public class Customer
{
    public long Id { get; set; }

    public string UserName { get; set; }

    public AddressModel Address { get; set; }
}

AddressModel Address属性是一个嵌套对象。该对象包含实际的地址属性。因此,我需要对我的JSON对象进行反序列化,以便将id和userName添加到Customer对象中,然后将地址字段添加到嵌套的Address对象中。

我没有找到一种内置于newtonsoft中可实现此目的的方法。有什么想法吗?


3
您的 C# 对象并不是您的 JSON 的表示形式。您的 JSON 对象具有 7 个属性 (id、userName、address、address2、city、state 和 zip)。您的 Customer 类需要具有相同的这些属性。 - maccettura
5个回答

7
您可以使用自定义的Json转换器来实现此功能。
public class CustomerJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value is Customer customer)
        {
            var token = new JObject
            {
                ["id"] = customer.Id,
                ["userName"] = customer.UserName,
                ["address"] = customer.Address.Address,
                ["address2"] = customer.Address.Address2,
                ["city"] = customer.Address.City,
                ["state"] = customer.Address.State,
                ["zip"] = customer.Address.ZIP
            };

            token.WriteTo(writer);
        }
        else
        {
            throw new InvalidOperationException();
        }

    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var obj = JToken.ReadFrom(reader);

        if (obj.Type != JTokenType.Object)
        {
            return null;
        }

        return new Customer
        {
            Id = (long) obj["id"],
            UserName = (string) obj["userName"],
            Address = obj.ToObject<AddressModel>()
        };
    }

    public override bool CanRead => true;

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Customer);
    }
}

Customer类上加上一个JsonConverterAttribute
[JsonConverter(typeof(CustomerJsonConverter))]
public class Customer
{
    public long Id { get; set; }

    public string UserName { get; set; }

    public AddressModel Address { get; set; }
}

public class AddressModel
{
    public string Address { get; set; }

    public string Address2 { get; set; }

    public string City { get; set; }

    public string State { get; set; }

    public string ZIP { get; set; }
}

并将其用作如下所示:
var customer = JsonConvert.DeserializeObject<Customer>(customerJson);

或者,您可以简单地拥有一个中间映射模型。

public class CustomerFlattened
{
    public long Id { get; set; }

    public string UserName { get; set; }

    public string Address { get; set; }

    public string Address2 { get; set; }

    public string City { get; set; }

    public string State { get; set; }

    public string ZIP { get; set; }

    public Customer ToCustomer()
    {
        return new Customer
        {
            Id = Id,
            UserName = UserName,
            Address = new AddressModel
            {
                Address = Address,
                Address2 = Address2,
                City = City,
                State = State,
                ZIP = ZIP
            }
        };
    }

    public static CustomerFlattened FromCustomer(Customer customer)
    {
        return new CustomerFlattened
        {
            Id = customer.Id,
            UserName = customer.UserName,
            Address = customer.Address.Address,
            Address2 = customer.Address.Address2,
            City = customer.Address.City,
            State = customer.Address.State,
            ZIP = customer.Address.ZIP
        };
    }
}

并且可以这样利用:
var customer =
    JsonConvert.Deserialize<CustomerFlattened>(
        jsonOriginal
    )
    .ToCustomer();

var customerFlattened = CustomerFlattened.FromCustomer(customer);

var jsonConverted = JsonConvert.Serialize(customerFlattened );

这个人懂得真谛。 - user1289451

3

如果你是设置将被代码消费的 JSON 的人,则你的 JSON 设置与你的对象不匹配。

它应该像这样:

{
    "id": 123,
    "userName": "fflintstone",
    "address": {
        // address properties here
    }
} 

否则,您需要更新C#对象以匹配JSON,这意味着为JSON中的每个项设置单独的属性:
public class Customer
{
    public long Id { get; set; }

    public string UserName { get; set; }

    public string Address { get; set; }

    public string Address2 { get; set; }

    public string City { get; set; }

    public string State { get; set; }

    public string ZIP { get; set; }
}

此外,如果您正在设置这两个内容,街道名称可能不应该命名为“地址”。对我来说,“地址”一词意味着街道名称、号码、城市、州和邮政编码作为一个整体。
如果您无法控制JSON,则没有真正的方法以干净的方式将JSON反序列化为您的对象,例如使用Json.Net。您需要设置某种映射器,或直接查找属性以获取它们的值并将它们添加到您的对象中。您可以将JSON解析为JObject,然后访问所需的属性。
JObject foo = JObject.Parse(//your JSON string here);
customer.Address = (string)foo["Address"];
customer.Address2 = (string)foo["Address2];

一般来说,如果你不能控制JSON结构,最好让你的对象与给定的JSON匹配。

虽然这可能是“正确”的答案,但它并没有回答我的问题。我知道如何使其“正常”反序列化。我想知道是否有一种按照我要求的方式进行反序列化的方法。也许没有,但这就是我提问的原因;为了找出是否有这样的方法。 - user1289451
1
我正在打字/编辑我的答案时,你添加了一个细节。现在我正在更新它。 :) - MattD

1

由于您的JSON和对象模型不匹配,您需要将其反序列化为临时对象,并自己映射字段。在此示例中,我反序列化为一个匿名对象,该对象像模板一样使用。

//This is our test data
var input = @"{
    ""id"": 123,
    ""userName"": ""fflintstone"",
    ""address"": ""345 Cave Stone Road"",
    ""address2"": """",
    ""city"": ""Bedrock"",
    ""state"": ""AZ"",
    ""zip"": """"   
}";

//This anonymous type will be used as a template to deserialize the test data
var template = new 
{ 
    id = default(int),
    userName = default(string),
    address = default(string),
    address2 = default(string),
    city = default(string),
    state = default(string),
    zip = default(string)
};

//Deserialize
var temp = JsonConvert.DeserializeAnonymousType(input, template);

//Use the deserialized object to create an AddressModel
var address = new AddressModel
{
    Address = temp.address,
    Address2 = temp.address,
    City = temp.city,
    State = temp.state,
    Zip = temp.zip
};

//Create a customer using the deserialized data and our new AddressModel instance
var customer = new Customer
{
    Id = temp.id,
    UserName = temp.userName,
    Address = address
};

//Output a couple fields to check
Console.WriteLine("{0} {1}", customer.Id, customer.Address.City);

输出:

123 Bedrock

在DotNetFiddle上的工作示例


1

如果您愿意使用额外的工具,可以创建一个与JSON匹配的DTO,然后使用像Automapper这样的映射器反向展平 DTO到您的对象模型中。


我仍然不明白为什么人们坚持使用可以被简单静态方法替代的工具。 - cwharris
@cwharris 没有人在坚持任何事情。回答创造了选择的机会。GG tho - David Peden
不知道在这个上下文中“GG”是什么意思。是的,有些人坚持认为Automapper是将一个对象转换为另一个对象的正确工具,但我就是看不出它有什么用处。 - cwharris

-1

首先,对于您想要使用AdressModel类创建的对象,您需要更改JSON文档,我已经更改为:

  {   "id": 123,   "userName": "fflintstone",   "Address": {
"address": "345 Cave Stone Road",
"address2": "",
"city": "Bedrock",
"state": "AZ",
"zip": ""   } }

然后对于模型,我创建了这两个模型:

public class Customer
{
    public long id { get; set; }
    public string userName { get; set; }
    public AddressModel Address { get; set; }
}

public class AddressModel
{
    public string address { get; set; }
    public string address2 { get; set; }
    public string city { get; set; }
    public string state { get; set; }
    public string zip { get; set; }
}

现在要反序列化Json文档,你可以像这样做:

Customer jsonConverted = new Customer();

        using (StreamReader r = new StreamReader(HostingEnvironment.ApplicationPhysicalPath + @"\infoFile.json"))
        {
            var json = r.ReadToEnd();
            jsonConverted = JsonConvert.DeserializeObject<Customer>(json);
        }

(我正在使用MVC项目进行测试) {{link1:在此输入图像描述}}

提问者表示他们无法控制JSON。 - MattD
抱歉,在我回答的时候问题被编辑了。不管怎样,这个答案可以作为一个例子。 - Arnold Sandí Calderón

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接