英文:
Trouble reading a nested structure
问题
我正在尝试使用Unity的JSON工具读取JSON文件,文件内容如下:
{ "entries": [{
"2019": [{
"january": [{
"6": [{
"name": "Litago",
"ingredients": [{
"kaloriar": "20",
"salt": "10"
}]
}]
}]
}]
}]
}
我对如何设置嵌套类有些困惑。我目前正在尝试这样做,但它不起作用。
[System.Serializable]
public class Entries
{
public KeyValuePair<string, List<Year>> Year;
}
[System.Serializable]
public class Year
{
public KeyValuePair<string, List<Month>> Month;
}
[System.Serializable]
public class Month
{
public KeyValuePair<string, List<Day>> Day;
}
[System.Serializable]
public class Day
{
public KeyValuePair<string, List<Meal>> Meal;
}
[System.Serializable]
public class Meal
{
public string Name;
public List<KeyValuePair<string, string>> ingredients;
}
我这样读取JSON:
Entries entries = JsonUtility.FromJson<Entries>(JSONString);
理想情况下,我想要做类似这样的事情:
Debug.Log(entries["2019"]["January"]["6"]["name"]); // 应该打印出 "Litago"
但由于我的类设置可能不正确,我收到了类型错误。如果有任何建议,将不胜感激,并欢迎其他更好的插件来读取JSON!
英文:
I'm trying to read a JSON file using Unity's JSON utility, and the file looks like this:
{ "entries": [{
"2019": [{
"january": [{
"6": [{
"name": "Litago",
"ingredients": [{
"kaloriar": "20",
"salt": "10"
}]
}]
}]
}]
}]
}
I'm struggling a bit with how I should set up my nested classes. I'm currently doing this, but it's not working.
[System.Serializable]
public class Entries
{
public KeyValuePair<string, List<Year>> Year;
}
[System.Serializable]
public class Year
{
public KeyValuePair<string, List<Month>> Month;
}
[System.Serializable]
public class Month
{
public KeyValuePair<string, List<Day>> Day;
}
[System.Serializable]
public class Day
{
public KeyValuePair<string, List<Meal>> Meal;
}
[System.Serializable]
public class Meal
{
public string Name;
public List<KeyValuePair<string, string>> ingredients;
}
I read the JSON like so:
Entries entries = JsonUtility.FromJson<Entries>(JSONString);
Ideally, I would like to do something like:
Debug.Log(entries["2019"]["January"]["6"]["name"]); // Should print "Litago"
but as my classes are most likely not setup correctly, I get type errors. Any ideas would be appreciated, and suggestions for other better plugins to read the JSON is welcome!
答案1
得分: 1
Here is the translated code portion you requested:
要回答你的问题,如何访问数据,你可以简单地执行以下操作以访问所需内容。
```csharp
JObject entries = JObject.Parse(jsonString);
Console.WriteLine(entries["entries"][0]["2019"][0]["january"][0]["6"][0]["ingredients"][0]["kaloriar"].ToString());
输出
20
创建字典的字典...
你可以创建一个递归方法来构建你的Dictionary<string, object>
项目。之所以要是对象字典是因为每次进入子节点时都有动态值。
public static Dictionary<string, object> BuildDictionary(JObject input)
{
var properties = input.Properties();
// 终止条件
if (properties.ToList().Any(x => x.Name.Equals("name")))
{
Day thisDay = new Day()
{
name = input["name"].ToString(),
ingredients = new Ingredients()
{
kaloriar = input["ingredients"][0]["kaloriar"].ToString(),
salt = input["ingredients"][0]["salt"].ToString()
}
};
return new Dictionary<string, object>() { { "Meal", thisDay } };
}
// 递归
Dictionary<string, object> obj = new Dictionary<string, object>();
foreach (JProperty property in properties)
{
foreach (var element in input[property.Name])
obj.Add(property.Name, BuildDictionary(element as JObject));
}
return obj;
}
在主方法中的用法
JObject entries = JObject.Parse(jsonString);
Dictionary<string, object> dict = BuildDictionary(entries);
生成的字典
{
"entries": {
"2019": {
"january": {
"6": {
"Meal": {
"name": "Litago",
"ingredients": {
"kaloriar": "20",
"salt": "10"
}
}
}
}
}
}
}
然后你可以以非常类似于你寻找的方式访问数据。
Console.WriteLine(JObject.Parse(JsonConvert.SerializeObject(dict))["entries"]["2019"]["january"]["6"]["Meal"]["ingredients"]["kaloriar"].ToString());
输出
20
本质上,你所做的是将元素数组转换为字典,以便以你想要的方式进行访问。
请注意,代码中的引号是直接翻译的,你可能需要根据你的编程环境进行必要的修正。
<details>
<summary>英文:</summary>
To answer your question, how to access the data, you can simply do the following to access what you need.
JObject entries = JObject.Parse(jsonString);
Console.WriteLine(entries["entries"][0]["2019"][0]["january"][0]["6"][0]["ingredients"][0]["kaloriar"].ToString());
**Output**
20
**Create Dictionary of Dictionary...**
You can create a recursive method to build out your `Dictionary<string, object>` items. Reason it has to be Dictionary of objects is because you have dynamic values each time you go in sub node.
public static Dictionary<string, object> BuildDictionary(JObject input)
{
var properties = input.Properties();
// Terminator
if (properties.ToList().Where(x => x.Name.Equals("name")).Count() > 0)
{
Day thisDay = new Day()
{
name = input["name"].ToString(),
ingredients = new Ingredients()
{
kaloriar = input["ingredients"][0]["kaloriar"].ToString(),
salt = input["ingredients"][0]["salt"].ToString()
}
};
return new Dictionary<string, object>() { { "Meal", thisDay } };
}
// Recursive
Dictionary<string, object> obj = new Dictionary<string, object>();
foreach (JProperty property in properties)
{
foreach (var element in input[property.Name])
obj.Add(property.Name, BuildDictionary(element as JObject));
}
return obj;
}
**Usage in Main**
JObject entries = JObject.Parse(jsonString);
Dictionary<string, object> dict = BuildDictionary(entries);
**Resulting Dictionary**
{
"entries": {
"2019": {
"january": {
"6": {
"Meal": {
"name": "Litago",
"ingredients": {
"kaloriar": "20",
"salt": "10"
}
}
}
}
}
}
}
And you can access the data you are looking for very similarly to what you wre looking for.
Console.WriteLine(JObject.Parse(JsonConvert.SerializeObject(dict))["entries"]["2019"]["january"]["6"]["Meal"]["ingredients"]["kaloriar"].ToString());
**Output**
20
In essence what you are doing is taking the array of elements and converting only elements into dictionaries for accessing the way you want.
</details>
# 答案2
**得分**: 1
以下是翻译的内容:
如果您愿意回到[Json.Net](https://www.newtonsoft.com/json)库,您可以利用其可扩展性点来构建与您所需的结构非常相似的东西:
1. 重写`List<T>`上的`[]`操作符,以便允许字符串输入,并使链接看起来更自然。
2. 重写Json.Net附带的`ExpandoObjectConverter`,以便注入您的自定义列表而不是默认值。
总的来说,代码可能如下所示:
```csharp
public class SearchableList<T> : List<T>
{
public object this[string item]
{
get {
var listItem = this.Cast<IDictionary<string, object>>().First(l => l.ContainsKey(item)); // 我假设您的顶级数组项只会有一个匹配的键
return listItem[item];
}
}
}
public class MyConverter : ExpandoObjectConverter
{
static bool IsPrimitiveToken(JsonToken token)
{
if ((uint)(token - 7) <= 5u || (uint)(token - 16) <= 1u)
{
return true;
}
return false;
}
bool MoveToContent(JsonReader reader)
{
JsonToken tokenType = reader.TokenType;
while (tokenType == JsonToken.None || tokenType == JsonToken.Comment)
{
if (!reader.Read())
{
return false;
}
tokenType = reader.TokenType;
}
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return ReadValue(reader);
}
private object ReadValue(JsonReader reader)
{
if (!MoveToContent(reader))
{
throw new JsonSerializationException("在读取ExpandoObject时出现意外结束。");
}
switch (reader.TokenType)
{
case JsonToken.StartObject:
return ReadObject(reader);
case JsonToken.StartArray:
return ReadList(reader);
default:
if (IsPrimitiveToken(reader.TokenType))
{
return reader.Value;
}
throw new JsonSerializationException("在转换ExpandoObject时出现意外令牌。");
}
}
private object ReadList(JsonReader reader)
{
IList<object> list = new SearchableList<object>(); // 不幸的是,由于这一行,不得不重新实现所有类有点不太幸运。
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.EndArray:
return list;
case JsonToken.Comment:
continue;
}
object item = ReadValue(reader);
list.Add(item);
}
throw new JsonSerializationException("在读取ExpandoObject时出现意外结束。");
}
private object ReadObject(JsonReader reader)
{
IDictionary<string, object> dictionary = new ExpandoObject();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.PropertyName:
{
string key = reader.Value.ToString();
if (!reader.Read())
{
throw new JsonSerializationException("在读取ExpandoObject时出现意外结束。");
}
object obj2 = dictionary[key] = ReadValue(reader);
break;
}
case JsonToken.EndObject:
return dictionary;
}
}
throw a JsonSerializationException("在读取ExpandoObject时出现意外结束。");
}
}
void Main()
{
var myConverter = new MyConverter();
dynamic entries = JsonConvert.DeserializeObject<ExpandoObject>("your json here", myConverter);
Console.WriteLine(entries.entries["2019"]["january"]["6"]["name"]);
}
您会注意到,MyConverter
中有许多看似无关的代码,这是由于ExpandoObjectConverter
在默认情况下的可扩展性相对有限所导致的不太幸运的后果。在您的源JSON格式下,您可能只需使用标准的ExpandoObjectConverter
,但它生成的对象在遍历时可能会变得有点尴尬。
希望这为您提供了一个探索的途径。
英文:
If you are open to reverting to Json.Net library, you could leverage it's extensibility points to build something very similar to your desired construct:
- Override
[]
operator on aList<T>
so it allows for string input and make chaining look more natural. - Override
ExpandoObjectConverter
that comes with Json.Net so it injects your custom lists instead of default.
Overall the code might look something like this:
public class SearchableList<T> : List<T>
{
public object this[string item]
{
get {
var listItem = this.Cast<IDictionary<string, object>>().First(l => l.ContainsKey(item)); // I am assuming that your top level array items will only have one matching key
return listItem[item];
}
}
}
public class MyConverter : ExpandoObjectConverter
{
static bool IsPrimitiveToken(JsonToken token)
{
if ((uint)(token - 7) <= 5u || (uint)(token - 16) <= 1u)
{
return true;
}
return false;
}
bool MoveToContent(JsonReader reader)
{
JsonToken tokenType = reader.TokenType;
while (tokenType == JsonToken.None || tokenType == JsonToken.Comment)
{
if (!reader.Read())
{
return false;
}
tokenType = reader.TokenType;
}
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return ReadValue(reader);
}
private object ReadValue(JsonReader reader)
{
if (!MoveToContent(reader))
{
throw new JsonSerializationException("Unexpected end when reading ExpandoObject.");
}
switch (reader.TokenType)
{
case JsonToken.StartObject:
return ReadObject(reader);
case JsonToken.StartArray:
return ReadList(reader);
default:
if (IsPrimitiveToken(reader.TokenType))
{
return reader.Value;
}
throw new JsonSerializationException("Unexpected token when converting ExpandoObject");
}
}
private object ReadList(JsonReader reader)
{
IList<object> list = new SearchableList<object>(); // it is quite unfortunate to have to reimplement all class just because of this one line.
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.EndArray:
return list;
case JsonToken.Comment:
continue;
}
object item = ReadValue(reader);
list.Add(item);
}
throw new JsonSerializationException("Unexpected end when reading ExpandoObject.");
}
private object ReadObject(JsonReader reader)
{
IDictionary<string, object> dictionary = new ExpandoObject();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.PropertyName:
{
string key = reader.Value.ToString();
if (!reader.Read())
{
throw new JsonSerializationException("Unexpected end when reading ExpandoObject.");
}
object obj2 = dictionary[key] = ReadValue(reader);
break;
}
case JsonToken.EndObject:
return dictionary;
}
}
throw new JsonSerializationException("Unexpected end when reading ExpandoObject.");
}
}
void Main()
{
var myConverter = new MyConverter();
dynamic entries = JsonConvert.DeserializeObject<ExpandoObject>("your json here", myConverter);
Console.WriteLine(entries.entries["2019"]["january"]["6"]["name"]);
}
you will notice, MyConverter
has a lot of seemingly unrelated code, which is a bit unfortunate consequence of how ExpandoObjectConverter
has pretty limited extensibility out of the box. You could potentially do just with stock standard ExpandoObjectConverter
but the object it produces gets a bit awkward to traverse given your source json format.
Hopefully this gives you an avenue to explore.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论