[英]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);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.