c# - 使用自定义 Newtonsoft JSON 转换器解析具有重复键的 JSON

标签 c# json json.net

我有一个无效的 JSON,我需要使用 Newtonsoft 进行解析。问题在于,JSON 没有使用正确的数组,而是包含数组中每个条目的重复属性。
我有一些工作代码,但真的不确定这是要走的路还是有更简单的方法?
无效的 JSON:

{
    "Quotes": {
        "Quote": {
            "Text": "Hi"
        },
        "Quote": {
            "Text": "Hello"
        }
    }
}
我试图序列化的对象:
class MyTestObject
{
    [JsonConverter(typeof(NewtonsoftQuoteListConverter))]
    public IEnumerable<Quote> Quotes { get; set; }
}

class Quote
{
    public string Text { get; set; }
}
JsonConverter的读取方法
public override IEnumerable<Quote> ReadJson(JsonReader reader, Type objectType, IEnumerable<Quote> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
    if (reader.TokenType == JsonToken.Null)
    {
        return null;
    }

    var quotes = new List<Quote>();
    while (reader.Read())
    {
        if (reader.Path.Equals("quotes", StringComparison.OrdinalIgnoreCase) && reader.TokenType == JsonToken.EndObject)
        {
            // This is the end of the Quotes block. We've parsed the entire object. Stop reading.
            break;
        }
        
        if (reader.Path.Equals("quotes.quote", StringComparison.OrdinalIgnoreCase) && reader.TokenType == JsonToken.StartObject)
        {
            // This is the start of a new Quote object. Parse it.
            quotes.Add(serializer.Deserialize<Quote>(reader));
        }
    }
    
    return quotes;
}
我只需要使用重复键读取 JSON,而不是写入。

最佳答案

我可以看到您的转换器存在一些问题:

  • 因为您对路径进行了硬编码,所以当 MyTestObject 时您的转换器将无法工作嵌入在一些更高级别的容器中。事实上,它可能会让读者定位不正确。
  • 您的转换器没有正确跳过过去的评论。
  • 您的转换器未填充传入 existingValue当存在时,这在反序列化 get-only 集合属性时是必需的。
  • 你不拿当前naming strategy考虑到。
  • 当遇到截断的文件时,您的转换器不会抛出异常或以其他方式指示错误。

  • 作为替代方案,您可以利用以下事实:当在 JSON 中多次遇到该属性时,Json.NET 将多次调用该属性的 setter 来累积 "Quote"DTO 中具有仅设置代理属性的属性值像这样:
    class NewtonsoftQuoteListConverter : JsonConverter<IEnumerable<Quote>>
    {
        class DTO
        {
            public ICollection<Quote> Quotes { get; init; }
            public Quote Quote { set => Quotes.Add(value); }
        }
    
        public override IEnumerable<Quote> ReadJson(JsonReader reader, Type objectType, IEnumerable<Quote> existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
                return null;
            var dto = new DTO { Quotes = existingValue is ICollection<Quote> l && !l.IsReadOnly ? l : new List<Quote>() }; // Reuse existing value if possible
            serializer.Populate(reader, dto); 
            return dto.Quotes;
        }
        
        public override bool CanWrite => true; // Replace with false if you don't need custom serialization.
        
        public override void WriteJson(JsonWriter writer,  IEnumerable<Quote> value, JsonSerializer serializer)
        {
            // Handle naming strategies.
            var name = ((JsonObjectContract)serializer.ContractResolver.ResolveContract(typeof(DTO))).Properties.Where(p => p.UnderlyingName == nameof(DTO.Quote)).First().PropertyName;
        
            writer.WriteStartObject();
            foreach (var item in value)
            {
                writer.WritePropertyName(name);
                serializer.Serialize(writer, item);
            }
            writer.WriteEndObject();
        }
    }
    
    public static partial class JsonExtensions
    {
        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;
        }
    }
    
    通过使用 DTO,可以考虑当前的命名约定。
    如果您不需要自定义序列化,请覆盖 CanWrite 并返回 false .
    演示 fiddle here .

    关于c# - 使用自定义 Newtonsoft JSON 转换器解析具有重复键的 JSON,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69452917/

    相关文章:

    javascript - XMLHttpRequest 仅适用于 IE

    c# - JSON.NET反序列化自定义日期格式

    c# - 使用 JSON.NET 自定义异常覆盖消息

    c# - 在 Heroku 上为只监听 https 的服务使用哪个环境变量

    C# 自动为参数选择最相关的方法

    c# - 通过 DataGrid 编辑 ObservableDictionary

    javascript - 如果数据类型是 JSON 为什么 MIME 类型 'application/json' 会被拒绝?

    c# - 如何在 .NET 中使用 CNG(或支持 AES-NI 的指令集)?

    javascript - 用于解析带有数字键的 javaScript 对象的 java 库

    .net - 为什么 ServiceStack.Text 不默认日期为 iso8601?