【问题标题】:Caching/interning object keys when deserialising JSON with Newtonsoft使用 Newtonsoft 反序列化 JSON 时缓存/暂存对象键
【发布时间】:2019-08-07 14:36:03
【问题描述】:

我有一个 API 方法可以将大约 100k 行从数据库加载到内存中,并且这些行中的每一行都包含一个 JSON 字符串。对于每个请求,JSON 对象结构在所有行中都是相同的(相同的对象键),尽管我不会提前知道这一点。

目前我正在为每一行调用JObject.Parse(row.Json) 以获取一个JObject。当我检查堆时,我可以看到每个对象键字符串的重复条目。因此,如果我在每一行的 JSON 中有对象键 id 并且我有 100k 行,我会在内存中看到该字符串的 100k 个实例。

我想缓存(或可能String.Intern(),取决于生命周期)这些对象键并重用这些JObjects 中的字符串。我可以看到使用 JsonConvert.DeserializeObject() 我可以提供一个自定义转换器,但是 AFAIK 他们让你修改 JSON 值而不是键。

注意:我必须一次在内存中保存所有 100k 行,因为我稍后会运行一个需要一次所有内容的算法。

【问题讨论】:

  • 听起来你需要一个不同的内存结构;从您的“对象”列表转换为一组数组...您可以解析第一行的键数组,然后可以将所有行解析为 JSON 数组,[key1,key2,key3][ [r1c1val],[r1c2val],[r1c3val], [r2c1val] ...
  • 相关:Newtonsoft json.net JsonTextReader Garbage Collector intensiveAutomaticJsonNameTable 来自 this answer 可能是你想要的。

标签: c# json string json.net deserialization


【解决方案1】:

如果您知道 JSON 的结构,您总是可以创建一个包含最常见字段的类。这样会节省不少空间。

class RowData
{
    [JsonProperty("id")]
    public int Id { get; set; }
    [JsonProperty("anyOtherFixedField")]
    public string OtherField{ get; set; }
    [JsonExtensionData]
    public IDictionary<string, JToken> ExtraProperties {get; set;}
}

具有属性的字段在堆上根本不会有字符串。

JSON 中没有相应属性的任何字段都将进入ExtraProperties 字典。

【讨论】:

  • 遗憾的是,请求之间没有这样的公共字段,只有在单个请求中对象看起来相同,但在编译时我不知道它们的结构。
【解决方案2】:

似乎没有一个很好的方法来挂钩到默认的 JObject 反序列化。

我根据提供的ExpandoObjectConverter 制作了一个自定义转换器,它创建一个JValue/JObject/JArray 而不是ExpandoObject。所有对象键都被缓存并在转换器的生命周期内重复使用。

要使用此转换器,您必须指定要反序列化为 JTokenJObjectJArray。 如果不指定目标类型,则不会使用此转换器。

var data = JsonConvert.DeserializeObject<JToken>(json, new NameCachingJObjectConverter());

以及实现。

public class NameCachingJObjectConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // can write is set to false
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return ReadValue(reader);
    }

    private JToken ReadValue(JsonReader reader)
    {
        if (!MoveToContent(reader))
        {
            throw new Exception("Unexpected end of content");
        }

        switch (reader.TokenType)
        {
            case JsonToken.StartObject:
                return ReadObject(reader);
            case JsonToken.StartArray:
                return ReadList(reader);
            default:
                if (IsPrimitiveToken(reader.TokenType))
                {
                    return new JValue(reader.Value);
                }

                throw new Exception("Unexpected token when converting object: {reader.TokenType}");
        }
    }

    private static bool IsPrimitiveToken(JsonToken token)
    {
        switch (token)
        {
            case JsonToken.Integer:
            case JsonToken.Float:
            case JsonToken.String:
            case JsonToken.Boolean:
            case JsonToken.Undefined:
            case JsonToken.Null:
            case JsonToken.Date:
            case JsonToken.Bytes:
                return true;
            default:
                return false;
        }
    }

    private static bool MoveToContent(JsonReader reader)
    {
        JsonToken t = reader.TokenType;
        while (t == JsonToken.None || t == JsonToken.Comment)
        {
            if (!reader.Read())
            {
                return false;
            }
            t = reader.TokenType;
        }
        return true;
    }

    private JArray ReadList(JsonReader reader)
    {
        var list = new JArray();

        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.Comment:
                    break;
                default:
                    object v = ReadValue(reader);

                    list.Add(v);
                    break;
                case JsonToken.EndArray:
                    return list;
            }
        }

        throw new Exception("Unexpected end when reading JObject.");
    }

    private JToken ReadObject(JsonReader reader)
    {
        var expandoObject = new JObject();

        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.PropertyName:
                    string propertyName = GetCachedName(reader.Value.ToString());

                    if (!reader.Read())
                    {
                        throw new Exception("Unexpected end when reading JObject.");
                    }

                    var v = ReadValue(reader);

                    expandoObject[propertyName] = v;
                    break;
                case JsonToken.Comment:
                    break;
                case JsonToken.EndObject:
                    return expandoObject;
            }
        }

        throw new Exception("Unexpected end when reading ExpandoObject.");
    }

    /// <summary>
    /// Determines whether this instance can convert the specified object type.
    /// </summary>
    /// <param name="objectType">Type of the object.</param>
    /// <returns>
    ///     <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
    /// </returns>
    public override bool CanConvert(Type objectType)
    {
        return (typeof(JToken).IsAssignableFrom(objectType));
    }

    /// <summary>
    /// Gets a value indicating whether this <see cref="JsonConverter"/> can write JSON.
    /// </summary>
    /// <value>
    ///     <c>true</c> if this <see cref="JsonConverter"/> can write JSON; otherwise, <c>false</c>.
    /// </value>
    public override bool CanWrite => false;



    private string GetCachedName(string value)
    {
        string ret;
        if (!cache.TryGetValue(value, out ret))
        {
            cache[value] = value;
            ret = value;
        }
        return ret;
    }
    private readonly Dictionary<string, string> cache = new Dictionary<string, string>();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-25
    • 1970-01-01
    • 1970-01-01
    • 2018-09-18
    • 1970-01-01
    相关资源
    最近更新 更多