在处理大型 JSON 对象时,重要的是不要在最终反序列化之前将整个 JSON 流加载到中间表示中。因此:
-
不要将 JSON 作为字符串下载。来自Performance Tips:
为了最小化内存使用和分配的对象数量,Json.NET 支持直接对流进行序列化和反序列化。一次读取或写入 JSON,而不是将整个 JSON 字符串加载到内存中,这在处理大小超过 85kb 的 JSON 文档以避免 JSON 字符串最终进入大对象堆时尤为重要。
相反,Newtonsoft 建议直接从响应流中反序列化,例如:
HttpClient client = new HttpClient();
using (Stream s = client.GetStreamAsync("http://www.test.com/large.json").Result)
using (StreamReader sr = new StreamReader(s))
using (JsonReader reader = new JsonTextReader(sr))
{
JsonSerializer serializer = new JsonSerializer();
// read the json from a stream
// json size doesn't matter because only a small piece is read at a time from the HTTP request
RootObject root = serializer.Deserialize<RootObject>(reader);
}
不要仅仅为了反序列化 "result" 值而将整个 JSON 加载到 JArray 中。而是使用JsonTextReader 流式传输JSON,直到找到名为"result" 的属性,然后反序列化其值,如JSON.NET deserialize a specific property 所示。
-
要自动将所有非集合值对象属性映射到单项数组,您可以创建一个custom IContractResolver,将适当的custom JsonConverter 应用于适当类型的属性。
将所有这些放在一起,您需要以下扩展方法和合约解析器:
public static class JsonExtensions
{
public static IEnumerable<T> DeserializeNamedProperties<T>(Stream stream, string propertyName, JsonSerializerSettings settings = null, int? depth = null)
{
using (var textReader = new StreamReader(stream))
foreach (var value in DeserializeNamedProperties<T>(textReader, propertyName, settings, depth))
yield return value;
}
public static IEnumerable<T> DeserializeNamedProperties<T>(TextReader textReader, string propertyName, JsonSerializerSettings settings = null, int? depth = null)
{
var serializer = JsonSerializer.CreateDefault(settings);
using (var jsonReader = new JsonTextReader(textReader))
{
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonToken.PropertyName
&& (string)jsonReader.Value == propertyName
&& depth == null || depth == jsonReader.Depth)
{
jsonReader.Read();
yield return serializer.Deserialize<T>(jsonReader);
}
}
}
}
}
public class ArrayToSingleContractResolver : DefaultContractResolver
{
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
static ArrayToSingleContractResolver instance;
static ArrayToSingleContractResolver() { instance = new ArrayToSingleContractResolver(); }
public static ArrayToSingleContractResolver Instance { get { return instance; } }
readonly SimplePropertyArrayToSingleConverter simpleConverter = new SimplePropertyArrayToSingleConverter();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var jsonProperty = base.CreateProperty(member, memberSerialization);
if (jsonProperty.Converter == null && jsonProperty.MemberConverter == null)
{
if (jsonProperty.PropertyType.IsPrimitive
|| jsonProperty.PropertyType == typeof(string))
{
jsonProperty.Converter = jsonProperty.MemberConverter = simpleConverter;
}
else if (jsonProperty.PropertyType != typeof(object)
&& !typeof(IEnumerable).IsAssignableFrom(jsonProperty.PropertyType)
&& !typeof(JToken).IsAssignableFrom(jsonProperty.PropertyType))
{
jsonProperty.Converter = jsonProperty.MemberConverter = new ObjectPropertyArrayToSingleConverter(this, jsonProperty.PropertyType);
}
}
return jsonProperty;
}
}
public static class JsonContractExtensions
{
public static bool? IsArrayContract(this JsonContract contract)
{
if (contract == null)
throw new ArgumentNullException();
if (contract is JsonArrayContract)
return true;
else if (contract is JsonLinqContract)
return null; // Could be an object or an array.
else
return false;
}
}
class SimplePropertyArrayToSingleConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
while (reader.TokenType == JsonToken.Comment)
reader.Read();
if (reader.TokenType == JsonToken.Null)
return null;
var contract = serializer.ContractResolver.ResolveContract(objectType);
bool hasValue = false;
if (reader.TokenType == JsonToken.StartArray)
{
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.Comment:
break;
case JsonToken.EndArray:
return UndefaultValue(objectType, existingValue, contract);
default:
if (hasValue)
throw new JsonSerializationException("Too many values at path: " + reader.Path);
existingValue = ReadItem(reader, objectType, existingValue, serializer, contract);
hasValue = true;
break;
}
}
// Should not come here.
throw new JsonSerializationException("Unclosed array at path: " + reader.Path);
}
else
{
existingValue = ReadItem(reader, objectType, existingValue, serializer, contract);
return UndefaultValue(objectType, existingValue, contract);
}
}
private static object UndefaultValue(Type objectType, object existingValue, JsonContract contract)
{
if (existingValue == null && objectType.IsValueType && Nullable.GetUnderlyingType(objectType) == null)
existingValue = contract.DefaultCreator();
return existingValue;
}
private static object ReadItem(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer, JsonContract contract)
{
if (contract is JsonPrimitiveContract || existingValue == null)
{
existingValue = serializer.Deserialize(reader, objectType);
}
else
{
serializer.Populate(reader, existingValue);
}
return existingValue;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartArray();
if (value != null)
serializer.Serialize(writer, value);
writer.WriteEndArray();
}
}
class ObjectPropertyArrayToSingleConverter : SimplePropertyArrayToSingleConverter
{
readonly Type propertyType;
readonly IContractResolver resolver;
int canConvert = -1;
public ObjectPropertyArrayToSingleConverter(IContractResolver resolver, Type propertyType)
: base()
{
if (propertyType == null || resolver == null)
throw new ArgumentNullException();
this.propertyType = propertyType;
this.resolver = resolver;
}
int GetIsEnabled()
{
var contract = resolver.ResolveContract(propertyType);
return contract.IsArrayContract() == false ? 1 : 0;
}
bool IsEnabled
{
get
{
// We need to do this in a lazy fashion since recursive calls to resolve contracts while creating a contract are problematic.
if (canConvert == -1)
Interlocked.Exchange(ref canConvert, GetIsEnabled());
return canConvert == 1;
}
}
public override bool CanRead { get { return IsEnabled; } }
public override bool CanWrite { get { return IsEnabled; } }
}
然后像这样使用它:
string url = @"..."; // Replace with your actual URL.
IList<BusinessFunctionData> outputlist;
WebRequest request = WebRequest.Create(url);
using (var response = request.GetResponse())
using (var responseStream = response.GetResponseStream())
{
var settings = new JsonSerializerSettings { ContractResolver = ArrayToSingleContractResolver.Instance, NullValueHandling = NullValueHandling.Ignore };
outputlist = JsonExtensions.DeserializeNamedProperties<List<BusinessFunctionData>>(responseStream, "result", settings).FirstOrDefault();
}