处理动态属性的Json到C#对象转换

4

我正在尝试在C#对象中实现JSON结构,并且正在尝试了解如何根据类型使用正确的对象。例如:

public class RootObject
{
    public string name { get; set; }
    public Content content { get; set; }

}
public class Content
{
    public string id{ get; set; }
    public string type { get; set; }
    public Dictionary<string, Item> child { get; set; }
}

public class Item
{
    public string id { get; set; }
    public string type { get; set; }
    public List<string> model { get; set;}
    public string[] color {get; set;}
}

请注意,这只是一个示例,每个对象都有更多属性。如果 Json 包含 type = "Boy",我该如何生成男孩对象。
示例 JSON:
string json = @"
            {
            'name': 'Object 1',
            'content': {
                'body': {
                    'id': 'body',
                    'type': 'Body'
                },
                'style': {
                    'id': 'style',
                    'type': 'Style'
                },
                'DynamicName-123': {
                    'id': 'DynamicName-123',
                    'type': 'Row'
                    'model': {},
                    'colors': []
                },
                'DynamicName-434': {
                    'id': 'DynamicName-434',
                    'type': 'Column'
                    'model': {},
                    'colors': []
                },
                'DynamicName-223': {
                    'id': 'DynamicName-223',
                    'type': 'Item'
                    'model': {},
                    'colors': []
                }
            }
        }";

你能否更改生成的JSON以包含类型,还是这是你接收到的数据?如果无法更改,则需要实现自己的自定义JsonConverter并根据属性覆盖读取方法。 - Icepickle
2
请阅读[提问指南]并展示您已经尝试过的内容。同时,创建一个包含一些JSON示例的[最小完整可复现示例]。例如,请参考如何将包含不同数据类型的JSON数组反序列化为单个对象。您可能需要一个自定义的JsonConverter,根据JSON中的“type”字符串实例化适当的类型。 - CodeCaster
你能分享一个 JSON 样例吗? - Icepickle
我真的很想知道男孩和女孩为什么有这么少的共同点,他们甚至没有共享“parent”这个东西,如果他们有的话,你可以让他们成为“Child”类的一部分,并让你的字典将“Child”作为男孩和女孩的其中一个。就目前而言,它真的是“object”或“dynamic”。所以你的问题是关于Json序列化还是类设计呢? :) - Icepickle
@Mask-dCodex,你试过我下面的解决方案了吗?因为JsonExtensionData是用来处理像你的这样的Json的。 - er-sho
显示剩余5条评论
2个回答

3

如果你的键值对不是固定的,数据必须可配置,则Newtonsoft.json有一个特性可用于此处,即 [JsonExtensionData]了解更多

现在,在序列化对象时会写入扩展数据。读取和写入扩展数据使得可以自动往返所有JSON,而无需将每个属性添加到您正在反序列化为的.NET类型中。只声明您感兴趣的属性,让扩展数据来完成其余部分。

在你的情况下,假设有一个类,

public class MyClass
{
    public string Qaz { get; set; }
    public string Wsx { get; set; }

    [JsonExtensionData]
    public Dictionary<string, JToken> child { get; set; }

    public MyClass()
    {
        child = new Dictionary<string, JToken>();
    }
}

在上述类中,您知道QazWsx始终从您的JSON中存在,无论它们包含值还是null。
但对于动态数据,您无法确定将从JSON中接收哪个键/值对,因此 [JsonExtensionData] 可以将所有这些键/值对收集到字典中。
假设以下类将用于您的动态数据:
public class ABC
{
    public string Abc { get; set; }
}

public class PQR
{
    public string Pqr { get; set; }
}

public class XYZ
{
    public string Xyz { get; set; }
}

序列化:

ABC aBC = new ABC { Abc = "abc" };
PQR pQR = new PQR { Pqr = "pqr" };
XYZ xYZ = new XYZ { Xyz = "xyz" };

MyClass myClass = new MyClass();

myClass.Qaz = "qaz";
myClass.Wsx = "wsx";

myClass.child.Add("ABC", JToken.FromObject(aBC));
myClass.child.Add("PQR", JToken.FromObject(pQR));
myClass.child.Add("XYZ", JToken.FromObject(xYZ));

string outputJson = JsonConvert.SerializeObject(myClass);

这将为您提供类似JSON的内容。
{
  "Qaz": "qaz",
  "Wsx": "wsx",
  "ABC": {
    "Abc": "abc"
  },
  "PQR": {
    "Pqr": "pqr"
  },
  "XYZ": {
    "Xyz": "xyz"
  }
}

反序列化:

MyClass myClass = JsonConvert.DeserializeObject<MyClass>(outputJson);

string Qaz = myClass.Qaz;
string Wsx = myClass.Wsx;

if (myClass.child.ContainsKey("ABC"))
{
    ABC abcObj = myClass.child["ABC"].ToObject<ABC>();
}

if (myClass.child.ContainsKey("PQR"))
{
    PQR pqrObj = myClass.child["PQR"].ToObject<PQR>();
}

if (myClass.child.ContainsKey("XYZ"))
{
    XYZ pqrObj = myClass.child["XYZ"].ToObject<XYZ>();
}

结论:[JsonExtensionData]的主要目的是使您的JSON类层次结构简单且更易于阅读,因此您无需为每个属性管理类结构。

获取字典中JToken内特定键的所有动态数据:

您可以使用LINQ从上述字典中提取特定键的所有动态数据。

var allAbcTypes = myClass.child
    .SelectMany(x => x.Value
                      .ToObject<JObject>()
                      .Properties()
                      .Where(p => p.Name == "Abc")    //<= Use "Column" instead of "Abc"
                      .Select(o => new ABC            //<= Use your type that contais "Column" as a property
                      {
                           Abc = o.Value.ToString()
                      })).ToList();

在您的情况下,类似于:

var allColumnTypes = myClass.child
    .SelectMany(x => x.Value
                      .ToObject<JObject>()
                      .Properties()
                      .Where(p => p.Name == "Column")
                      .Select(o => new Item
                      {
                         id = x.Value["id "].ToString(),
                         type = x.Value["type "].ToString(),
                         model = x.Value["model"].ToObject<List<string>>(),
                         color = x.Value["color"].ToObject<string[]>()
                      })).ToList();

请告诉我你的 JSON 中有多少 "boy" 对象,因为在同一级别的 JSON 中,不允许您多次添加相同的键, 请展示你的样本 JSON 以清楚地说明你想要做什么? - er-sho
我已经更新了该线程,并提供了一个示例。我只是试图生成一个对象列表,就像上面的结构一样,但在我的情况下,每个对象都有一个唯一的名称,但属性是相同的。我不能确切地调用[JsonProperty()],因为它总是唯一的。 - Freddy.
你现在更新的类结构已经为你的JSON显示提供了唯一的名称,因为你已经使用了Dictionary并且这个字典将处理唯一的名称,因为字典不允许重复的键名,除非你从Content类中删除'id'和'type'。然后一切都会为你工作。 - er-sho
正如您在上面的评论中所说,“每个对象都有一个唯一的名称,但属性是相同的”,因此类Item对于每个唯一的键来说是相同的,但数据不同,对吗? - er-sho
我在答案的最后添加了一个部分,请仔细查看。它对我有效 :) - er-sho
显示剩余7条评论

0
如果你想将一个键为字符串和动态值(在这种情况下是男孩或女孩)的序列化对象反序列化为字典,我所知道的唯一方法就是使用Dynamic类:
 public List<Dictionary<string, dynamic>> child { get; set; }

啊,有趣,这可能是我需要的解决方案,因为我并不真正关心我正在反序列化的Json,我只想存储它并从中提取数据。我该如何访问这些数据? - Freddy.
你可以像使用其他类一样使用它: var dynamic = new Boy(); Console.WriteLine(dynamic.Name) 但在你的情况下,也许最好的选择是强制转换对象(我不确定你要做什么,但我给你写了一个例子): if (element is Boy) { var boy = element as Boy; 在男孩的情况下编写您的代码 } else if (element is Girl) { var girl = element as Girl; 在女孩的情况下编写您的代码} 如果您只想序列化对象的其他情况,则最好的选择是像er-ho所说的JTOKEN。 - Kifreak

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