我有一个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
。所有对象键都会被缓存,并在转换器的生存期内重复使用。
要使用此转换器,您必须指定要反序列化为JToken
、JObject
或JArray
。 如果未指定目标类型,则不会使用此转换器。
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>();
}