使用自定义转换器在 Newtonsoft.Json 中进行类型解析

问题描述 投票:0回答:1

我在尝试在 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>();
        }
    }
}
c# unity-game-engine generics types json.net
1个回答
0
投票

当您为给定类型编写自己的

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
    验证传入类型。

    有关详细信息,请参阅 Newtonsoft Json 中的

    TypeNameHandling 注意事项

    由于 Json.Net TypeNameHandling auto 而导致外部 json 易受攻击?

    模型小提琴
  • 这里

© www.soinside.com 2019 - 2024. All rights reserved.