首页 > 解决方案 > 如何反序列化包含同一类嵌套的 json 类(Unity C#)?

问题描述

我有一个类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; }
}

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

问题当然是我最终会在Friends. 如果我想检索玛丽的朋友年龄,它将导致空值:

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数组位于一个单独的类中,我必须将列表设为静态才能始终检索它(我相信这也是不好的做法)。有没有更好的方法可以一次性实现我想要实现的目标?

标签: c#arraysjsonunity3djson.net

解决方案


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


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

所以添加这些类:

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>()
    };
}

推荐阅读