-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Description
A common pattern when implementing JsonConverter<MyType> is to read a few properties and forward the Utf8JsonReader to JsonSerializer.Deserialize<TValue>(ref reader) to read some common type that don't need special handling. If a JsonException is thrown in the forwarded call, LineNumber, BytePositionInLine and Path is wrong with regards to the original json that was tried to deserialize.
I'd like to keep the references LineNumber, BytePositionInLine and Path relative to the full json.
Reproduction Steps
using System.Text.Json;
using System.Text.Json.Serialization;
var json = @"{
""name"": ""test"",
""props"": [
""prop1"",
""prop2"",
[""this"", ""is"", ""a"", ""error""]
]
}";
try
{
var comp = JsonSerializer.Deserialize<MyComponent>(json, new JsonSerializerOptions(JsonSerializerDefaults.Web));
}
catch (JsonException e)
{
Console.WriteLine(e.Message);
Console.WriteLine($"Path: {e.Path}");
Console.WriteLine($"LineNumber: {e.LineNumber}");
Console.WriteLine($"BytePositionInLine: {e.BytePositionInLine}");
}
[JsonConverter(typeof(MyComponentConverter))]
public class MyComponent
{
public string? Name { get; set; }
public List<string>? Props { get; set; }
}
public class MyComponentConverter : JsonConverter<MyComponent>
{
public override MyComponent? Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
{
var component = new MyComponent();
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
{
var propName = reader.GetString();
reader.Read();
switch (propName)
{
case "name":
component.Name = reader.GetString();
break;
case "props":
// use default behaviour for this list
component.Props = JsonSerializer.Deserialize<List<string>>(ref reader, options);
break;
default:
reader.Skip();
break;
}
}
return component;
}
public override void Write(Utf8JsonWriter writer, MyComponent value, JsonSerializerOptions options) => throw new NotImplementedException();
}Expected behavior
I expect the JsonException to tell me where the parser was, when the error occurred. Just like it does when I don't use the custom converter.
Message: The JSON value could not be converted to System.String. Path: $.props[2] | LineNumber: 5 | BytePositionInLine: 9.
Path: $.props[2]
LineNumber: 5
BytePositionInLine: 9
Actual behavior
The actual result resets the e.Path, e.LineNumber and e.BytePositionInLine when the Utf8JsonReader is passed to JsonSerializer.Deserialize
Message: The JSON value could not be converted to System.String. Path: $[2] | LineNumber: 3 | BytePositionInLine: 9.
Path: $[2]
LineNumber: 3
BytePositionInLine: 9
Regression?
I don't know, but assume this has always been this way.
Known Workarounds
I can read everything inside the custom converter from the Utf8JsonReader without forwarding the simple components to the builtin tools.
Configuration
dotnet 6 and 7
Other information
I have a draft solution that does not forward path information in main...ivarne:runtime:custom-recursive-json-parsing. Not sure if it is correct. I don't understand all the concepts here.