使用自定义 Newtonsoft JSON 转换器解析具有重复键的 JSON
Parse JSON with duplicate keys using custom Newtonsoft JSON converter
我有一个无效的 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 反序列化为具有双属性的 c# 实例,因为 c# 不允许这样做。因此,您需要创建不带双重属性的替代 class 并相应地修复 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"
}
}
]
}
classes
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; }
}
我发现您的转换器存在一些问题:
因为您对路径进行了硬编码,所以当 MyTestObject
嵌入到某些更高级别的容器中时,您的转换器将无法工作。事实上,它可能会使 reader 定位不正确。
您的转换器没有正确跳过过去的评论。
您的转换器不会填充传入的 existingValue
,这在反序列化仅获取集合属性时是必需的。
你没有考虑当前的naming strategy。
您的转换器在遇到截断文件时不会抛出异常或以其他方式指示错误。
作为替代方案,您可以利用 Json.NET 会在 属性 多次遇到 属性 时多次调用 setter在 JSON 中,用 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
和 return false
。
演示 fiddle here.
我有一个无效的 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 反序列化为具有双属性的 c# 实例,因为 c# 不允许这样做。因此,您需要创建不带双重属性的替代 class 并相应地修复 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"
}
}
]
}
classes
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; }
}
我发现您的转换器存在一些问题:
因为您对路径进行了硬编码,所以当
MyTestObject
嵌入到某些更高级别的容器中时,您的转换器将无法工作。事实上,它可能会使 reader 定位不正确。您的转换器没有正确跳过过去的评论。
您的转换器不会填充传入的
existingValue
,这在反序列化仅获取集合属性时是必需的。你没有考虑当前的naming strategy。
您的转换器在遇到截断文件时不会抛出异常或以其他方式指示错误。
作为替代方案,您可以利用 Json.NET 会在 属性 多次遇到 属性 时多次调用 setter在 JSON 中,用 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
和 return false
。
演示 fiddle here.