Reputation: 498
I have a class Person.cs
which needs to store a list of Friends (List of Person.cs
). However, it would be overkill to store the whole Person information in that array, just to have a complete object.
Json file
[
{
"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# class
public class Person
{
public string Username { get; set; }
public string DisplayName { get; set; }
public int Age { get; set; }
public List<Person> Friends { get; set; }
}
As you can see the Property Friends
is an array of Person
. It would be overkill for me to redefine all the persons in the array of friends of the json file, especially if it's a large file. I can't instantiate 'mary', because her friends 'jonathan' and 'katy' appear later in the json file. As you see this becomes a nesting dependency problem. Also assume this file has a lot of circular references as everyone is friends with each other.
The problem is ofcourse that I will end up with incomplete Person object in the array of Friends
. If I want to retrieve mary's friends ages, it will result in null values:
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
}
So I tried to fix it by looping through the list again and manually retrieving the friend variable:
foreach (Person friend in mary.Friends)
{
Person friend = PersonManager.Persons.Find(i => i.Username == profile.Username);
int age = friend.age;
}
This works, but is very inefficient though. Especially if I have more properties that are like the Friends
property. Mind you, the Persons
array is in a separate class and I had to make the list static in order to retrieve it all times (i believe this is bad practice too). Is there a better way to achieve what I'm trying to achieve in one go?
Upvotes: 0
Views: 392
Reputation: 155658
Btw, your JSON is invalid. You'll need to look at "Jonathan", see how the record has two UserName
properties and no DisplayName
. I assume the second occurrence should be named DisplayName
.
The quick-and-easy solution is to use PreserveReferencesHandling
, but this also needs to be used by the code that generates the JSON in the first place. If you have no control over the JSON being generated and you want to keep on using Json.NET (aka Newtonsoft.Json
) then you'll need to define a separate DTO type (do not use class inheritance to avoid copying+pasting properties between your DTO type and your business/domain entity type, that's not what inheritance is for - if you're concerned about tedium then use T4 to generate partial
types from a shared list of properties).
So add these classes:
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; }
}
Then change your deserialization code to this:
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>()
};
}
Upvotes: 1