使用 Newtonsoft 反序列化 JSON 时缓存/暂留对象键



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

目前,我正在打电话给JObject.Parse(row.Json),要求每一行都得到一个JObject。当我检查堆时,我可以看到每个对象键字符串都有一个重复的条目。因此,如果我在每行的 JSON 中id对象键,并且有 100k 行,我会在内存中看到此字符串的 100k 个实例。

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

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

如果您知道 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字典。

似乎没有一种好方法可以挂钩到默认的 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>();
}

最新更新