Unity中关于Json的那些事(二)

前言

        此前我已经写过一篇关于在Unity中使用Json的文章。这篇文章是我在此之后写项目中遇到的关于Json的问题。

环境

        Newtonsoft Json 3.2.1 Unity 2022.3.8f1c1

使用Newtonsoft转Json排除全部属性

        最近做项目遇到了一个这样的问题,我要将一个类序列化为json发给服务器。但是这个类中存在一个属性,如下面示例的类一样:

1
2
3
4
5
6
7
8
9
10
11
12
private class JsonProblemSample
{
[JsonRequired]
private int data;
public int Data { get { return data; } }

// 这里做示例才额外用一个函数去做赋值,实际项目中它的赋值不会这么简单
public void SetData(int data)
{
this.data = data;
}
}

执行序列化后,你会得到如下的结果

1
{"data":1,"Data":1}

从上面的Json中,我们可以很明显的发现类中属性也被转换为了Json。我并不想这个属性会被序列化出来,且因为这个类也需要反序列化,所以data必须要使用[JsonRequired]进行修饰。因此我只能选择修改隐藏属性的序列化。

        这个问题有很多解决的办法,比如给属性添加JsonIgnore标志;或者你也可以自己写一个ContractResolver去满足你的需求,比如类似这篇博客园文章中的办法。下面我也给出我自己的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Reflection;

namespace JsonTest
{
public class IgnoreAllProperty : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty res = base.CreateProperty(member, memberSerialization);
if (member.MemberType == MemberTypes.Property)
{
res.Ignored = true;
}
return res;
}
}
}

这里你或许有疑问,为什么我不直接使用new JsonProperty()然后设置Ignored。这样还省下了一部分性能。这是因为我直接这样做会报下面的错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ArgumentNullException: Value cannot be null.
Parameter name: key
Newtonsoft.Json.DefaultJsonNameTable.Add (System.String key) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.DefaultContractResolver.CreateProperties (System.Type type, Newtonsoft.Json.MemberSerialization memberSerialization) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.DefaultContractResolver.CreateObjectContract (System.Type objectType) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.DefaultContractResolver.CreateContract (System.Type objectType) (at <761cf2a144514d2291a678c334d49e9b>:0)
System.Collections.Concurrent.ConcurrentDictionary`2[TKey,TValue].GetOrAdd (TKey key, System.Func`2[T,TResult] valueFactory) (at <b89873cb176e44a995a4781c7487d410>:0)
Newtonsoft.Json.Utilities.ThreadSafeStore`2[TKey,TValue].Get (TKey key) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.DefaultContractResolver.ResolveContract (System.Type type) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.GetContract (System.Object value) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.GetContractSafe (System.Object value) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize (Newtonsoft.Json.JsonWriter jsonWriter, System.Object value, System.Type objectType) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.JsonSerializer.SerializeInternal (Newtonsoft.Json.JsonWriter jsonWriter, System.Object value, System.Type objectType) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.JsonSerializer.Serialize (Newtonsoft.Json.JsonWriter jsonWriter, System.Object value, System.Type objectType) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.JsonConvert.SerializeObjectInternal (System.Object value, System.Type type, Newtonsoft.Json.JsonSerializer jsonSerializer) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.JsonConvert.SerializeObject (System.Object value, System.Type type, Newtonsoft.Json.JsonSerializerSettings settings) (at <761cf2a144514d2291a678c334d49e9b>:0)
Newtonsoft.Json.JsonConvert.SerializeObject (System.Object value, Newtonsoft.Json.JsonSerializerSettings settings) (at <761cf2a144514d2291a678c334d49e9b>:0)
JsonTest.DoubleDataProblem.Start () (at Assets/Scripts/Json/Newtonsoft/DoubleDataProblem.cs:27)

使用的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using Newtonsoft.Json;
using System.Collections.Generic;
using UnityEngine;

namespace JsonTest
{
public class DoubleDataProblem : MonoBehaviour
{
private class JsonProblemSample
{
[JsonRequired]
private int data;
public int Data { get { return data; } }

public void SetData(int data)
{
this.data = data;
}
}

void Start()
{
var sample = new JsonProblemSample();
sample.SetData(123);
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new IgnoreAllProperty();
var jsonInfo = JsonConvert.SerializeObject(sample, settings);
Debug.Log(jsonInfo);
}
}
}

变量名映射

         你或许有这样的需求,如下面json的例子:

1
2
{"userName": "张三"}
{"playerName": "李四"}

这其实都是一个表面用户名的json,但是这两个json用了不一样的命名表达。而我们写类时要是像下面这样去做,就会让其他看到这个代码的人显得迷惑。

1
2
3
4
5
public class UserInfo
{
public string userName;
public string playerName;
}

如果这时候又出现了一个json如下所示:

1
{"name": "张三"}

那我们又要再加变量。这显然有些滑稽了。我就遇到了,不然我没事想这个干什么。之前后端接平台的时候没考虑到前面已经接好的平台导致了不同平台间相同的信息用不同的变量名。我对接的时候刚好要用到这部分信息,因此我发现了。但是服务端这边的逻辑牵扯太多改不了,所以只能麻烦我们客户端来做处理。幸好我在Newtonsoft Json看到一个官方示例,这个示例刚好能用较为轻便的方法解决这个问题。我们将刚刚的UserInfo改为如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class UserInfo
{
public string playerName;

[JsonExtensionData]
private IDictionary<string, JToken> otherData = new Dictionary<string, JToken>();

[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
if (otherData.ContainsKey("userName"))
this.playerName = (string)otherData["userName"];

if (otherData.ContainsKey("name"))
this.playerName = (string)otherData["name"];

otherData.Clear();
}
}

这样如果还出现类似的情况也就是增加一个if,当然最好还是减少类似情况的发生。

关于ScriptableObject的Json转出

        在对ScriptableObject进行Json转换的时候,最好使用Unity本身提供的Json转换。当你使用Newtonsoft进行Json转换的时候,Unity会直接报错。我想这是因为ScriptableObject本身就很特殊的原因吧。

碎碎念

        本来我想多攒一些然后再发布文章,可是我发现项目中对于Json的用法大概率不会增加了。我快一年没有再写新增的Json用法。所以这篇文章也就先这样,如果后续我有什么新的发现,我会再更新。