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



反序列化以下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并返回相应的name属性。

还可以创建List<ObjectBase>,以便对这些对象执行其他LINQ操作。

我想我最终想要的是List<ObjectBase>

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

给定Json结构,最自然的表示(IMO(是反序列化为Dictionary<string, ObjectBase>。然后您可以将字典转换为List<ObjectBase>。您需要一个类来匹配(更新的(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的注释,您可以将所有属性添加到单个类中,并反序列化为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...
}

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

上述方法将反序列化为ObjectBaseSingleClass

在线演示

仅名称查找

如果您需要基于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到Json的查询
    • 我们遍历JObjects并检索它们的id属性
      • GetValue返回一个JToken,因为我们知道它是一个数字,所以我们将它强制转换为int
    • 我们基于lookupId执行过滤
    • 我们选择name属性的值
  • 最后,我们需要发出FirstOrDefault方法调用,因为上一个查询返回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);
}
  • 首先,我们根据类定义生成两个json模式
  • 然后我们执行几乎相同的查询,这里唯一的区别是select部分
    • 我们在这里返回整个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));

我将把这个答案留在这里,以防它能帮助那些无法通过去鉴定达到这一点的人(也许他们的物品没有通过去鉴定装箱(。

最新更新