c# - Newtonsoft Json 序列化程序在使用 CamelCasePropertyNamesContractResolver 时出现 NullReferenceException
问题描述
我正在使用 Newtonsoft 通过 CamelCasePropertyNamesContractResolver Contract Resolver 将对象序列化为 Json。
有时(随机)Newtonsoft 在尝试解析合同时收到 NullReferenceException,然后所有其余的序列化都失败了。
我在 Linux 机器上使用 .Net core 2.1 和 Newtonsoft 版本 11.0.2。合同解析器类型是 CamelCasePropertyNamesContractResolver。
ex=System.NullReferenceException: Object reference not set to an instance of an object.
at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
at System.Collections.Generic.Dictionary`2..ctor(IDictionary`2 dictionary, IEqualityComparer`1 comparer)
at Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver.ResolveContract(Type type)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(JsonWriter writer, Object value, JsonContainerContract contract, JsonProperty member, JsonProperty property, JsonContract& memberContract, Object& memberValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)
序列化对象的代码
if (serializerSettings == null)
serializerSettings = new CamelCaseSerializerSettings();
var serializer = JsonSerializer.Create(serializerSettings);
using (var streamWriter = new StreamWriter(writeTo, DEFAULT_ENCODING, 1024*2, true))
using (var jsonTextWriter = new JsonTextWriter(streamWriter))
{
jsonTextWriter.CloseOutput = false;
serializer.Serialize(jsonTextWriter, data);
}
public CamelCaseSerializerSettings(Formatting serializerFormatting = Formatting.Indented, JsonConverter converter = null)
{
Formatting = serializerFormatting;
ContractResolver = new CamelCasePropertyNamesContractResolver();
if (converter != null)
{
Converters.Add(converter);
}
}
- 为什么会发生此错误?
- 我该如何解决?
解决方案
这可能是 Newtonsoft 的CamelCasePropertyNamesContractResolver
. 此合约解析器使用其自己的线程安全字典缓存机制的内联实现在相同解析器类型的所有实例之间全局共享合约信息,如参考源所示。值得注意的是,这种缓存机制不同于ThreadSafeStore<TKey, TValue>
Json.NET 中其他地方使用的缓存机制,包括DefaultContractResolver
. 当 Json.NET 尝试从预先存在的缓存构建新的静态缓存以添加新合同时,您看到的异常就会发生。
您可能希望向Newtonsoft报告有关此问题的问题,如果用于缓存,问题可能会自行解决。例如,后者在其方法中使用内存屏障,而骆驼案例解析器的内联实现不使用内存屏障。CamelCasePropertyNamesContractResolver
ThreadSafeStore<TKey, TValue>
AddValue()
作为一种潜在的解决方法,请考虑用CamelCasePropertyNamesContractResolver
适当配置的静态实例替换DefaultContractResolver
:
static IContractResolver camelCaseResolver = new DefaultContractResolver
{
// Set ProcessDictionaryKeys and OverrideSpecifiedNames as per your preference. These are the settings used by CamelCasePropertyNamesContractResolver
NamingStrategy = new CamelCaseNamingStrategy { ProcessDictionaryKeys = true, OverrideSpecifiedNames = true }
};
public static IContractResolver CamelCaseResolver => camelCaseResolver;
public static JsonSerializerSettings CreateCamelCaseSerializerSettings(Formatting serializerFormatting = Formatting.Indented, JsonConverter converter = null)
{
var settings = new JsonSerializerSettings
{
ContractResolver = CamelCaseResolver,
Formatting = serializerFormatting,
};
if (converter != null)
settings.Converters.Add(converter);
return settings;
}
Newtonsoft建议缓存合约解析器以获得最佳性能,所以我也这样做。
更新
审核后:
我相信可能存在一个或多个线程错误CamelCasePropertyNamesContractResolver.ResolveContract()
:
private static readonly object TypeContractCacheLock = new object();
private static Dictionary<StructMultiKey<Type, Type>, JsonContract>? _contractCache;
public override JsonContract ResolveContract(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
// for backwards compadibility the CamelCasePropertyNamesContractResolver shares contracts between instances
StructMultiKey<Type, Type> key = new StructMultiKey<Type, Type>(GetType(), type);
Dictionary<StructMultiKey<Type, Type>, JsonContract>? cache = _contractCache;
if (cache == null || !cache.TryGetValue(key, out JsonContract contract))
{
contract = CreateContract(type);
// avoid the possibility of modifying the cache dictionary while another thread is accessing it
lock (TypeContractCacheLock)
{
// Bug: No checking to see whether the cache contains the contract inside the lock, which is needed in case `_contractCache` was modified by another thread between the outer check and the lock, or the value of `_contractCache` was stale.
// Possible bug: no volatile read.
cache = _contractCache;
Dictionary<StructMultiKey<Type, Type>, JsonContract> updatedCache = (cache != null)
? new Dictionary<StructMultiKey<Type, Type>, JsonContract>(cache)
: new Dictionary<StructMultiKey<Type, Type>, JsonContract>();
updatedCache[key] = contract;
// Bug: no Thread.MemoryBarrier()
// Possible bug: no volatile write.
_contractCache = updatedCache;
}
}
return contract;
}
可能的错误包括:
没有
Thread.MemoryBarrier()
之前_contractCache
被重置。这允许编译器重新排序代码,如下所示:_contractCache = updatedCache; updatedCache[key] = contract;
这将允许未锁定的读取器线程在修改时访问
updatedCache
它,这可能会导致异常。Newtonsoft
ThreadSafeStore<TKey, TValue>
正确地做到了这一点。没有双重检查锁定来查看所需的合约是否已添加到lock 内部的缓存中。
再一次
ThreadSafeStore<TKey, TValue>
正确地做到了这一点。可能
_contractCache
需要volatile
。我不确定这是必要的,因为它只会从
lock
语句内部发生变异。如上所述添加双重检查锁定可能就足够了。ThreadSafeStore<TKey, TValue>
不用于其volatile
内部缓存。
切换到静态DefaultContractResolver
应该可以解决这些问题,因为它在ThreadSafeStore<TKey, TValue>
内部使用。
推荐阅读
- c++ - 推力中的 argsort
- java - 二和 LeetCode 问题:蛮力解决方法不起作用?
- numpy - 从 cython 中的指针创建一个 numpy 数组
- python - Python SQL 没有为整个 URL 列表插入数据
- python - 使用线程同时运行两个包含无限循环的方法的首选方法是什么?
- reactjs - 如何使用 Typescript 和 styled-components 创建 ref
- c# - 什么是 Roslyn 中的 PatternSyntax
- typescript - TypeScript Type Inference For Simple Function Combination
- reactjs - ReactJS - 我如何验证输入字段
- amp-html - 如果我使用 android 相机使用输入文件,属性 on="change:..." 在 AMP 中不起作用