如何重置JsonReader实例而不是调用JToken.CreateReader? [英] How can I reset a JsonReader instance instead of calling JToken.CreateReader?

查看:61
本文介绍了如何重置JsonReader实例而不是调用JToken.CreateReader?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个很大的JSON文件,正在使用Newtonsoft.Json反序列化-尤其是我遇到了性能问题.关于一个具体细节:

I have a large JSON file that I'm deserializing using Newtonsoft.Json - and I'm experiencing performance issues, esp. regarding one specific detail:

反序列化时,我需要使用IOC(控制反转")容器来创建所需的实例.

While deserializing I need to use an IOC ("Inversion Of Control") Container to create the required instances.

灵感来自 https://skrift.io/issues/bulletproof- interface-deserialization-in-jsonnet/和其他资源我实现了一个覆盖ReadJson的自定义JsonConverter.方法签名包括Type objectType,但例如对于数组,这很可能只是一些通用接口,而不是特定的类类型.

Inspired by https://skrift.io/issues/bulletproof-interface-deserialization-in-jsonnet/ and other sources I implemented a custom JsonConverter that overrides ReadJson. The method signature includes Type objectType, but e.g. in the case of arrays, this may well only be some common interface and not a specific class type.

我使用选项TypeNameHandling.Objects序列化了数据,该选项包含每个类的"$type"令牌.

I serialized the data using the option TypeNameHandling.Objects, which includes a "$type" token for each class.

如果检测到类型不足,请执行以下操作:

If I detect that the type is not sufficient, I do something like the following:

JObject o = JObject.Load(reader);

var typeName = o["$type"].ToString();

if (!tryGetBindingByName(typeName, out var detectedBinding))
    throw new SpecificBindingNotFoundBinderException(typeName, binding);

result = detectedBinding.DeserializeType;

//"reset" reader, because due to `JObject.Load(reader)` call above the reader's current position has changed
reader = o.CreateReader();

我认为性能至关重要的部分-尤其是.关于堆分配-是我手动读取JSON对象的位置-然后需要调用reader = o.CreateReader().

I think the performance critical part - esp. regarding heap allocations - is where I manually read the JSON object - and then the need to call reader = o.CreateReader().

我的问题:现有的JTokenReader是否可以重置"?这样我就可以继续使用它,而不是一直创建一个新实例?

My question: can an existing JTokenReader be "reset" so that I can continue using it, instead of creating a new instance all the time?

推荐答案

. JTokenReader 或抽象的

No. Neither JTokenReader, nor the abstract JsonReader on which it is based, provide a means to "reset" or go backward to an earlier point. They are forward-only readers by design.

但是,听起来您真正要解决的问题只是让Json.Net使用IOC容器以高效的方式创建类的具体实例.为此,我认为您可能使用了错误的工具来完成这项工作.代替使用JsonConverter,请尝试使用自定义的 ContractResolver .您可以提供自己的DefaultCreator函数来处理每种类型的实例化,如下所示.

However, it sounds like the problem you are really trying to solve is just getting Json.Net to use your IOC container to create concrete instances of your classes in a performant manner. For that, I think you might be using the wrong tool for the job. Instead of a JsonConverter, try using a custom ContractResolver. You can supply your own DefaultCreator function to handle the instantiation for each Type, as shown below.

class CustomResolver : DefaultContractResolver
{
    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        JsonObjectContract contract = base.CreateObjectContract(objectType);
        contract.DefaultCreator = () =>
        {
            // Change this to use your IOC container to create the instance.
            var instance = Activator.CreateInstance(objectType);

            return instance;
        };
        return contract;
    }
}

要使用解析器,只需将其添加到已使用的TypeNameHandling.Objects选项旁边的JsonSerializerSettings中即可.

To use the resolver, you just need to add it to the JsonSerializerSettings alongside the TypeNameHandling.Objects option you are already using:

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Objects,
    ContractResolver = new CustomResolver(),
};

这是一个往返演示: https://dotnetfiddle.net/eHcLMb

如果您还需要自定义写入JSON的类型名称,则可以实现自定义 SerializationBinder 来完成这一部分.

If you also need to customize the type names that are written into the JSON, you can implement a custom SerializationBinder to do that part.

这是一个人为设计的例子来说明这个概念.下面的CustomSerializationBinder使用一些硬编码词典将某些已知类型映射到别名",这只是我选择代表该类型的唯一字符串.所述已知类型"指的是已知类型".这里的内容与"Json.Net中的防弹接口反序列化"相同.您在问题中链接到的文章.您可以使用所需的任何类型查找机制来替换字典.请注意,在下面的示例中,我并没有使用assemblyName只是为了保持JSON的简单性.

Here is a contrived example to demonstrate the concept. The CustomSerializationBinder below uses some hardcoded dictionaries to map some known types to an "alias", which is just a unique string I chose to represent that type. The "known types" here are the same ones from the "Bulletproof Interface Deserialization in Json.Net" article you linked to in your question. You can replace the dictionaries with whatever type lookup mechanism you require. Note that I'm not using the assemblyName in the example below just to keep the JSON simple.

class CustomSerializationBinder : ISerializationBinder
{
    Dictionary<string, Type> AliasToTypeMapping { get; set; }
    Dictionary<Type, string> TypeToAliasMapping { get; set; }

    public CustomSerializationBinder()
    {
        TypeToAliasMapping = new Dictionary<Type, string>
        {
            { typeof(Person), "Peep" },
            { typeof(Programming), "Coder" },
            { typeof(Writing), "Wordsmith" }
        };

        AliasToTypeMapping = new Dictionary<string, Type>(
            TypeToAliasMapping.Select(kvp => new KeyValuePair<string, Type>(kvp.Value, kvp.Key))
        );
    }

    public void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
        if (TypeToAliasMapping.TryGetValue(serializedType, out string alias))
        {
            assemblyName = null;  // I don't care about the assembly name for this example
            typeName = alias;
            return;
        };
        throw new Exception($"Type {serializedType.Name} is not mapped to an alias");
    }

    public Type BindToType(string assemblyName, string typeName)
    {
        if (AliasToTypeMapping.TryGetValue(typeName, out Type type))
        {
            return type;
        }
        throw new Exception("No type was found matching the alias {typeName}");
    }
}

要使用自定义活页夹,您已经猜到了,只需将其与其他内容一起添加到设置中即可:

To use the custom binder, you guessed it, just add it to the settings with the other stuff:

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Objects,
    ContractResolver = new CustomResolver(),
    SerializationBinder = new CustomSerializationBinder()
};

以下是与活页夹相同的往返演示: https://dotnetfiddle.net/YC9IAT

Here is the same round-trip demo with the binder: https://dotnetfiddle.net/YC9IAT

这篇关于如何重置JsonReader实例而不是调用JToken.CreateReader?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆