I am receiving a property that could either have null { "some_obj" : null }
, an empty array [] { "some_obj" : [] }
, or an object has is not null with some properties { "some_obj" : 'name' : 'nick' }
as the value. In this context, an empty array should translate to null, so I created this converter.
public class MyConverter<T> : JsonConverter<T>
{
public override T? Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if(reader.TokenType == JsonTokenType.StartArray)
{
return default(T);
}
else if (reader.TokenType == JsonTokenType.StartObject)
{
return JsonSerializer.Deserialize<T>(ref reader, options);
}
else
{
return default(T);
}
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
=> JsonSerializer.Serialize(writer, (T?)value, options);
}
When the data coming in is { "some_obj" : [] }
, the first if statement is true and I get the following the JsonException when returning default(T)
: "The converter {converter} read too much or not enough." Any idea of what I'm doing wrong? When the data is { "some_obj" : null }
or { "some_obj" : 'name' : 'nick' }
, it's working fine.
I'm using the attribute on a type in one my classes
public class SomeClass
{
[JsonConverter(typeof(MyConverter<SomeObj>))]
[JsonPropertyName("some_obj")]
public SomeObj? SomeObj{ get; set; }
}
public class SomeObj
{
[JsonPropertyName("name")]
public string? Name{ get; set; }
}
UPDATE Making sure I ended on JsonTokenType.EndArray was the solution. Thanks Simon for the ticket reference
public override T? Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.StartObject)
{
return JsonSerializer.Deserialize<T>(ref reader, options);
}
if (reader.TokenType == JsonTokenType.StartArray)
{
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
{
return default(T);
}
}
}
throw new JsonException();
}
CodePudding user response:
Inside JsonConverter<T>.Read()
, if you want to ignore the contents of the current JSON token, you must advance the reader to end of the token, reading past any and all child tokens. The method Utf8JsonReader.Skip()
does this:
public class MyConverter<T> : JsonConverter<T>
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.StartObject:
return JsonSerializer.Deserialize<T>(ref reader, options);
default:
{
reader.Skip();
return default(T);
}
}
}
This method Skips the children of the current JSON token which is necessary when the token to be ignored is an array (and does nothing in the event the current token is a primitive value such as null
).
Demo fiddle here.
Notes:
The version of the converter from your UPDATE will correctly skip past arrays without nested values such as
[]
, but will not correctly skip past arrays with nested child arrays such as[[]]
. In order to manually skip to the end of an array with child arrays you would need to track theCurrentDepth
as well as theTokenType
.Skip()
takes care of this automatically.If you want to ignore a property value or array entry, you must also use
Utf8JsonReader.Skip()
to consume that value. See How can I correctly skip an unknown property inside the JsonConverter<T>.Read() method of System.Text.Json? for details.