首页 > 解决方案 > 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 Json serializer Getting NullReferenceException when using CamelCasePropertyNamesContractResolver #2507

标签: c#jsonmultithreadingjson.net

解决方案


可能是 Newtonsoft 的CamelCasePropertyNamesContractResolver. 此合约解析器使用其自己的线程安全字典缓存机制的内联实现在相同解析器类型的所有实例之间全局共享合约信息,如参考源所示。值得注意的是,这种缓存机制不同于ThreadSafeStore<TKey, TValue>Json.NET 中其他地方使用的缓存机制,包括DefaultContractResolver. 当 Json.NET 尝试从预先存在的缓存构建新的静态缓存以添加新合同时,您看到的异常就会发生。

您可能希望向Newtonsoft报告有关此问题的问题,如果用于缓存,问题可能会自行解决。例如,后者在其方法中使用内存屏障,而骆驼案例解析器的内联实现不使用内存屏障。CamelCasePropertyNamesContractResolverThreadSafeStore<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它,这可能会导致异常。

    NewtonsoftThreadSafeStore<TKey, TValue>正确地做到了这一点。

  • 没有双重检查锁定来查看所需的合约是否已添加到lock 内部的缓存中。

    再一次ThreadSafeStore<TKey, TValue>正确地做到了这一点。

  • 可能 _contractCache需要volatile

    我不确定这是必要的,因为它只会从lock语句内部发生变异。如上所述添加双重检查锁定可能就足够了。

    ThreadSafeStore<TKey, TValue>不用于其volatile内部缓存。

切换到静态DefaultContractResolver应该可以解决这些问题,因为它在ThreadSafeStore<TKey, TValue>内部使用。


推荐阅读