繁体   English   中英

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

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

如何反序列化以下 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 忽略特定的字典键?

您可以为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;
        }
    }
}

然后您可以将其添加到设置中,如下所示:

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; }
}

笔记:

  • 我以通用方式编写转换器来处理任何类型的字典值,包括诸如intDateTime之类的原语以及数组或对象。

  • 虽然具有无效字典值(不能反序列化为字典值类型)的 JSON 文件应该是可反序列化的,但格式错误的 JSON文件(例如,被截断的文件)仍应导致抛出异常。

    转换器通过首先将值加载到JToken然后尝试反序列化令牌来处理此问题。 如果文件格式不正确, JToken.Load(reader)将抛出异常,故意不捕获该异常。

  • 据报道,Json.NET 的异常处理“非常不稳定”(参见问题 #1580: Regression from Json.NET v6: cannot skip an invalid object value type in an array via exception handling )所以我不依赖它来跳过无效的字典值。

  • 我不是 100% 确定我的所有评论处理案例都是正确的。 所以这可能需要额外的测试。

工作示例 .Net在这里摆弄。

我认为你可以忽略这样的异常:

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 继续。

暂无
暂无

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

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