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

4

我有一个无效的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; }
}
JsonConverterread 方法
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,不需要写入。

你需要编写具有重复键的JSON,还是只需要读取? - dbc
@dbc 请阅读。 - Joel
2个回答

3
我看到你的转换器存在几个问题:
  1. 由于你硬编码了路径,当 MyTestObject 嵌入到某个更高级别的容器中时,你的转换器将无法工作。实际上,它很可能会使读者定位不正确。

  2. 你的转换器没有正确跳过注释。

  3. 你的转换器在存在时没有填充传入的 existingValue,这在反序列化只读集合属性时是必要的。

  4. 你没有考虑当前的命名策略

  5. 当遇到截断文件时,你的转换器将不会抛出异常或以其他方式指示错误。

作为替代方案,你可以利用 Json.NET 会在 JSON 中多次遇到同一属性时调用该属性的 setter 的特性,通过一个仅设置的替代属性,在 DTO 中累积 "Quote" 属性的值,如下所示:

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
演示代码可以在这里找到。

1
哈哈,谢谢!我有一种预感,我的解决方案并不是很好... :D 我可以确认这个很好用,谢谢! - Joel

0

由于 C# 不支持带有双精度属性的 json 序列化为 C# 类型的实例,因此您需要创建一个没有双精度属性的替代类,并相应地修复 json。完成此操作后,您可以无问题地进行反序列化。

您可以使用几种不同的方法来修复 json,但对我而言,这种方法最简单。

var jsonOrig=" ... your json";

var json=jsonOrig.Replace("Quotes\":{","Quotes\":[{").Replace("}}}","}}]}");

var quotesRoot = JsonConvert.DeserializeObject<QuotesRoot>(json);

 List<QuoteItem> quotes=quotesRoot.Quotes;

结果

{
  "Quotes": [
    {
      "Quote": {
        "Text": "Hi"
      }
    },
    {
      "Quote": {
        "Text": "Hello"
      }
    }
  ]
}

public partial class QuotesRoot
    {
        [JsonProperty("Quotes")]
        public List<QuoteItem> Quotes { get; set; }
    }

    public partial class QuoteItem
    {
        [JsonProperty("Quote")]
        public Quote Quote { get; set; }
    }

    public partial class Quote
{
    [JsonProperty("Text")]
    public string Text { get; set; }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接