简体   繁体   English

如何忽略 Json.NET 反序列化中的特定字典键?

[英]How to ignore specific dictionary key in Json.NET deserialization?

How can one deserialize the following JSON如何反序列化以下 JSON

{  
  "result"     : {
    "master"   : [
      ["one", "two"],
      ["three", "four"],
      ["five", "six", "seven"],
    ],
    "blaster"  : [
      ["ein", "zwei"],
      ["drei", "vier"]
    ],
    "surprise" : "nonsense-nonsense-nonsense"
  }
}

into the following data structure进入如下数据结构

class ResultView
{
  public Dictionary<string, string[][]> Result { get; set; }
}

with Json.NET?使用 Json.NET?

It has to be dictionary because key names such as 'master' and 'blaster' are unknown at the time of compilation.它必须是字典,因为在编译时不知道诸如“master”和“blaster”之类的键名。 What is known is that they always point to an array of arrays of strings.众所周知,它们总是指向一个字符串数组。 The problem is that key 'surprise', whose name is known and always the same, points to something that cannot be interpreted as string[][] , and this leads to exception in Json.NET.问题在于,名称已知且始终相同的键“惊喜”指向无法解释为string[][]的内容,这会导致 Json.NET 中的异常。

Is there any way to make Json.NET ignore specific dictionary key?有没有办法让 Json.NET 忽略特定的字典键?

You can introduce a custom generic JsonConverter for IDictionary<string, TValue> that filters out invalid dictionary values (ie those that cannot be deserialized successfully to the dictionary value type):您可以为IDictionary<string, TValue>引入自定义通用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;
        }
    }
}

Then you can add it to settings as follows:然后您可以将其添加到设置中,如下所示:

var settings = new JsonSerializerSettings
{
    Converters = { new TolerantDictionaryItemConverter<IDictionary<string, TValue>, TValue>() },
};

var root = JsonConvert.DeserializeObject<ResultView>(json, settings);

Or add it directly to ResultView with JsonConverterAttribute :或者使用JsonConverterAttribute ResultView

class ResultView
{
    [JsonConverter(typeof(TolerantDictionaryItemConverter<IDictionary<string, string[][]>, string[][]>))]
    public Dictionary<string, string[][]> Result { get; set; }
}

Notes:笔记:

  • I wrote the converter in a general way to handle any type of dictionary value including primitives such as int or DateTime as well as arrays or objects.我以通用方式编写转换器来处理任何类型的字典值,包括诸如intDateTime之类的原语以及数组或对象。

  • While a JSON file with invalid dictionary values (ones that cannot be deserialized to the dictionary value type) should be deserializable, an ill-formed JSON file (eg one that is truncated) should still result in an exception being thrown.虽然具有无效字典值(不能反序列化为字典值类型)的 JSON 文件应该是可反序列化的,但格式错误的 JSON文件(例如,被截断的文件)仍应导致抛出异常。

    The converter handles this by first loading the value into a JToken then attempting to deserialize the token.转换器通过首先将值加载到JToken然后尝试反序列化令牌来处理此问题。 If the file is ill-formed, JToken.Load(reader) will throw an exception, which is intentionally not caught.如果文件格式不正确, JToken.Load(reader)将抛出异常,故意不捕获该异常。

  • Json.NET's exception handling is reported to be "very flaky" (see eg Issue #1580: Regression from Json.NET v6: cannot skip an invalid object value type in an array via exception handling ) so I did not rely on it to skip invalid dictionary values.据报道,Json.NET 的异常处理“非常不稳定”(参见问题 #1580: Regression from Json.NET v6: cannot skip an invalid object value type in an array via exception handling )所以我不依赖它来跳过无效的字典值。

  • I'm not 100% sure I got all cases of comment handling correct.我不是 100% 确定我的所有评论处理案例都是正确的。 So that may need additional testing.所以这可能需要额外的测试。

Working sample .Net fiddle here .工作示例 .Net在这里摆弄。

I think you could ignore exceptions like this:我认为你可以忽略这样的异常:

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 would contain the actual error message. args.ErrorContext.Error.Message 将包含实际的错误消息。

args.ErrorContext.Handled = true; args.ErrorContext.Handled = true; will tell Json.Net to proceed.将告诉 Json.Net 继续。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM