Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JSONPath to the exception thrown by JsonDocument while parsing invalid JSON #29715

Closed
steveharter opened this issue May 30, 2019 · 8 comments · Fixed by #34167
Closed

Add JSONPath to the exception thrown by JsonDocument while parsing invalid JSON #29715

steveharter opened this issue May 30, 2019 · 8 comments · Fixed by #34167
Assignees
Labels
area-System.Text.Json enhancement Product code improvement that does NOT require public API changes/additions
Milestone

Comments

@steveharter
Copy link
Member

As an extension to https://github.com/dotnet/corefx/issues/37768, the JsonDocument class should support setting the Path property on JsonReaderException.

In addition, once this is complete, the serializer should re-throw JsonReaderException (as it does today) but now with the Path property pre-pended with the results from JsonReaderException to include the full Path of the serializer + JsonDocument.

See also PR dotnet/corefx#37938

@MarcoRossignoli
Copy link
Member

@steveharter is this fixed?Should we close?

@ahsonkhan
Copy link
Contributor

cc @bartonjs

@bartonjs
Copy link
Member

Is the idea that the database building routine be in a try/catch where the catch modifies the exception object and then continues the same callstack/dispatch with throw;? Next most obvious alternative: That in the catch it throws a new Exception, wrapping the old one, and the outer one has the path set.

Either of those seems doable; either by making the StackRow value also keep track of the StartObject/StartArray (which would also eliminate the seek-backwards for every EndObject/EndArray at the expense of needing to grow the temporary buffer more often) or changing/overloading FindIndexOfFirstUnsetSizeOrLength to accept a "no later than" parameter. -- The StackRowStack knows how many elements already exist in the array (maybe off-by-one, but the data is there), and for an object property the name is available from the node after StartArray.

It's probably something easy enough for up-for-grabs.

I'd do it by renaming

        private static void Parse(
            ReadOnlySpan<byte> utf8JsonSpan,
            Utf8JsonReader reader,
            ref MetadataDb database,
            ref StackRowStack stack)
        {

to ParseCore, and making a new Parse with the same signature which just does

private static void Parse(
    ReadOnlySpan<byte> utf8JsonSpan,
    Utf8JsonReader reader,
    ref MetadataDb database,
    ref StackRowStack stack)
{
    try
    {
        ParseCore(utf8JsonSpan, reader, ref database, ref stack);
    }
    catch (JsonReaderException e)
    {
        // Using stack, database, and maybe Stack<string> parts, build the path
        e.Path = path;
    }
}

@gary-holland
Copy link

Hi,

Is JsonPath support for querying the JsonDocument/JsonElement structures still being looked at, as this is a major gap for us shifting from Newtonsoft to system.text.json.

What we need is an equivalent to:

var jsonPath = "$.my.path";
var json = JToken.Parse(jsonString);
var token = json.SelectToken(jsonPath);

Something like this would be ideal:

var jsonPath = "$.my.path";
var jsonDoc = JsonDocument.Parse(json);
var element = jsonDoc.SelectElement(jsonPath); //returns JsonElement
var elements = jsonDoc.SelectElements(jsonPath); //returns JsonElement.ArrayEnumerator

Thanks.

@ahsonkhan
Copy link
Contributor

Is JsonPath support for querying the JsonDocument/JsonElement structures still being looked at

This issue is specifically about storing the JSON path information as part of the exception whenever the JsonDocument sees an invalid JSON while parsing, and not about full-fledged JsonPath support for querying the JSON data within the JsonDocument. Since this would be a separate feature, please create a separate issue for it. I would imagine the path support (at least for your use case), primarily be for convenience (requiring less code).

@ahsonkhan ahsonkhan changed the title Support JSONPath on JsonDocument Add JSONPath to the exception thrown by JsonDocument while parsing invalid JSON Oct 4, 2019
@gary-holland
Copy link

Thanks. @ahsonkhan. I've created a new issue dotnet/corefx#41537

@msftgits msftgits transferred this issue from dotnet/corefx Feb 1, 2020
@msftgits msftgits added this to the Future milestone Feb 1, 2020
@layomia layomia modified the milestones: Future, 5.0 Feb 21, 2020
@layomia layomia self-assigned this Feb 21, 2020
@layomia
Copy link
Contributor

layomia commented Feb 21, 2020

Triage: action item is to remove this test and close this issue.

[Fact]
[ActiveIssue("JsonElement needs to support Path")]
public static void ExtensionPropertyRoundTripFails()
{
try
{
JsonSerializer.Deserialize<ClassWithExtensionProperty>(@"{""MyNestedClass"":{""UnknownProperty"":bad}}");
Assert.True(false, "Expected JsonException was not thrown.");
}
catch (JsonException e)
{
// Until JsonElement supports populating Path ("UnknownProperty"), which will be prepended by the serializer ("MyNestedClass"), this will fail.
Assert.Equal("$.MyNestedClass.UnknownProperty", e.Path);
}
}

@anreton
Copy link

anreton commented Mar 29, 2020

It would be great if it was implemented.

For example, there is JsonConverter:

public class Options
{
	public Options(bool a, bool b, bool c)
	{
		this.A = a;
		this.B = b;
		this.C = c;
	}

	public bool A { get; }
	public bool B { get; }
	public bool C { get; }
}

public class OptionsJsonConverter : JsonConverter<Options>
{
	public override Options Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
	{
		using var jsonDocument = JsonDocument.ParseValue(ref reader);
		var rootElement = jsonDocument.RootElement;

		var a = rootElement.GetProperty("A").GetBoolean();
		var b = rootElement.GetProperty("B").GetBoolean();
		var c = rootElement.GetProperty("C").GetBoolean();

		return new Options(a, b, c);
	}

	public override void Write(Utf8JsonWriter writer, Options value, JsonSerializerOptions options)
	{
		writer.WriteStartObject();
		{
			writer.WriteBoolean(nameof(value.A), value.A);
			writer.WriteBoolean(nameof(value.B), value.B);
			writer.WriteBoolean(nameof(value.C), value.C);
		}
		writer.WriteEndObject();
	}
}

We are trying to deserialize:

const string json = @"""A"": 42";

var jsonOptions = new JsonSerializerOptions();
jsonOptions.Converters.Add(new OptionsJsonConverter());

var options = JsonSerializer.Deserialize<Options>(json, jsonOptions);

and get:

System.InvalidOperationException: 'The requested operation requires an element of type 'Object', but the target element has type 'String'.'

It would be great to get JsonException with the path and line number.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 13, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Text.Json enhancement Product code improvement that does NOT require public API changes/additions
Projects
None yet
8 participants