Json.Net - 基于接口的数据结构的高性能反序列化?

时间:2017-07-03 09:02:33

标签: c# json interface tree json.net

一旦我在数据结构中没有具体类型我将一个巨大的json树结构反序列化,它开始使用大量的内存,但它的内存占用率保持相对较小反序列化为完全具体的类型......有一个优雅的解决方法吗?

我得到的json是在其他地方生成的,所以我对其得到的格式没有影响(它是一个树结构,类似于下面的代码示例,如果它直接序列化为json),并且在最坏的情况下大概250-300MB吧。 我用于映射它的数据结构看起来有点像下面的例子(虽然在某些地方有结构)

public class Node : INode
{
    [JsonConverter(typeof(NodeTypeConverter<IInnerNode, InnerNodeType1>))]
    public List<INodeInner> InnerNodes { get; set; }
}

public class InnerNodeNodeType1 : INode
{
    [JsonConverter(typeof(NodeTypeConverter<IInnerNode, InnerNodeType2>))]
    public List<INodeInner> InnerNodes { get; set; }

    // some other properties
}

public class InnerNodeNodeType2 : INode
{
    [JsonConverter(typeof(NodeTypeConverter<IInnerNode, InnerNodeType3>))]
    public List<INodeInner> InnerNodes { get; set; }

    // some even different properties
}

…

然而,我没有找到一种方法来映射这个,而不会让它运行的PC瘫痪,特别是记忆(除此之外,在List<interface>的一些地方,我甚至没有得到json .Net使用转换器,它甚至在检查转换器类之前就抛出了错误Could not create an instance of type {type}. Type is an interface or abstract class and cannot be instantiated.。)。

所以现在,我将它更改为具体类型/具体类型实例列表而不是接口和转换器,并且它运行的内存占用量少(数量级!)。但它不够优雅,因为这样,我不能将大多数类重用于不同类型的树,我将不得不在程序的其他地方使用它们,这些树类似,但略有不同。

对此有优雅的解决方案吗?

PS:感谢您阅读这篇文章!这个问题可能没有完美提出和/或包含建议解决方案可能需要的所有和任何类型的信息。然而,我发现,无论何时我试图涵盖所有基础并预测所有进一步的问题,我都没有得到的答案,所以这次是我尝试不同的问题...:P

1 个答案:

答案 0 :(得分:0)

您没有提供问题的具体示例,但您确实写了我将其更改为具体类型/具体类型实例列表而不是接口和转换器,并且它运行时更少的内存占用(数量级!)。听起来好像你必须在一些中间表示中将大块JSON加载到内存中,例如整个JSON的stringJArray数组的完整内容public List<INodeInner> InnerNodes 。之后,您将中间表示转换为最终对象。

您需要做的是避免加载任何中间表示,或者如果必须这样做,请一次只加载尽可能小的JSON块。以下是一个示例实现:

public interface INode
{
    List<INodeInner> InnerNodes { get; set; }
}

public interface INodeInner : INode
{
}

public class Node : INode
{
    [JsonProperty(ItemConverterType = typeof(InterfaceToConcreteConverter<INodeInner, InnerNodeNodeType1>))]
    public List<INodeInner> InnerNodes { get; set; }
}

public class InnerNodeNodeType1 : INodeInner
{
    [JsonProperty(ItemConverterType = typeof(InterfaceToConcreteConverter<INodeInner, InnerNodeNodeType2>))]
    public List<INodeInner> InnerNodes { get; set; }

    // some other properties
    public int Type1Property { get; set; }
}

public class InnerNodeNodeType2 : INodeInner
{
    [JsonProperty(ItemConverterType = typeof(InterfaceToConcreteConverter<INodeInner, InnerNodeNodeType3>))]
    public List<INodeInner> InnerNodes { get; set; }

    // some even different properties
    public int Type2Property { get; set; }
}

public class InnerNodeNodeType3 : INodeInner
{
    [JsonProperty(ItemConverterType = typeof(InterfaceToConcreteConverter<INodeInner, InnerNodeNodeType3>))]
    public List<INodeInner> InnerNodes { get; set; }

    // some even different properties
    public int Type3Property { get; set; }
}

public class InterfaceToConcreteConverter<TInterface, TConcrete> : JsonConverter where TConcrete : TInterface
{
    public InterfaceToConcreteConverter()
    {
        // TConcrete should be a subtype of an abstract type, or an implementation of an interface.  If they
        // are identical an infinite recursion could result, so throw an exception.
        if (typeof(TInterface) == typeof(TConcrete))
            throw new InvalidOperationException(string.Format("typeof({0}) == typeof({1})", typeof(TInterface), typeof(TConcrete)));
    }

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return serializer.Deserialize(reader, typeof(TConcrete));
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

然后,加载:

Node root;
var settings = new JsonSerializerSettings
{
    // Whatever settings you need.
};
using (var stream = File.OpenRead(fileName))
using (var textReader = new StreamReader(stream))
using (var reader = new JsonTextReader(textReader))
{
    root = JsonSerializer.CreateDefault(settings).Deserialize<Node>(reader);
}

注意:

  1. 我没有为整个List<INodeInner> InnerNodes编写转换器并将其应用于[JsonConverter(typeof(NodeTypeConverter<IInnerNode, InnerNodeType1>))],而是为每个列表项创建了一个转换器,并通过设置JsonPropertyAttribute.ItemConverterType来应用它:

    [JsonProperty(ItemConverterType = typeof(InterfaceToConcreteConverter<INodeInner, InnerNodeNodeType1>))]
    public List<INodeInner> InnerNodes { get; set; }
    

    因此简化了转换器并保证,如果转换器需要将JSON预加载到中间JToken,则只预加载一个列表项并立即转换。

  2. 因为在您的示例中,INodeInner的类型对于每种类型的INode都是固定的,所以甚至不需要预加载单个列表项。相反,在JsonConverter.ReadJson()中,使用正确的具体类型直接从传入的JsonReader反序列化:

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return serializer.Deserialize(reader, typeof(TConcrete));
    }
    
  3. Newtonsoft Performance Tips: Optimize Memory Usage中所述,在反序列化大型JSON文件时,直接从流中反序列化:

    using (var stream = File.OpenRead(fileName))
    using (var textReader = new StreamReader(stream))
    using (var reader = new JsonTextReader(textReader))
    {
        root = JsonSerializer.CreateDefault(settings).Deserialize<Node>(reader);
    }
    
  4. 示例fiddle显示此工作。