【问题标题】:JsonConvert dictionary with key of type Vector2Int具有 Vector2Int 类型键的 JsonConvert 字典
【发布时间】:2018-11-05 09:41:56
【问题描述】:

我正在开发一个游戏,它有一个名为UserInfo 的类。

UserInfo 类中有一个public List<Lineup> lineups 变量。

Lineup 包含public Dictionary<Vector2Int, Collection> cardLocations

我的问题是,JsonConvert.SerializeObject 确实正确地产生了我想要的结果,但是当我做 JsonConvert.DeserializeObject<UserInfo> 时,Vector2Int 仍然是像 "(x, y)" 这样的字符串。

我应该如何解决它?

我写了一个JsonConverter来转换它,并使用了

JsonConvert.SerializeObject(userInfo, Formatting.Indented, new Vec2Converter()) 

用于序列化和

JsonConvert.DeserializeObject<UserInfo>(json, new Vec2Converter())

去反序列化,但它不起作用。

这是我的JsonConverter 脚本:

using Newtonsoft.Json;
using UnityEngine;
using System;

public class Vec2Converter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteValue(value.ToString());
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var vector2Int = serializer.Deserialize<Vector2Int>(reader);
        Debug.Log(vector2Int);
        return vector2Int;
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Vector2Int);
    }
}

另一个奇怪的事情是,当我尝试将Vector2Int 更改为LineupDictionary&lt;Vector2Int, Collection&gt; 等其他数据结构时,单击播放时Unity 退出了。我改回来之后就没事了。 ok 表示它没有退出,但仍然给我错误消息。

忘记放错误信息:ArgumentException: Could not cast or convert from System.String to UnityEngine.Vector2Int

这是课程。

public class UserInfo {
    public string username;
    public int playerID;
    public List<Collection> collection = new List<Collection>();
    public List<Lineup> lineups = new List<Lineup>(); // Here is the problem
    public Dictionary<string, int> contracts = new Dictionary<string, int>();
    public int coins = 0;
    public int rank = 0;
    public int lastLineupSelected = -1;
    public int winsToday = 0;
    public Stats total = new Stats();
    public Dictionary<string, Stats> boardResults = new Dictionary<string, Stats>();
    public List<Mission> missions;
    public string preferredBoard = "Standard Board";
    public string lastModeSelected = "";
    public int gameID;
    public bool missionSwitched = false;
}

public class Lineup
{
    public Dictionary<Vector2Int, Collection> cardLocations;  // Here is the problem
    public List<Tactic> tactics;
    public string boardName;
    public string lineupName;
    public string general;
    public bool complete;
}

public class Collection
{
    public string name = "";
    public string type = "";
    public int count = 1;
    public int health = 0;
    public int oreCost = 0;
}

【问题讨论】:

标签: c# unity3d serialization json.net


【解决方案1】:

手动解析字符串并创建所需的类型。

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
    var vector2IntString = reader.Value.ToString();//expecting "(x, y)"
    Debug.Log(vector2IntString);
    var parts = vector2IntString.Split(new char[]{ '(', ')', ',', ' '}, StringSplitOptions.RemoveEmptyEntries);
    var x = int.Parse(parts[0]);
    var y = int.Parse(parts[1]); 
    var vector2Int = new Vector2Int(x, y);
    return vector2Int;
}

上面采用"(x, y)" 字符串并提取x 和y 值。它将它们转换为整数并使用它们来初始化所需类型的实例 (Vector2Int)

一些关于这个特定场景的进一步阅读揭示了

如果您只需要对 JSON 进行类型转换,您还可以使用 JSON.NET 自己的自定义类型转换器。虽然这在字典键的情况下不起作用。

最后,如果您有一个复杂类型作为您的字典键,您可能需要考虑将其序列化为一个集合,然后使用 JSON.NET 自定义转换器对其进行反序列化。

【讨论】:

  • 谢谢您,但我刚刚尝试了您的代码,它仍然无法正常工作。我认为我的问题不是没有创建正确的 Vector2Int。我想我应该使用像 lineupConverter 或 dictConverter 这样的转换器?但这太复杂了,因为我有很多变量。
  • @Seaky 解释一下not working 的意思。发生了什么事?
  • 在尝试您的代码之前和之后,我遇到了 ArgumentException:无法从 System.String 转换或转换为 UnityEngine.Vector2Int。
  • @Seaky 在哪一行?
  • 它没有告诉我。我认为脚本和代码是正确的,但是否 Vector2Int 位于数据结构内部以至于无法检测到?
【解决方案2】:

派生自 JsonConverter 的自定义序列化程序需要更多工作,并且几乎没有错误。首先,请注意您正在尝试序列化/反序列化 DictionaryVector2Int 而不仅仅是 Vector2Int 变量。

是这样的:

public Dictionary<Vector2Int, Collection> cardLocations.

不是

public Vector2Int cardLocations;

由于上述声明,您的override bool CanConvert(Type objectType) 函数应该检查typeof(Dictionary&lt;Vector2Int, Collection&gt;) 不是 typeof(Vector2Int)。此外,您还需要添加检查以确定何时运行自定义反序列化程序。

序列化

1.在序列化json的WriteJson函数中,您只需要自定义抛出异常的Dictionary&lt;Vector2Int, Collection&gt;部分,因此请确保自定义代码只运行 当类型为Dictionary&lt;Vector2Int, Collection&gt; 时。您可以通过将代码放入 if (value is Dictionary&lt;Vector2Int, Collection&gt;) 中来做到这一点。

2。从第二个参数中获取要序列化的字典。调用writer.WriteStartArray() 以便将每个值写入数组。现在,遍历Dictionary,用writer.WriteValue(key) 写入键,然后用serializer.Serialize(writer, entry.Value); 写入值。

3。在循环调用writer.WriteStartArray(); 之后/之外,然后从该函数返回。

如果 #1 中的if (value is Dictionary&lt;Vector2Int, Collection&gt;)false,则只需调用writer.WriteStartObject() 后跟writer.WriteEndObject()


反序列化

4.确定何时运行自定义反序列化程序

我们在 #1 中使用if (value is Dictionary&lt;Vector2Int, Collection&gt;) 在序列化时确定自定义序列化程序代码是否应该运行,但对于反序列化,我们使用if (reader.TokenType == JsonToken.StartArray) 确定我们是否已到达数组部分我们在 ReadJson 函数中进行了自定义序列化。

5.使用JArray.Load(reader);获取我们序列化的数组数据。在返回的数组中,其中的第一个元素是Dictionary 中的键。第二个元素是Dictionary 中的值。第三个元素是Dictionary 中的第二个键。第四个元素是Dictionary ans 中的第二个值。

6。将JArray中的键和值分开。

为了简化这一点,循环遍历JArray 增量2 而不是1。通过增加2,可以很容易地使用JArray[loop + 0] 检索密钥,并且可以使用JArray[loop + 1] 在同一循环中检索值。

for (int i = 0; i < jArray.Count; i += 2)
{
    //Key
    string key = jArray[i + 0].ToString();
    //Value
    string value = jArray[i + 1].ToString();
}

7.获取Vector2Int格式的密钥。

只需将 #6 中的密钥反序列化为 Vector2IntJsonConvert.DeserializeObject&lt;Vector2Int&gt;(key)

8.获取Collection格式的值。

只需将 #6 中的值反序列化为 CollectionJsonConvert.DeserializeObject&lt;Collection&gt;(value)

9.重构数据

创建Dictionary&lt;Vector2Int, Collection&gt; 的新实例,然后将#7 中的键和#8 中的值添加到其中,然后从ReadJson 函数中返回它。

如果 #4 中的if (reader.TokenType == JsonToken.StartArray)false,只需从ReadJson 函数创建并返回Dictionary&lt;Vector2Int, Collection&gt; 的新实例,而不向其添加键或值。

class Vec2DictionaryConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(Dictionary<Vector2Int, Collection>).IsAssignableFrom(objectType);
    }

    //Deserialize json to an Object
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        //Debug.Log("De-serializing!");
        if (reader.TokenType == JsonToken.StartArray)
        {
            // Load JArray from stream
            JArray jArray = JArray.Load(reader);

            //Where to re-create the json data into 
            Dictionary<Vector2Int, Collection> dict = new Dictionary<Vector2Int, Collection>();

            if (jArray == null || jArray.Count < 2)
            {
                return dict;
            }

            //Do the loop faster with +=2
            for (int i = 0; i < jArray.Count; i += 2)
            {
                //first item = key
                string firstData = jArray[i + 0].ToString();
                //second item = value
                string secondData = jArray[i + 1].ToString();

                //Create Vector2Int key data 
                Vector2Int vect = JsonConvert.DeserializeObject<Vector2Int>(firstData);

                //Create Collection value data
                Collection values = JsonConvert.DeserializeObject<Collection>(secondData);

                //Add both Key and Value to the Dictionary if key doesnt exit yet
                if (!dict.ContainsKey(vect))
                    dict.Add(vect, values);
            }
            //Return the Dictionary result
            return dict;
        }
        return new Dictionary<Vector2Int, Collection>();
    }

    //SerializeObject to Json
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        //Debug.Log("Serializing!");
        if (value is Dictionary<Vector2Int, Collection>)
        {
            //Get the Data to serialize
            Dictionary<Vector2Int, Collection> dict = (Dictionary<Vector2Int, Collection>)value;

            //Loop over the Dictionary array and write each one
            writer.WriteStartArray();
            foreach (KeyValuePair<Vector2Int, Collection> entry in dict)
            {
                //Write Key (Vector) 
                serializer.Serialize(writer, entry.Key);
                //Write Value (Collection)
                serializer.Serialize(writer, entry.Value);
            }
            writer.WriteEndArray();
            return;
        }
        writer.WriteStartObject();
        writer.WriteEndObject();
    }
}

用法

序列化

string json = JsonConvert.SerializeObject(userInfo, new Vec2DictionaryConverter());

反序列化

UserInfo obj = JsonConvert.DeserializeObject<UserInfo>(json, new Vec2DictionaryConverter());

【讨论】:

    【解决方案3】:

    对于像我这样会遇到同样问题的人。

    @Programmer 的回答是正确的,但可能已经过时(我不知道 Json.NET 在 2018 年的样子)而且代码太多(至少对我来说)。

    这是我的解决方案:

    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    
    ...
    
    [JsonConverter(typeof(Vec2DictionaryConverter))]
    public Dictionary<Vector2Int, Collection> cardLocations;
    
    ...
    
    public class DictionaryConverter : JsonConverter<Dictionary<Vector2Int, Collection>> {
    
        private static readonly string[] SEPARATORS = new[] { "(", ", ", ")" };
    
        public override Dictionary<Vector2Int, Value> ReadJson(JsonReader reader, Type objectType,
                Dictionary<Vector2Int, Collection> existingValue, bool hasExistingValue, JsonSerializer serializer) {
            if (JsonToken.Null == reader.TokenType) {
                return null;
            }
            var dictionary = new Dictionary<Vector2Int, Collection>();
            foreach (var pair in JObject.Load(reader)) {
                var vector = pair.Key.Split(SEPARATORS, StringSplitOptions.RemoveEmptyEntries)
                    .Select(it => Convert.ToInt32(it));
                var key = new Vector2Int(vector.First(), vector.Last());
                var value = pair.Value.ToObject<Collection>(serializer);
                dictionary.Add(key, value);
            }
            return dictionary;
        }
    
        public override void WriteJson(JsonWriter writer,
                Dictionary<Vector2Int, Collection> value, JsonSerializer serializer) {
            serializer.Serialize(writer, value);
        }
    }
    
    1. 使用JsonConverter 属性而不是一直将Converter 传递给SerializeObject/DeserializeObject
    2. 使用了泛型JsonConverter&lt;Dictionary&lt;Vector2Int, Collection&gt;&gt;,因此不再需要实现CanConvert
    3. 仅实现 ReadJson,因为 OP 没有序列化问题

    虽然我们依赖Vector2Int.ToString 实现,但这工作得很好而且很花哨,但我发现Unity 的JsonUtility 序列化至少在我的测试中更快。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-15
      • 1970-01-01
      • 2012-03-11
      • 2022-08-12
      • 1970-01-01
      • 2015-12-22
      • 2015-11-06
      • 1970-01-01
      相关资源
      最近更新 更多