如何反序列化包含同一类嵌套的json类(Unity C#)?

Posted

技术标签:

【中文标题】如何反序列化包含同一类嵌套的json类(Unity C#)?【英文标题】:How to deserialize json class containing a nesting of the same class (Unity C#)? 【发布时间】:2021-06-05 12:57:54 【问题描述】:

我有一个类Person.cs,它需要存储一个朋友列表(Person.cs 列表)。但是,将整个 Person 信息存储在该数组中,只是为了拥有一个完整的对象,这将是矫枉过正。

Json 文件

[ 
     
        "Username": "mary",    
        "DisplayName": "Mary Sanchez",
        "Age": 20,
        "Friends": [
            "Username": "jonathan",
            "Username": "katy"
        ]
    , 
     
        "Username": "jonathan",
        "Age": 25,
        "DisplayName": "Jonathan White",    
        "Friends": [
            "Username": "mary",
            "Username": "katy"
        ]
    ,
     
        "Username": "katy",   
        "DisplayName": "Katy Rivers", 
        "Age": 28,
        "Friends": [
            "Username": "mary",
            "Username": "jonathan"
        ]
    
]

C# 类

public class Person


        public string Username  get; set; 
        public string DisplayName  get; set; 
        public int Age  get; set; 
        public List<Person> Friends  get; set; 

如您所见,属性FriendsPerson 的数组。重新定义 json 文件的朋友数组中的所有人员对我来说太过分了,特别是如果它是一个大文件。我无法实例化“mary”,因为她的朋友“jonathan”和“katy”稍后出现在 json 文件中。如您所见,这变成了嵌套依赖问题。 还假设这个文件有很多循环引用,因为每个人都是彼此的朋友。

问题当然是我会在Friends 的数组中得到不完整的 Person 对象。如果我想检索玛丽的朋友年龄,它将导致空值:

StreamReader reader = new StreamReader("Assets/Resources/Json/persons.json");
Persons = JsonConvert.DeserializeObject<List<Person>>(reader.ReadToEnd());
Person mary = Persons.Find(i => i.Username == "mary");
foreach(Person friend in mary.friends)
    int age = friend.Age; //this will be null

所以我尝试通过再次循环列表并手动检索朋友变量来修复它:

foreach (Person friend in mary.Friends)

    Person friend = PersonManager.Persons.Find(i => i.Username == profile.Username);
    int age = friend.age;

这可行,但效率很低。特别是如果我有更多类似于Friends 属性的属性。请注意,Persons 数组在一个单独的类中,我必须将列表设为静态才能始终检索它(我相信这也是不好的做法)。有没有更好的方法来一次性实现我想要实现的目标?

【问题讨论】:

什么代码序列化了对象图?如果该代码也使用 Newtonsoft,则使用其内置的对象引用设置。 将你的人映射到某个 PersonVM 或 PersonDto,然后反序列化。你的 VM 或 DTO 应该只包含你需要的属性 @IT-Girl 正确的解决方案(不支持好友列表中的完整 PersonDtos)的工作方式如下:1)反序列化为 List。 2) 循环并将它们全部转换为 Person 实例而不填充 Friends 列表。 3) 从 List 而不是 List 创建字典。 4) 在第二遍中,使用对应 List&lt;PersonDto&gt;[i].Friends 列表中的 Friends 列表填充 List&lt;Person&gt;[i].Friends 列表(使用字典进行 O(1) 查找)。 PreserveReferencesHandling.Objects 可以满足您的需求,它会在第一次序列化对象时将"$id" 添加到对象,并在随后遇到时使用"$ref" 对其进行序列化。请参阅:Serialize one to many relationships in Json.net 和 JSON.NET Error Self referencing loop detected for type。 Person in Person 列表是递归的,可能会导致无底的序列化问题。 A 有 B 有 C 有 A 有 B.... 您应该存储人员 id 列表和 Person by id 字典以获取它们。 【参考方案1】:

顺便说一句,您的 JSON 无效。您需要查看“Jonathan”,查看记录如何具有两个 UserName 属性而没有 DisplayName。我假设第二次出现应该命名为DisplayName


快速简便的解决方案是to use PreserveReferencesHandling,但首先生成 JSON 的代码也需要使用它。如果您无法控制生成的 JSON 并且您想继续使用 Json.NET(又名 Newtonsoft.Json),那么您需要定义一个单独的 DTO 类型(不要 使用类继承来避免在 DTO 类型和业务/域实体类型之间复制+粘贴属性,这不是继承的用途 - 如果您担心单调乏味,那么使用 T4 从共享属性列表)。

所以添加这些类:

public class PersonDto

    public string          UserName     get; set; 
    public string          DisplayName  get; set; 
    public int             Age          get; set; 
    public List<PersonRef> Friends      get; set; 


public class PersonRef

    public string UserName  get; set; 

然后将您的反序列化代码更改为:

public List<Person> GetPeople( String jsonText )

    List<PersonDto> peopleDto = JsonConvert.DeserializeObject< List<PersonDto> >( jsonText );

    List<Person> people = peopleDto
        .Select( p => ToPersonWithoutFriends( p ) )
        .ToList();

    Dictionary<String,PersonDto> peopleDtoByUserName = peopleDto.ToDictionary( pd => pd.UserName );

    Dictionary<String,Person> peopleByUserName = people.ToDictionary( p => p.UserName );

    foreach( Person p in people )
    
        PersonDto pd = peopleDtoByUserName[ p.UserName ];
        
        p.Friends.AddRange(
            pd.Friends.Select( pr => peopleByUserName[ pr.UserName ] )
        );
    

    return people;


private static Person ToPersonWithoutFriends( PersonDto dto )

    return new Person()
    
         UserName    = dto.UserName,
         DisplayName = dto.DisplayName,
         Age         = dto.Age,
         Friends     = new List<Person>()
    ;

【讨论】:

太棒了,谢谢!我快到了,但是它给了我“无法将类型 List' 隐式转换为'List”,因为 Profile.cs 中的 Friends 是 List 而 peopleByUserName 是 PersonDto。知道我能做些什么来解决这个问题吗? @IT-Girl 我错了能够内联ToPerson。我已经更新了我的答案。 @IT-Girl 我现在已经通过更改恢复了我的答案。

以上是关于如何反序列化包含同一类嵌套的json类(Unity C#)?的主要内容,如果未能解决你的问题,请参考以下文章

如何将具有嵌套属性的 JSON 对象反序列化为 Symfony 实体?

Unity Json反/序列化嵌套数据[重复]

C# json反序列化 对象中嵌套数组 (转载)

Newtonsoft.Json.JsonConvert 从同一个类中序列化和反序列化

如何使用 Gson @SerializedName 注释在 Kotlin 中反序列化嵌套的 Json API 响应

如何通过 gson 将 json 反序列化为嵌套的自定义地图?