簡體   English   中英

在反序列化具有來自 JSON 的重復鍵的字典時,如何強制拋出異常?

[英]How can I force an exception to be thrown when deserializing a dictionary with duplicated keys from JSON?

我有一個數據 model 和一個Dictionary<string, string>屬性如下:

public class Model
{
    public string Name { get; set; }
    public Dictionary<string, string> Attributes { get; set; }
}

在某些極少數情況下,我收到 JSON 的Attributes屬性名稱重復,例如:

{
   "name":"Object Name",
   "attributes":{
      "key1":"adfadfd",
      "key1":"adfadfadf"
   }
}

我希望在這種情況下拋出異常,但是當我使用 Json.NET 反序列化時沒有錯誤,字典反而包含遇到的最后一個值。 在這種情況下如何強制錯誤?


作為一種解決方法,我目前將屬性聲明為鍵/值對列表:

    public List<KeyValuePair<string, string>> Attributes { get; set; 

這需要我以下列格式序列化屬性:

"attributes": [
    {
        "key": "key1",
        "value": "adfadfd"
    },
    {
        "key": "key1",
        "value": "adfadfadf"
    }
]

然后我可以檢測到重復項。 但是,我更願意使用更緊湊的 JSON object 語法而不是 JSON 數組語法,並將屬性聲明為字典。

似乎,當從 JSON object 中反序列化具有重復屬性名稱的字典時,Json.NET(以及 System.Text.Json)會使用最后一個重復鍵的值靜默填充字典。 此處演示。)這並不奇怪,因為 JSON RFC 8259指出

當 object 中的名稱不唯一時,接收此類 object 的軟件的行為是不可預測的。 許多實現僅報告姓氏/值對......

由於您不希望這樣,您可以創建一個自定義 JsonConverter ,它會在屬性名稱重復時拋出錯誤:

public class NoDuplicateKeysDictionaryConverter<TValue> : NoDuplicateKeysDictionaryConverter<Dictionary<string, TValue>, TValue> 
{
}

public class NoDuplicateKeysDictionaryConverter<TDictionary, TValue> : JsonConverter<TDictionary> where TDictionary : IDictionary<string, TValue>
{
    public override TDictionary ReadJson(JsonReader reader, Type objectType, TDictionary existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return typeof(TDictionary).IsValueType && Nullable.GetUnderlyingType(typeof(TDictionary)) == null ? throw new JsonSerializationException("null value") : default;
        reader.AssertTokenType(JsonToken.StartObject);
        var dictionary = existingValue ?? (TDictionary)serializer.ContractResolver.ResolveContract(typeof(TDictionary)).DefaultCreator();
        // Todo: decide whether you want to clear the incoming dictionary.
        while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject)
        {
            var key = (string)reader.AssertTokenType(JsonToken.PropertyName).Value;
            var value = serializer.Deserialize<TValue>(reader.ReadToContentAndAssert());
            // https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.idictionary-2.add#exceptions
            // Add() will throw an ArgumentException when an element with the same key already exists in the IDictionary<TKey,TValue>.
            dictionary.Add(key, value);
        }
        return dictionary;
    }

    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, TDictionary value, JsonSerializer serializer) => throw new NotImplementedException();
}

public static partial class JsonExtensions
{
    public static JsonReader AssertTokenType(this JsonReader reader, JsonToken tokenType) => 
        reader.TokenType == tokenType ? reader : throw new JsonSerializationException(string.Format("Unexpected token {0}, expected {1}", reader.TokenType, tokenType));
    
    public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
        reader.ReadAndAssert().MoveToContentAndAssert();

    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

然后將其添加到您的 model,如下所示:

    [Newtonsoft.Json.JsonConverter(typeof(NoDuplicateKeysDictionaryConverter<string>))]
    public Dictionary<string, string> Attributes { get; set; }

每當嘗試向字典中添加重復的鍵時,都會拋出ArgumentException

在這里演示。

查看Json.NET 的源代碼,代碼就是這樣做的:

dictionary[keyValue] = itemValue;

因此,一種選擇是為Dictionary編寫一個包裝器,以提供您想要的功能。 我們可以傳遞所有調用,但索引器除外,它傳遞給Add而不是這將導致異常。

從技術上講, Json.NET 代碼只要求IDictionary ,而不是IDictionary<TKey, TValue>但如果不強制轉換和/或拆箱,您將無法讀取它。

     const string json =@"
     {
        ""name"":""Object Name"",
        ""attributes"":{
           ""key1"":""adfadfd"",
           ""key1"":""adfadfadf""
        }
     }
     ";
     Console.WriteLine(JsonConvert.DeserializeObject<Model>(json));
 public class Model
 {
     public string Name { get; set; }
     public StrictDictionary<string, string> Attributes { get; set; }
 }
 
 public class StrictDictionary<TKey, TValue> : IDictionary<TKey, TValue>
 {
     public Dictionary<TKey, TValue> InnerDictionary {get; set; } = new Dictionary<TKey, TValue>();
     
     public bool ContainsKey(TKey key) => InnerDictionary.ContainsKey(key);
     public void Add(TKey key, TValue value) => InnerDictionary.Add(key, value);
     void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> kvp) => ((ICollection<KeyValuePair<TKey, TValue>>) InnerDictionary).Add(kvp);
     bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> kvp) => ((ICollection<KeyValuePair<TKey, TValue>>) InnerDictionary).Contains(kvp);
     void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int i) => ((ICollection<KeyValuePair<TKey, TValue>>) InnerDictionary).CopyTo(array, i);
     public void Clear() => InnerDictionary.Clear();
     public bool Remove(TKey key) => InnerDictionary.Remove(key);
     bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> kvp) => ((ICollection<KeyValuePair<TKey, TValue>>) InnerDictionary).Remove(kvp);
     public bool TryGetValue(TKey key, out TValue value) => InnerDictionary.TryGetValue(key, out value);
     public ICollection<TKey> Keys => InnerDictionary.Keys;
     public ICollection<TValue> Values => InnerDictionary.Values;
     public int Count => InnerDictionary.Count;
     public bool IsReadOnly => ((ICollection<KeyValuePair<TKey, TValue>>) InnerDictionary).IsReadOnly;
     public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => InnerDictionary.GetEnumerator();
     IEnumerator IEnumerable.GetEnumerator() => InnerDictionary.GetEnumerator();

     public TValue this[TKey key]
     {
         get => InnerDictionary[key];
         set => InnerDictionary.Add(key, value);
     }
 }

做.netfiddle

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM