如何在 C# 中搜索多态 JSON 对象?

How to search polymorphic JSON objects in c#?

反序列化以下内容json

{
    "MetaData1": "hello world",
    "MetaData2": 2022,
    "Data": {
        "ObjectA": {
            "id": 1,
            "name": "steve",
            "hobbies": 1
        },
        "ObjectB": {
            "id": 2,
            "name": "dave",
            "age": 55
        }
    }
}

进入对应的c#对象

public class ObjectBase
{
    public int id { get; set; }
    public string name { get; set; }

}
public class ObjectA : ObjectBase
{
    public int hobbies { get; set; }
}

public class ObjectB : ObjectBase
{
    public int age { get; set; }
}

public class Data
{
    public ObjectA ObjectA { get; set; }
    public ObjectB ObjectB { get; set; }
}

public class Root
{
    public string metaData1 { get; set; }
    public int metaData2 { get; set; }
    public Data Data { get; set; }
}

使用

Root object = JsonConvert.DeserializeObject<Root>(json);

如何在 Root.Data 的对象属性的 id 属性中搜索匹配的 int 和 return 对应的 name 属性.

能够创建 List<ObjectBase> 以便可以对这些对象执行其他 LINQ 操作也很有用。

仅名称查找

如果您需要根据 id 查找 name 属性 那么您可以使用以下代码来实现:

var semiParsed = JsonConvert.DeserializeObject<Dictionary<string, JObject>>(json);
var name = (from node in semiParsed.Values
            let id = (int)node.GetValue("id")
            where id == lookupId
            select (string)node.GetValue("name"))
    .FirstOrDefault();

Console.WriteLine(name);
  • 首先我们将json反序列化为一个集合
    • 最上面的属性名称将是 Dictionary
    • 的键
    • 最上面属性的对象将被视为 JObjects(半解析 jsons)
  • 然后我们执行一个Linq to Json查询
    • 我们遍历 JObject 并检索它们的 id 属性
      • GetValue returns a JToken 因为我们知道它是一个数字,所以我们将它转​​换为 int
    • 我们根据 lookupId
    • 执行过滤
    • 而我们 select name 属性 的值
  • 最后我们需要发出一个 FirstOrDefault 方法调用,因为之前的查询 return 是一个 IEnumerable<string>

这里我假设 id 是唯一的。如果提供的 lookupId 未在 json 内定义,则结果将为 null.

包装对象查找

如果您需要查找包装对象,那么您还需要使用 Json.NET Schema

var generator = new JSchemaGenerator();
JSchema schemaA = generator.Generate(typeof(ObjectA));
JSchema schemaB = generator.Generate(typeof(ObjectB));

var semiParsed = JsonConvert.DeserializeObject<Dictionary<string, JObject>>(json);
var theNode = (from node in semiParsed.Values
            let id = (int)node.GetValue("id")
            where id == lookupId
            select node)
        .FirstOrDefault();

if (theNode == null)
    return;

if (theNode.IsValid(schemaA))
{
    var objA = theNode.ToObject<ObjectA>();
    Console.WriteLine(objA.hobbies);
} else if (theNode.IsValid(schemaB))
{
    var objB = theNode.ToObject<ObjectB>();
    Console.WriteLine(objB.age);
}
  • 首先我们根据 class 定义生成两个 json 模式
  • 然后我们执行几乎相同的查询这里唯一的区别是 select 部分
    • 我们 return 这里是整个 JObject 对象而不只是它的名字
  • 最后执行架构验证
    • 如果检索到的 json 与 schemaA 匹配,那么我们可以安全地将 (ToObject) 转换为 ObjectA
    • 我们也对照 schemaB 检查 json

最简单的方法是将您的 json 转换为 JObjects 字典,在这种情况下您根本不需要任何 类

var dict = JObject.Parse(json).Properties().ToDictionary(jo => jo.Value["id"],jo=>jo.Value);

var searchId=2;

var name = dict[searchId]["name"];  // dave

或者您可以将 json 反序列化为 C# 对象列表

List<ObjectBase> list = JObject.Parse(json).Properties()
.Select(jo => jo.Value.ToObject<ObjectBase>()).ToList();

并使用 linq 获取数据

正如 serge 在对他的回答的回复中指出的那样,这个使用反射的答案优化不佳。

foreach (var prop in root.GetType().GetProperties())
{

    var obj = prop.GetValue(root);

    if ((int) obj.GetType().GetProperty("id").GetValue(obj) == 2)
    {
        Console.WriteLine(obj.GetType().GetProperty("name").GetValue(obj).ToString());
        break;
    }

}

另一种使用反射获取List<ObjectBase>的方法

var objects = root.Data.Objects;
List<ObjectBase> objectList = objects.GetType().GetProperties().ToList<PropertyInfo>().ConvertAll(x => (ObjectBase)x.GetValue(objects));

我将把这个答案留在这里,以防它帮助那些无法通过灭菌达到这一点的人(也许他们的物品不是通过灭菌装箱的)。

i think what i am ultimately after here is to end up with List<ObjectBase>.

这可以通过 System.Text.Json(或 Newtonsoft)轻松实现。

给定您的 Json 结构的最自然表示 (IMO) 是反序列化为 Dictionary<string, ObjectBase>。然后你可以将字典转换为 List<ObjectBase>。您需要一个 class 来匹配您的(更新的)Json:

中的 Data 元素
// 'root' class to represent the 'Data' element
public class Root
{
    public string MetaData1 { get; set; }
    public int MetaData2 { get; set; }
    public Dictionary<string, ObjectBase> Data { get; set; }
}

// Dictionary<string, ObjectBase>
var model = JsonSerializer.Deserialize<Root>(json);
foreach (var key in model.Data.Keys)
    // do something with model.Data[key].id/name

// convert to List<ObjectBase>
var list = new List<ObjectBase>(model.Data.Values);

扩展 Lasse V. Karlsen 的评论,您可以改为将所有属性添加到单个 class 并反序列化为 Dictionary<string, SingleClass>:

public class SingleClass
{
    public int id { get; set; }
    public string name { get; set; }
    public int hobbies { get; set; }
    public int age { get; set; }
    // all other properties...
}

如果您选择此方法,您可能需要考虑使其他属性可为空(如果您有兴趣区分没有 hobbies 属性 或有 hobbies = 0 的人,因为例如)。

上述方法将反序列化为 ObjectBaseSingleClass.

Demo online