【问题标题】:Parse JSON with duplicate keys using custom Newtonsoft JSON converter使用自定义 Newtonsoft JSON 转换器解析带有重复键的 JSON
【发布时间】:2021-10-05 15:04:22
【问题描述】:

我有一个无效的 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,而不需要写入。

【问题讨论】:

  • 您需要用重复键编写 JSON,还是只读取?
  • @dbc 刚刚阅读。

标签: c# json json.net


【解决方案1】:

我发现您的转换器存在一些问题:

  1. 因为您对路径进行了硬编码,所以当 MyTestObject 嵌入到某个更高级别的容器中时,您的转换器将无法工作。事实上,它可能会使阅读器的位置不正确。

  2. 您的转换器没有正确跳过 cmets。

  3. 当存在时,您的转换器不会填充传入的existingValue,这在反序列化 get-only 集合属性时是必需的。

  4. 您没有考虑当前的naming strategy

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

作为替代方案,您可以利用 Json.NET 将在 JSON 中多次遇到该属性时多次调用该属性的设置器这一事实,以使用 set- 来累积 "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

演示小提琴here.

【讨论】:

  • 哈哈,谢谢!我有一种预感,我的解决方案并不是那么好...... :D 我可以确认这很好用,谢谢!
【解决方案2】:

您不能将 json 反序列化为具有双重属性的 c# 实例,因为 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; }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-22
    • 1970-01-01
    • 1970-01-01
    • 2016-06-16
    相关资源
    最近更新 更多