我在尝试在 Unity 中反序列化 ScriptableObjects 时遇到问题。我正在使用 Newtonsoft.Json 库,因为我想解析类型,但这似乎与使用自定义转换器发生冲突。如果不使用
CustomCreationConverter
,代码可以正确解析类型,但是 Unity 抱怨使用 new() 创建 ScriptableObject。当我添加转换器时,所有内容都被转换为基类,而不是适当的子类。
如何确保即使在使用转换器时也能解析类型?
我的加载器的代码示例:
public abstract class Database<T> : ScriptableObject where T : ScriptableObject {
public T LoadEntry(string path)
{
JsonSerializerSettings settings =
new()
{
TypeNameHandling = TypeNameHandling.Auto,
};
settings.Converters.Add(new ScriptableObjectConverter());
string fileContent = File.ReadAllText(path);
T entry = JsonConvert.DeserializeObject<T>(fileContent, settings);
return entry;
}
private class ScriptableObjectConverter : CustomCreationConverter<T>
{
public override T Create(Type objectType)
{
Debug.Log(objectType);
return CreateInstance<T>();
}
}
}
JsonConverter
时,您必须自己处理一切,包括读取和写入元数据属性,例如"$type"
和"$id"
。 这有点麻烦,但肯定是可能的,请参阅 在 Json.net 中使用自定义 JsonConverter 和 TypeNameHandling 作为示例。
ScriptableObject.CreateInstance()
的调用替换默认构造函数,因此有一种更简单的方法:您可以创建一个覆盖 DefaultContractResolver.CreateObjectContract()
的自定义合约解析器并设置
JsonContract.DefaultCreator
。 为此,请创建以下合约解析器:
class ScriptableObjectContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
if (typeof(ScriptableObject).IsAssignableFrom(objectType) && !objectType.IsAbstract)
{
contract.DefaultCreator = () => ScriptableObject.CreateInstance(objectType);
}
return contract;
}
}
然后使用它,例如如下:
//Newtonsoft recommends caching resolvers statically for best performance, see https://www.newtonsoft.com/json/help/html/Performance.htm#ReuseContractResolver
static readonly ScriptableObjectContractResolver resolver = new();
public T LoadEntry(string path)
{
JsonSerializerSettings settings =
new()
{
TypeNameHandling = TypeNameHandling.Auto,
ContractResolver = resolver,
};
using (var textReader = new StreamReader(path, new UTF8Encoding(false)))
using (var jsonReader = new JsonTextReader(textReader))
{
return JsonSerializer.CreateDefault(settings).Deserialize<T>(jsonReader); // Add some check for a returned null value here?
}
}
public void SaveEntry(string path, T entry)
{
JsonSerializerSettings settings =
new()
{
// Note that TypeNameHandling.Auto is never respected for the ROOT JSON object, for confirmation see
// https://stackoverflow.com/questions/38859074/why-does-json-net-not-include-type-for-the-root-object-when-typenamehandling-is
// So you need to use a different TypeNameHandling value or follow the workaround from
// https://stackoverflow.com/questions/36356336/json-net-how-to-add-property-type-only-on-root-object
TypeNameHandling = TypeNameHandling.Objects,
ContractResolver = resolver,
};
using (var textWriter = new StreamWriter(path, false, new UTF8Encoding(false)))
using (var jsonWriter = new JsonTextWriter(textWriter))
{
JsonSerializer.CreateDefault(settings).Serialize(jsonWriter, entry);
}
}
备注:
"$type"
时,Json.NET 不会序列化
root 对象的
TypeNameHandling.Auto
属性。 有关原因,请参阅当 TypeNameHandling 为 Auto 时,为什么 Json.NET 不包含根对象的 $type?。 如果您需要根对象的类型信息,则必须使用不同的设置,或者使用 json.net 的解决方法 - 如何仅在根对象上添加属性 $type 。
FileStream
序列化比从
string
序列化然后写入和读取字符串更高效。
TypeNameHandling
存在安全风险。正如
docs中所解释的: 当您的应用程序从外部源反序列化 JSON 时,应谨慎使用 TypeNameHandling。使用 None 以外的值进行反序列化时,应使用自定义 SerializationBinder
验证传入类型。TypeNameHandling 注意事项模型小提琴