serial ontern on serializer.Deserialize <t>()

时间:2016-01-20 15:27:27

标签: c# json json.net string-interning

我目前正在使用json.net反序列化一个中等大小的对象集合的字符串。共计7000件。

每个项目都有一个由4个相同字符串组成的重复组,在内存分析中,根据嵌套等创建大约40,000个引用。

有没有办法让序列化器为每个相同的字符串使用相同的引用?

示例Json:

  [{
    "name":"jon bones",
    "groups":[{
        "groupName":"Region",
        "code":"1"
    },{
        "groupName":"Class",
        "code":"4"
    }]
},
{
    "name":"Swan moans",
    "groups":[{
        "groupName":"Region",
        "code":"12"
    },{
        "groupName":"Class",
        "code":"1"
    }]
}]

添加了示例。正如您所见,groupName值几乎在所有对象上重复。只是相关的代码改变了。这不是一个很大的问题,但随着数据集的增长,我宁愿不要过多地增加分配。

似乎“代码”可能会重复,但它对每个人来说都是独一无二的。基本上是同一对象的多个标识符。

2 个答案:

答案 0 :(得分:5)

如果你事先知道你的4个标准字符串,你可以使用String.Intern()实习它们(或者只是将它们声明为字符串文字 - 完成工作)然后使用以下custom JsonConverter进行转换如果找到一个JSON字符串文字到其实习值:

public class InternedStringConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(string);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var s = reader.TokenType == JsonToken.String ? (string)reader.Value : (string)JToken.Load(reader); // Check is in case the value is a non-string literal such as an integer.
        return String.IsInterned(s) ?? s;
    }

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

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

这可以通过序列化设置全局应用:

        var settings = new JsonSerializerSettings { Converters = new [] { new InternedStringConverter() } };
        var root = JsonConvert.DeserializeObject<RootObject>(jsonString, settings);

您还可以使用JsonPropertyAttribute.ItemConverterType将其应用于特定的字符串集合:

public class Group
{
    [JsonProperty(ItemConverterType = typeof(InternedStringConverter))]
    public List<string> StandardStrings { get; set; }
}

如果你事先不知道4个字符串,你可以创建一个转换器,在读取字符串时对其进行内插:

public class AutoInterningStringConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        // CanConvert is not called when a converter is applied directly to a property.
        throw new NotImplementedException("AutoInterningStringConverter should not be used globally");
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var s = reader.TokenType == JsonToken.String ? (string)reader.Value : (string)JToken.Load(reader); // Check is in case the value is a non-string literal such as an integer.
        return String.Intern(s);
    }

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

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

但是,我强烈建议不要全局使用它,因为最终可能会在内部字符串表中添加大量字符串。相反,将它应用于您确信包含少量唯一字符串重复的特定字符串集合:

public class Group
{
    [JsonProperty(ItemConverterType = typeof(AutoInterningStringConverter))]
    public List<string> StandardStrings { get; set; }
}

<强>更新

根据您更新的问题,我看到您的字符串属性具有标准值,而不是具有标准值的字符串集合。因此,您将在每个上使用[JsonConverter(typeof(AutoInterningStringConverter))]

public class Group
{
    [JsonConverter(typeof(AutoInterningStringConverter))]
    public string groupName { get; set; }

    public string code { get; set; }
}

答案 1 :(得分:0)

正如其他答案中所指出的,由于该分配的生命周期,您需要非常小心使用String.Intern。对于一小组经常使用的字符串,这可能是合适的。

对于我们的场景,我选择遵循.Net中XML Serializers的模式。它们使用类调用“System.Xml.NameTable”来解析XML文档中唯一出现的字符串。我遵循上面'dbc'提供的实现模式,但使用了NameTable而不是String.Intern

public class JsonNameTable
    : System.Xml.NameTable
{
}

public class JsonNameTableConverter
    : JsonConverter
{
    private JsonNameTable _nameTable;
    public JsonNameTableConverter(JsonNameTable nameTable)
    {
        _nameTable = nameTable;
    }

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var s = reader.TokenType == JsonToken.String ? (string)reader.Value : (string)Newtonsoft.Json.Linq.JToken.Load(reader); // Check is in case the value is a non-string literal such as an integer.
        if (s != null)
        {
            s = _nameTable.Add(s);
        }
        return s;
    }

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

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

然后在使用代码中,将转换器设置为Json设置

JsonNameTable nameTable = new JsonNameTable();
settings.Converters.Add(new JsonNameTableConverter(nameTable));

这允许您共享字符串,并通过引用JsonNameTable来控制字符串的生命周期。

这里可能有一些改进:NameTable实际上会返回一个给定char [],开始和结束索引的现有字符串。有可能将nameTable进一步降低到从流中读取字符串的位置,从而绕过任何重复字符串的创建。但是,我无法弄清楚如何在Json.Net中做到这一点