【问题标题】:Newtonsoft Json serializer Getting NullReferenceException when using CamelCasePropertyNamesContractResolverNewtonsoft Json 序列化程序在使用 CamelCasePropertyNamesContractResolver 时出现 NullReferenceException
【发布时间】:2021-04-04 16:36:51
【问题描述】:

我正在使用 Newtonsoft 通过 CamelCasePropertyNamesContractResolver 合同解析器将对象序列化为 Json。

有一次(随机)Newtonsoft 在尝试解析合同时收到 NullReferenceException,然后所有其余的序列化都失败了。

我在 Linux 机器上使用 .Net core 2.1 和 Newtonsoft 版本 11.0.2。合约解析器类型是 CamelCasePropertyNamesContractResolver。

error

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

【问题讨论】:

  • 这看起来是一个小时前的 NullReferenceException when using Newtonsoft Json serializer 的副本,该副本已作为 What is a NullReferenceException, and how do I fix it? 的副本关闭。如果您需要更具体的帮助,请将您的问题edit 分享给minimal reproducible example,特别是重现问题的 JSON 和代码(作为文本而不是屏幕截图)。
  • @Steve - NullReferenceException 正在 Newtonsoft 内部深处发生,试图构建某种Dictionary<TKey, TValue> - 不是直接在查询者的代码中。所以,给定一个正确的minimal reproducible example,这可能需要一个单独的答案。
  • @Steve 你没有看问题!!!!这与 [什么是 NullReferenceException,我该如何解决?] (stackoverflow.com/questions/4660142/…) 问题无关,并且那里的答案没有回答我的问题。我无法重现该问题,因为它发生在随机物体上。如果我可以选择重现问题,我已经做到了,并且不在这里寻求帮助。
  • 这似乎是 Newtonsoft 中的一个错误,这就是我问这个问题的原因。感谢您重新打开。

标签: c# json multithreading json.net


【解决方案1】:

可能是 Newtonsoft 的 CamelCasePropertyNamesContractResolver 的线程错误。此合同解析器 shares contract information globally across all instances of the same resolver type,使用其自己的线程安全字典缓存机制的内联实现,可以在reference source 中看到。值得注意的是,这种缓存机制不同于 Json.NET 中其他地方使用的ThreadSafeStore<TKey, TValue>,包括DefaultContractResolver。您看到的异常发生在 Json.NET 尝试从预先存在的缓存中构造新的静态缓存以添加新合同时。

您可能希望 report an issue 将此事告知 Newtonsoft,如果 CamelCasePropertyNamesContractResolver 使用 ThreadSafeStore<TKey, TValue> 进行缓存,问题可能自行解决。例如。后者uses memory barriers inside its AddValue() method 而骆驼案例解析器的内联实现不使用内存屏障。

作为一种潜在的解决方法,考虑将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 recommends caching the contract resolver for best performance 我也是。


更新

审核后:

我认为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;
}

可能的错误包括:

  • _contractCache 被重置之前没有Thread.MemoryBarrier()。这允许编译器按如下方式重新排序代码:

             _contractCache = updatedCache;
             updatedCache[key] = contract;
    

    这将允许未锁定的读取器线程在修改 updatedCache 时访问它,这可能会导致异常。

    Newtonsoft 的 ThreadSafeStore&lt;TKey, TValue&gt; 正确地做到了这一点。

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

    ThreadSafeStore&lt;TKey, TValue&gt; 再次正确地做到了这一点。

  • 可能 _contractCache 需要是 volatile

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

    ThreadSafeStore&lt;TKey, TValue&gt;使用volatile作为其内部缓存。

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

【讨论】:

  • 我不确定锁内的内存屏障是否能解决任何问题。但是由于非只读的_contractCache字段也在锁之外被访问,它肯定应该是一个易失的字段。
  • @GyörgyKőszeg - Newtonsoft 的 ThreadSafeStore 使用 double-checked locking 而不是 volatile(除非 ConcurrentDictionary&lt;&gt; 可用,它将优先使用任何自制实现。)我认为这也应该有效,所以DefaultContractResolver 应该没问题。
  • @GyörgyKőszeg - 嗯,你可能是对的,根据Is volatile still needed inside lock statements?(答案:)。
猜你喜欢
  • 1970-01-01
  • 2016-09-10
  • 2018-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-06
相关资源
最近更新 更多