c# - 使用自定义 Newtonsoft JSON 转换器解析带有重复键的 JSON
问题描述
我有一个无效的 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 集合属性时是必需的。您没有考虑当前的命名策略。
当遇到截断的文件时,您的转换器不会抛出异常或以其他方式指示错误。
作为替代方案,您可以利用以下事实:当在 JSON 中多次遇到该属性时,Json.NET 将多次调用该属性的 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
.
演示小提琴在这里。
推荐阅读
- php - 将 SMTP 添加到 php 表单 Bootstrap
- javascript - 验证来自 javascript ( chrome 控制台) 的角度输入表单
- python - 循环只迭代一次
- python - Python 协程:它们为什么有用?有哪些用例?
- r - 在 ggrepel 标签中使用 plotmath
- leaflet - 为什么此传单折线未显示?
- excel - Access VBA 创建和格式化图表宏在 Excel 中有效,但在 Access 中无效
- javafx - 获取 TableView 中某个对象在某列中的运行次数
- bash - 换行符不被打印
- python - 在系列上应用 RE 模块