如何反序列化以下JSON
{
"result" : {
"master" : [
["one", "two"],
["three", "four"],
["five", "six", "seven"],
],
"blaster" : [
["ein", "zwei"],
["drei", "vier"]
],
"surprise" : "nonsense-nonsense-nonsense"
}
}
进入以下数据结构
class ResultView
{
public Dictionary<string, string[][]> Result { get; set; }
}
使用Json.NET吗?
它必须是字典,因为在编译时,诸如“ master”和“ blaster”之类的键名是未知的。众所周知,它们始终指向字符串数组的数组。问题在于,名称已知且始终相同的关键“惊奇”指向无法解释为string[][]
的事物,这导致Json.NET中出现异常。
有什么方法可以使Json.NET忽略特定的字典键吗?
答案 0 :(得分:0)
我认为您可以忽略这样的异常:
ResultView result = JsonConvert.DeserializeObject<ResultView>(jsonString,
new JsonSerializerSettings
{
Error = delegate (object sender, Newtonsoft.Json.Serialization.ErrorEventArgs args)
{
// System.Diagnostics.Debug.WriteLine(args.ErrorContext.Error.Message);
args.ErrorContext.Handled = true;
}
}
);
args.ErrorContext.Error.Message将包含实际的错误消息。
args.ErrorContext.Handled = true;将告诉Json.Net继续。
答案 1 :(得分:0)
您可以为IDictionary<string, TValue>
引入custom generic JsonConverter
,以过滤掉无效的字典值(即那些不能成功反序列化为字典值类型的值):
public class TolerantDictionaryItemConverter<TDictionary, TValue> : JsonConverter where TDictionary : IDictionary<string, TValue>
{
public override bool CanConvert(Type objectType)
{
return typeof(TDictionary).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type dictionaryType, object existingValue, JsonSerializer serializer)
{
// Get contract information
var contract = serializer.ContractResolver.ResolveContract(dictionaryType) as JsonDictionaryContract;
if (contract == null)
throw new JsonSerializationException(string.Format("Invalid JsonDictionaryContract for {0}", dictionaryType));
if (contract.DictionaryKeyType != typeof(string))
throw new JsonSerializationException(string.Format("Key type {0} not supported", dictionaryType));
var itemContract = serializer.ContractResolver.ResolveContract(contract.DictionaryValueType);
// Process the first token
var tokenType = reader.SkipComments().TokenType;
if (tokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException(string.Format("Expected {0}, encountered {1} at path {2}", JsonToken.StartArray, reader.TokenType, reader.Path));
// Allocate the dictionary
var dictionary = existingValue as IDictionary<string, TValue> ?? (IDictionary<string, TValue>) contract.DefaultCreator();
// Process the collection items
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndObject)
{
return dictionary;
}
else if (reader.TokenType == JsonToken.PropertyName)
{
var key = (string)reader.Value;
reader.ReadSkipCommentsAndAssert();
// For performance, skip tokens we can easily determine cannot be deserialized to itemContract
if (itemContract.QuickRejectStartToken(reader.TokenType))
{
System.Diagnostics.Debug.WriteLine(string.Format("value for {0} skipped", key));
reader.Skip();
}
else
{
// What we want to do is to distinguish between JSON files that are not WELL-FORMED
// (e.g. truncated) and that are not VALID (cannot be deserialized to the current item type).
// An exception must still be thrown for an ill-formed file.
// Thus we first load into a JToken, then deserialize.
var token = JToken.Load(reader);
try
{
var value = serializer.Deserialize<TValue>(token.CreateReader());
dictionary.Add(key, value);
}
catch (Exception)
{
System.Diagnostics.Debug.WriteLine(string.Format("value for {0} skipped", key));
}
}
}
else if (reader.TokenType == JsonToken.Comment)
{
continue;
}
else
{
throw new JsonSerializationException(string.Format("Unexpected token type {0} object at path {1}.", reader.TokenType, reader.Path));
}
}
// Should not come here.
throw new JsonSerializationException("Unclosed object at path: " + reader.Path);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static partial class JsonExtensions
{
public static JsonReader SkipComments(this JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
public static void ReadSkipCommentsAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
while (reader.Read())
{
if (reader.TokenType != JsonToken.Comment)
return;
}
new JsonReaderException(string.Format("Unexpected end at path {0}", reader.Path));
}
internal static bool QuickRejectStartToken(this JsonContract contract, JsonToken token)
{
if (contract is JsonLinqContract)
return false;
switch (token)
{
case JsonToken.None:
return true;
case JsonToken.StartObject:
return !(contract is JsonContainerContract) || contract is JsonArrayContract; // reject if not dictionary or object
case JsonToken.StartArray:
return !(contract is JsonArrayContract); // reject if not array
case JsonToken.Null:
return contract.CreatedType.IsValueType && Nullable.GetUnderlyingType(contract.UnderlyingType) == null;
// Primitives
case JsonToken.Integer:
case JsonToken.Float:
case JsonToken.String:
case JsonToken.Boolean:
case JsonToken.Undefined:
case JsonToken.Date:
case JsonToken.Bytes:
return !(contract is JsonPrimitiveContract); // reject if not primitive.
default:
return false;
}
}
}
然后您可以将其添加到设置中,如下所示:
var settings = new JsonSerializerSettings
{
Converters = { new TolerantDictionaryItemConverter<IDictionary<string, TValue>, TValue>() },
};
var root = JsonConvert.DeserializeObject<ResultView>(json, settings);
或使用JsonConverterAttribute
将其直接添加到ResultView
:
class ResultView
{
[JsonConverter(typeof(TolerantDictionaryItemConverter<IDictionary<string, string[][]>, string[][]>))]
public Dictionary<string, string[][]> Result { get; set; }
}
注意:
我以通用的方式编写了转换器,以处理任何类型的字典值,包括诸如int
或DateTime
之类的基元以及数组或对象。
虽然具有无效字典值的JSON文件(无法反序列化为字典值类型的JSON文件)应可反序列化,但是格式错误 JSON文件(例如,被截断的JSON文件)应该仍然会引发异常。
转换器首先通过将值加载到JToken
中,然后尝试反序列化令牌来处理此问题。如果文件格式不正确,JToken.Load(reader)
将引发异常,该异常有意未被捕获。
据报道,Json.NET的异常处理“非常不稳定”(例如,参见Issue #1580: Regression from Json.NET v6: cannot skip an invalid object value type in an array via exception handling),因此我没有依靠它来跳过无效的字典值。
我不确定100%是否正确处理了所有评论。因此可能需要其他测试。
工作示例.Net小提琴here。