在Unity中对Json和Json数组进行序列化和反序列化

144

我有一个由 PHP 文件发送到 Unity 的项目列表,使用 WWW

WWW.text 看起来像:

[
    {
        "playerId": "1",
        "playerLoc": "Powai"
    },
    {
        "playerId": "2",
        "playerLoc": "Andheri"
    },
    {
        "playerId": "3",
        "playerLoc": "Churchgate"
    }
]

我需要从字符串中修剪掉多余的 [],然后使用 Boomlagoon.JSON 进行解析时,只能检索到第一个对象。我发现我必须对列表进行 deserialize() 并导入 MiniJSON。

但我不知道如何对这个列表进行 deserialize(),我想循环遍历每个 JSON 对象并检索数据。在 Unity 中,我应该如何使用 C# 实现?

我正在使用的类是:

public class player
{
    public string playerId { get; set; }
    public string playerLoc { get; set; }
    public string playerNick { get; set; }
}

去掉[]后,我能够使用MiniJSON解析json数据。但它只返回第一个KeyValuePair

IDictionary<string, object> players = Json.Deserialize(serviceData) as IDictionary<string, object>;

foreach (KeyValuePair<string, object> kvp in players)
{
    Debug.Log(string.Format("Key = {0}, Value = {1}", kvp.Key, kvp.Value));
}

谢谢!


你为什么要去掉外面的 [] 呢?那才是它成为列表的关键。请不要再去掉它,将其反序列化为数组或列表,我相信它会正常工作的。请贴出你尝试过的代码。 - Jon Skeet
请展示用于反序列化的类。格式很奇怪,为什么第二个playerId没有被包含在花括号中?它应该反序列化为某种列表,比如List<PlayerLocation>,因为这确实是一个数组。 - Maximilian Gerhardt
@MaximilianGerhardt 抱歉花括号是个打字错误。我已经在问题中修复了它并添加了代码。谢谢。 - dil33pm
1
我认为你对这个库如何处理反序列化有些误解。它不是通常的反序列化(就像你可能在Newtonsoft.Json中看到的那样),而是Json.Deserialize()总是返回一个IDictionary<string,object>给你,然后你操作List<object>。请参考http://stackoverflow.com/a/22745634/5296568。最好使用一个更好的JSON反序列化器来进行你熟悉的反序列化。 - Maximilian Gerhardt
@MaximilianGerhardt 我尝试使用 IDictionary<string,object>。我能够取出值,但只有第一个 KeyValuePair<> - dil33pm
10个回答

356

在Unity5.3.3更新后,Unity API新增了JsonUtility。除非你在进行更加复杂的操作,否则请不要考虑使用第三方库。相较于其他 Json 库,JsonUtility 更快。请升级到 Unity 版本5.3.3或以上版本,然后尝试以下解决方案。

JsonUtility是一个轻量级的API,仅支持简单类型,不支持类似字典这样的集合。唯一的例外是List,它支持对ListList数组进行序列化!

如果您需要序列化一个Dictionary,或者需要进行除简单数据类型序列化和反序列化之外的其他操作,请使用第三方API。否则,请继续阅读。

以下是一个示例类:

[Serializable]
public class Player
{
    public string playerId;
    public string playerLoc;
    public string playerNick;
}

1. 单个数据对象(非数组JSON)

部分A的序列化:

使用public static string ToJson(object obj);方法将其序列化为Json格式。

Player playerInstance = new Player();
playerInstance.playerId = "8484239823";
playerInstance.playerLoc = "Powai";
playerInstance.playerNick = "Random Nick";

//Convert to JSON
string playerToJson = JsonUtility.ToJson(playerInstance);
Debug.Log(playerToJson);

输出:

{"playerId":"8484239823","playerLoc":"Powai","playerNick":"Random Nick"}

序列化部分 B:

使用public static string ToJson(object obj, bool prettyPrint);方法重载将对象序列化为Json格式。简单地向JsonUtility.ToJson函数传递true将格式化数据。将下面的输出与上面的输出进行比较。

Player playerInstance = new Player();
playerInstance.playerId = "8484239823";
playerInstance.playerLoc = "Powai";
playerInstance.playerNick = "Random Nick";

//Convert to JSON
string playerToJson = JsonUtility.ToJson(playerInstance, true);
Debug.Log(playerToJson);

输出:

{
    "playerId": "8484239823",
    "playerLoc": "Powai",
    "playerNick": "Random Nick"
}

反序列化 Part A:

使用public static T FromJson(string json);方法重载来反序列化JSON。

string jsonString = "{\"playerId\":\"8484239823\",\"playerLoc\":\"Powai\",\"playerNick\":\"Random Nick\"}";
Player player = JsonUtility.FromJson<Player>(jsonString);
Debug.Log(player.playerLoc);

反序列化 Part B:

使用 public static object FromJson(string json, Type type); 方法重载进行json反序列化。

string jsonString = "{\"playerId\":\"8484239823\",\"playerLoc\":\"Powai\",\"playerNick\":\"Random Nick\"}";
Player player = (Player)JsonUtility.FromJson(jsonString, typeof(Player));
Debug.Log(player.playerLoc);

反序列化部分 C:

使用public static void FromJsonOverwrite(string json, object objectToOverwrite);方法将 json 反序列化。当使用 JsonUtility.FromJsonOverwrite 时,不会创建需要反序列化的对象的新实例。它将简单地重复使用您传入的实例并覆盖其值。

这很高效,应该尽可能使用。

Player playerInstance;
void Start()
{
    //Must create instance once
    playerInstance = new Player();
    deserialize();
}

void deserialize()
{
    string jsonString = "{\"playerId\":\"8484239823\",\"playerLoc\":\"Powai\",\"playerNick\":\"Random Nick\"}";

    //Overwrite the values in the existing class instance "playerInstance". Less memory Allocation
    JsonUtility.FromJsonOverwrite(jsonString, playerInstance);
    Debug.Log(playerInstance.playerLoc);
}

2. 多个数据(数组JSON)

您的Json包含多个数据对象。例如,playerId出现了多次。由于Unity的JsonUtility仍然是新技术,因此不支持数组,但您可以使用helper这个人提供的类来使JsonUtility数组一起工作。

创建一个名为JsonHelper的类。直接从下面复制JsonHelper。

public static class JsonHelper
{
    public static T[] FromJson<T>(string json)
    {
        Wrapper<T> wrapper = JsonUtility.FromJson<Wrapper<T>>(json);
        return wrapper.Items;
    }

    public static string ToJson<T>(T[] array)
    {
        Wrapper<T> wrapper = new Wrapper<T>();
        wrapper.Items = array;
        return JsonUtility.ToJson(wrapper);
    }

    public static string ToJson<T>(T[] array, bool prettyPrint)
    {
        Wrapper<T> wrapper = new Wrapper<T>();
        wrapper.Items = array;
        return JsonUtility.ToJson(wrapper, prettyPrint);
    }

    [Serializable]
    private class Wrapper<T>
    {
        public T[] Items;
    }
}

序列化Json数组:

Player[] playerInstance = new Player[2];

playerInstance[0] = new Player();
playerInstance[0].playerId = "8484239823";
playerInstance[0].playerLoc = "Powai";
playerInstance[0].playerNick = "Random Nick";

playerInstance[1] = new Player();
playerInstance[1].playerId = "512343283";
playerInstance[1].playerLoc = "User2";
playerInstance[1].playerNick = "Rand Nick 2";

//Convert to JSON
string playerToJson = JsonHelper.ToJson(playerInstance, true);
Debug.Log(playerToJson);

输出:

{
    "Items": [
        {
            "playerId": "8484239823",
            "playerLoc": "Powai",
            "playerNick": "Random Nick"
        },
        {
            "playerId": "512343283",
            "playerLoc": "User2",
            "playerNick": "Rand Nick 2"
        }
    ]
}

反序列化 Json 数组:

string jsonString = "{\r\n    \"Items\": [\r\n        {\r\n            \"playerId\": \"8484239823\",\r\n            \"playerLoc\": \"Powai\",\r\n            \"playerNick\": \"Random Nick\"\r\n        },\r\n        {\r\n            \"playerId\": \"512343283\",\r\n            \"playerLoc\": \"User2\",\r\n            \"playerNick\": \"Rand Nick 2\"\r\n        }\r\n    ]\r\n}";

Player[] player = JsonHelper.FromJson<Player>(jsonString);
Debug.Log(player[0].playerLoc);
Debug.Log(player[1].playerLoc);

输出:

Powai

User2


如果这是来自服务器的Json数组且你不是手动创建的:
您可能需要在接收到的字符串前面添加{"Items":,然后在末尾添加}
我为此编写了一个简单的函数:
string fixJson(string value)
{
    value = "{\"Items\":" + value + "}";
    return value;
}

然后你可以使用它:
string jsonString = fixJson(yourJsonFromServer);
Player[] player = JsonHelper.FromJson<Player>(jsonString);

3.没有类的反序列化json字符串和带数字属性的Json反序列化

这是以数字或数字属性开头的Json。

例如:

{ 
"USD" : {"15m" : 1740.01, "last" : 1740.01, "buy" : 1740.01, "sell" : 1744.74, "symbol" : "$"}, 

"ISK" : {"15m" : 179479.11, "last" : 179479.11, "buy" : 179479.11, "sell" : 179967, "symbol" : "kr"},

"NZD" : {"15m" : 2522.84, "last" : 2522.84, "buy" : 2522.84, "sell" : 2529.69, "symbol" : "$"}
}

Unity的JsonUtility不支持此操作,因为“15m”属性以数字开头。类变量不能以整数开头。
从Unity的wiki下载SimpleJSON.cs。
获取USD的“15m”属性:
var N = JSON.Parse(yourJsonString);
string price = N["USD"]["15m"].Value;
Debug.Log(price);

获取 ISK 的 "15m" 属性:
var N = JSON.Parse(yourJsonString);
string price = N["ISK"]["15m"].Value;
Debug.Log(price);

获取NZD的“15m”属性:
var N = JSON.Parse(yourJsonString);
string price = N["NZD"]["15m"].Value;
Debug.Log(price);

Json属性中不以数字开头的部分可以由Unity的JsonUtility处理。

4.故障排除JsonUtility:

使用JsonUtility.ToJson序列化时出现问题?

使用JsonUtility.ToJson得到空字符串或"{}"?

A. 确保类不是数组。如果是,请改用上面的辅助类JsonHelper.ToJson代替JsonUtility.ToJson

B. 在要序列化的类顶部添加[Serializable]

C. 从类中删除属性。例如,在变量public string playerId { get; set; }中,删除{ get; set; }。Unity无法序列化此内容。

使用JsonUtility.FromJson反序列化时出现问题?

A. 如果得到Null,请确保Json不是Json数组。如果是,请改用上面的辅助类JsonHelper.FromJson代替JsonUtility.FromJson

B。如果在反序列化时出现NullReferenceException,请在类顶部添加[Serializable]

C。如果出现其他问题,请验证您的json是否有效。转到此网站here并粘贴json。它应该向您显示json是否有效。它还应该使用Json生成适当的类。只需确保从每个变量中删除remove{ get; set; },并在生成的每个类的顶部添加[Serializable]


Newtonsoft.Json:

如果出于某些原因必须使用 Newtonsoft.Json,则从2022年2月起可以直接从Unity获取,链接在此处:com.unity.nuget.newtonsoft-json@3.0。只需通过包管理器添加com.unity.nuget.newtonsoft-json即可。详见 通过UPM安装官方版本 by kalle (jag)
您还可以查看来自 SaladLab / Json.Net.Unity3D 的Unity分支版本。请注意,如果使用某些功能可能会导致崩溃,请小心。

回答您的问题:

您的原始数据为

 [{"playerId":"1","playerLoc":"Powai"},{"playerId":"2","playerLoc":"Andheri"},{"playerId":"3","playerLoc":"Churchgate"}]

前面添加{"Items":,然后在末尾添加}

执行此操作的代码:

serviceData = "{\"Items\":" + serviceData + "}";

现在你有:
 {"Items":[{"playerId":"1","playerLoc":"Powai"},{"playerId":"2","playerLoc":"Andheri"},{"playerId":"3","playerLoc":"Churchgate"}]}

要将来自PHP的多个数据序列化为数组,现在可以执行以下操作:

public player[] playerInstance;
playerInstance = JsonHelper.FromJson<player>(serviceData);

playerInstance[0] 是你的第一份数据

playerInstance[1] 是你的第二份数据

playerInstance[2] 是你的第三份数据

或者是在类中使用 playerInstance[0].playerLoc, playerInstance[1].playerLoc, playerInstance[2].playerLoc ...... 来获取数据。

在访问前,你可以使用 playerInstance.Length 检查长度。

注意:从 player 类中移除 { get; set; }。如果有 { get; set; },它将无法工作。Unity 的 JsonUtility 不适用于被定义为属性的类成员。


2
看一下我上面发布的代码。它向你展示了三种使用JsonUtility.FromJson的方法。我忘记告诉你从player类中删除{ get; set; }。如果你有{ get; set; },它不会起作用。将你的player类与我上面发布的那个进行比较,你就会明白我在说什么了。 - Programmer
1
没问题。当Unity很快添加对数组的支持时,我会进行编辑,这样你就不再需要那个Helper类了。 - Programmer
3
我不会说“忘记所有第三方库”。JsonUtility有其限制。它不能返回可进行操作的JSON对象。例如,我获取了一个JSON文件并想要检查是否存在“success”键。无法做到。JsonUtility要求使用者知道JSON文件的确切内容。此外,没有字典转换器。因此,它做了一些好事,但仍然需要使用第三方库。 - Everts
3
使用JsonHelper即可。如果您使用它创建JSON,也可以使用它读取JSON,无需额外的步骤。唯一需要做额外处理的情况是如果您从服务器接收JSON数组,并且这包含在我的答案中。另一种不使用JsonHelper的方法是将类放入另一个类中,然后将其制作为List。这对于大多数人都有效。如果您正在寻找保存和加载游戏数据的方法,请参见此处您可以用一行代码加载和保存数据 - Programmer
1
请注意,在较新版本中,Newtonsoft JSON 作为 Package 部分“内置”于包管理器中!它更加强大,例如支持属性、字典、嵌套数组等。 - derHugo
显示剩余13条评论

24

假设您获得了这样的JSON

[
    {
        "type": "qrcode",
        "symbol": [
            {
                "seq": 0,
                "data": "HelloWorld9887725216",
                "error": null
            }
        ]
    }
]

要在Unity中解析上述JSON,你可以创建如下的JSON模型。

[System.Serializable]
public class QrCodeResult
{
    public QRCodeData[] result;
}

[System.Serializable]
public class Symbol
{
    public int seq;
    public string data;
    public string error;
}

[System.Serializable]
public class QRCodeData
{
    public string type;
    public Symbol[] symbol;
}

然后只需以以下方式解析即可...

var myObject = JsonUtility.FromJson<QrCodeResult>("{\"result\":" + jsonString.ToString() + "}");

现在,您可以根据需要修改JSON/CODE。 https://docs.unity3d.com/Manual/JSONSerialization.html


这实际上运行得非常好,我用类似于Symbol的类使其工作,而不是数组。 - Gennon
4
我正在尝试使用Unity 2018,但是不起作用:数组没有被解析。 - Jean-Michaël Celerier
适用于Unity 2018和2019。非常好用。像这样访问数组数据:Debug.Log(myObject.result[0].symbol[0].data); 或者 for(var i = 0; i <= myObject.result.Length - 1; i ++) { Debug.Log(myObject.result[i].type); } 或者 foreach (var item in myObject.result) { Debug.Log(item.type); } - Towerss
@Narottam Goyal,你的方法在某些情况下不起作用,对于初学者来说也是一个非常困难的解决方案,请参考我在此线程上的答案链接 - Junaid Pathan
@JunedKhanMomin,你的答案基本上是相同的,但没有解决这个问题,即这里特定的 JSON 数据中的根级数组。一般来说,你应该参考程序员的答案,那个更加详细。 - derHugo

5

Unity <= 2019

Narottam Goyal的一个好主意是将数组包装成json对象,然后反序列化为结构体。以下使用泛型来解决所有类型的数组问题,而不是每次都创建一个新类。

[System.Serializable]
private struct JsonArrayWrapper<T> {
    public T wrap_result;
}

public static T ParseJsonArray<T>(string json) {
    var temp = JsonUtility.FromJson<JsonArrayWrapper<T>>("{\"wrap_result\":" + json + "}");
    return temp.wrap_result;
}

它可以以下列方式使用:

string[] options = ParseJsonArray<string[]>(someArrayOfStringsJson);

Unity 2020

在Unity 2020中,有一个官方的newtonsoft软件包,这是一个更好的JSON库。


5
您提供的链接已失效,但是以下链接被列为相关文章: https://docs.unity3d.com/Packages/com.unity.nuget.newtonsoft-json@2.0/manual/index.html 该链接带有注释:“这是一个旨在用于Unity内部开发项目的包,因此该包不受支持。请自行承担使用风险。” - R2RT
第一种解决方案还有效吗?我得到的只是wrap_result的空值。 - david.pfx
@david.pfx,有一个错别字已经被修复了,现在怎么样? - Justin Meiners
抱歉,无法测试它,已经切换到Newtonsoft以保持进展。但是我会在有时间的时候再试一次 - 我真的不想添加不必要的依赖关系。 - david.pfx
2
终于在Unity中获得了支持多态的序列化,这要感谢这个包。在Unity中,只需打开“包管理器”窗口,然后选择按名称添加包,并提供com.unity.nuget.newtonsoft-json作为名称即可。 - Peppe L-G

3

您需要在PlayerItem类中添加[System.Serializable],像这样:

using System;
[System.Serializable]
public class PlayerItem   {
    public string playerId;
    public string playerLoc;
    public string playerNick;
}

0

要阅读JSON文件,请参考以下简单示例

您的JSON文件(StreamingAssets/Player.json)

{
    "Name": "MyName",
    "Level": 4
}

C#脚本

public class Demo
{
    public void ReadJSON()
    {
        string path = Application.streamingAssetsPath + "/Player.json";
        string JSONString = File.ReadAllText(path);
        Player player = JsonUtility.FromJson<Player>(JSONString);
        Debug.Log(player.Name);
    }
}

[System.Serializable]
public class Player
{
    public string Name;
    public int Level;
}

2
数组怎么样?这个答案有什么补充,是之前被接受的答案三年前没有提到的吗? - derHugo
@derHugo,它包括对File.ReadAllText的调用,我认为这很有帮助,其他答案没有提到 :) - Ruzihm
1
@Ruzihm,其他答案没有提到这个原因是因为这个问题的范围是如何(反)序列化,而不是如何进行文件IO ;) - derHugo

0

就像 @Maximiliangerhardt 所说的那样,MiniJson 没有适当反序列化的功能。我使用了 JsonFx 并且工作得很好。可以使用 []

player[] p = JsonReader.Deserialize<player[]>(serviceData);
Debug.Log(p[0].playerId +" "+ p[0].playerLoc+"--"+ p[1].playerId + " " + p[1].playerLoc+"--"+ p[2].playerId + " " + p[2].playerLoc);

0

您可以使用 Newtonsoft.Json,只需将 Newtonsoft.dll 添加到您的项目中,并使用以下脚本:

using System;
using Newtonsoft.Json;
using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{

    [Serializable]
    public class Person
    {
        public string id;
        public string name;
    }
    public Person[] person;

    private void Start()
    {
       var myjson = JsonConvert.SerializeObject(person);

        print(myjson);

    }
}

enter image description here

另一种解决方案是使用JsonHelper
using System;
using Newtonsoft.Json;
using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{

    [Serializable]
    public class Person
    {
        public string id;
        public string name;
    }
    public Person[] person;

    private void Start()
    {
        var myjson = JsonHelper.ToJson(person);

        print(myjson);

    }
}

enter image description here


2
请注意,例如在Android上构建时,newtonsoft似乎无法在Unity中工作。但是,在Unity编辑器中它可以正常工作。在这种情况下,建议使用适用于Unity的newtonsoft分支。参考链接:https://dev59.com/0bfna4cB1Zd3GeqP1OrL - Rose

0

如果您来到这里是为了了解如何使用JsonUtility序列化List而不需要任何额外的包,请继续阅读。

[System.Serializable]
public class JsonableListWrapper<T>
{
    public List<T> list;
    public JsonableListWrapper(List<T> list) => this.list = list;
}

示例用法:

List<string> stringList = new List<string>(){"one","two","three"};
// To Json
string stringListAsJson = JsonUtility.ToJson(new JsonListWrapper<string>(stringList));
// From Json
List<string> stringListFromJson = JsonUtility.FromJson<JsonListWrapper<string>>(stringListAsJson).list;

Credit to theonlysake from this thread: https://forum.unity.com/threads/jsonutilities-tojson-with-list-string-not-working-as-expected.722783/ 看起来你不能直接序列化/反序列化一个List,但如果它是一个类的属性,你可以。在我自己的个人案例中,我用一个自定义类的类型替换了字符串类型,效果很好。

-1
不要去掉[],这样就没问题了。 []代表一个JSON数组,正是你需要的,可以用来迭代其中的元素。

-1

如果您正在使用Vector3,这是我所做的。

1- 我创建了一个名为Player的类

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class Player
{
    public Vector3[] Position;

}

第二步,我这样调用它

if ( _ispressed == true)
        {
            Player playerInstance = new Player();
            playerInstance.Position = newPos;
            string jsonData = JsonUtility.ToJson(playerInstance);

            reference.Child("Position" + Random.Range(0, 1000000)).SetRawJsonValueAsync(jsonData);
            Debug.Log(jsonData);
            _ispressed = false;
        }

3- 这是结果

"位置":[ {"x":-2.8567452430725099,"y":-2.4323320388793947,"z":0.0}]}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接