如何在 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
的键
- 最上面属性的对象将被视为
JObject
s(半解析 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
的人,因为例如)。
上述方法将反序列化为 ObjectBase
或 SingleClass
.
反序列化以下内容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
的键
- 最上面属性的对象将被视为
JObject
s(半解析 jsons)
- 最上面的属性名称将是
- 然后我们执行一个Linq to Json查询
- 我们遍历
JObject
并检索它们的id
属性GetValue
returns aJToken
因为我们知道它是一个数字,所以我们将它转换为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
对象而不只是它的名字
- 我们 return 这里是整个
- 最后执行架构验证
- 如果检索到的 json 与
schemaA
匹配,那么我们可以安全地将 (ToObject
) 转换为ObjectA
- 我们也对照
schemaB
检查 json
- 如果检索到的 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
的人,因为例如)。
上述方法将反序列化为 ObjectBase
或 SingleClass
.