Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

JsonArray and transformations API implementation#40469

Merged
kasiabulat merged 30 commits intodotnet:masterfrom
kasiabulat:kasiabulat/transformations
Aug 26, 2019
Merged

JsonArray and transformations API implementation#40469
kasiabulat merged 30 commits intodotnet:masterfrom
kasiabulat:kasiabulat/transformations

Conversation

@kasiabulat
Copy link
Copy Markdown
Contributor

I implemented JsonArray and transformations API for JsonNode. In order to do this, I needed to adjust other types as well. I also made some minor fixes and included some feedback from discussions. I still new to add much more tests and documentation to some of the methods, but I wanted to create this PR today to discuss new issues that came up during tomorrow meeting. I can split it in to couple of PRs later on, if the number of changes it too big there.

New open questions (I added them to the open questions section in specification as well):

  • Where do we want to enable user to set handling properties manner? Do we want to support it as JsonElement does not? (and currently JsonNode.Parse implementation uses JsonDocument.Parse)
  • Do we want to support transforming JsonObject into JSON string? Should ToString behave this way or do we want an additional method - e.g. GetJsonString (it might be confusing as we have a type JsonString) or GetJsonRepresenation?
  • AsJsonElement function on JsonNode currently does not work for null node. Do we want to support null case somehow?
  • Do we want separate JsonElement implementations for JsonNode and JsonDocument or can the implementation be mixed as it is now (with a lot of ifs in each method)?
  • Do we want separate JsonElement.ArrayEnumerator and JsonElement.ObjectEnumerator implementations for JsonNode and JsonDocument? Do we want separate methods in JsonElement returning strongly typed different implementations?
  • Is it OK that JsonElement.GetRawText does not return the raw text for JsonNode parent?
  • Should we support JsonElement.WriteTo for JsonNode parent?
  • Private property JsonElement.TokenType is currently throwing an InvalidCast exception for JsonNode parent in debugger. Is it OK?
  • Do we want both Clone and DeepCopy methods for JsonNode?
  • Are we OK with needing to cast JsonArray to JsonNode in order to add it to JsonObject? (there's an ambiguous call between JsonArray and IEnumerable<JsonNode> right now)
  • Do we want IsImmutable property for JsonElement?
  • Is DateTime for JsonString handled correctly?

addresses: #39922
cc: @joperezr @bartonjs @ericstj @ahsonkhan @terrajobst @JamesNK @stephentoub

Comment thread src/System.Text.Json/src/System/Text/Json/Node/JsonArray.cs
Comment thread src/System.Text.Json/src/System/Text/Json/Node/JsonArray.cs Outdated
Comment thread src/System.Text.Json/src/System/Text/Json/Node/JsonArray.cs Outdated
Comment thread src/System.Text.Json/ref/System.Text.Json.cs
Comment thread src/System.Text.Json/ref/System.Text.Json.cs
@ahsonkhan ahsonkhan added this to the 5.0 milestone Aug 20, 2019
Comment thread src/System.Text.Json/src/System/Text/Json/Node/JsonArray.cs Outdated
Copy link
Copy Markdown
Member

@bartonjs bartonjs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the ref and src changes. Test changes to follow.

Comment thread src/System.Text.Json/docs/writable_json_dom_spec.md Outdated
Comment thread src/System.Text.Json/docs/writable_json_dom_spec.md
Comment thread src/System.Text.Json/docs/writable_json_dom_spec.md Outdated
Comment thread src/System.Text.Json/docs/writable_json_dom_spec.md Outdated
Comment thread src/System.Text.Json/docs/writable_json_dom_spec.md Outdated
Comment thread src/System.Text.Json/src/System/Text/Json/Node/JsonObject.cs Outdated
Comment thread src/System.Text.Json/src/System/Text/Json/Node/JsonObject.cs Outdated
Comment thread src/System.Text.Json/src/System/Text/Json/Node/JsonString.cs Outdated
Comment thread src/System.Text.Json/src/System/Text/Json/Node/JsonString.cs Outdated
Comment thread src/System.Text.Json/src/System/Text/Json/Node/JsonString.cs Outdated
@kasiabulat
Copy link
Copy Markdown
Contributor Author

I addressed review comments, made fixes and added documentation to JsonNode. I will add API (and specification) changes in new PR in case we want to come back to this version. I will change JsonElement.ObjectEnumerator not to use JsonObjectEnumerator, add more tests and improve existing ones in PR with API changes.

@kasiabulat
Copy link
Copy Markdown
Contributor Author

kasiabulat commented Aug 23, 2019

Do you think I can merge this PR? @ahsonkhan @bartonjs @joperezr

Comment thread src/System.Text.Json/src/System/Text/Json/Document/JsonElement.ArrayEnumerator.cs Outdated
Comment thread src/System.Text.Json/src/System/Text/Json/Document/JsonElement.ArrayEnumerator.cs Outdated
Comment thread src/System.Text.Json/src/System/Text/Json/Document/JsonElement.ArrayEnumerator.cs Outdated
Comment thread src/System.Text.Json/src/System/Text/Json/Document/JsonElement.ArrayEnumerator.cs Outdated
Comment thread src/System.Text.Json/src/System/Text/Json/Document/JsonElement.ArrayEnumerator.cs Outdated
Comment thread src/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs
Comment thread src/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs Outdated
}

var jsonNode = (JsonNode)_parent;
return jsonNode.ToString();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be ToJson(), unless that's being done at a later time (changes whether or not string values can be read, for one)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I don't have ToJson yet, but I'll add it later. I was wondering if we want this method supported for JsonNode at all if we actually aren't going to have "the original input data backing this value".

/// <see langword="true"/> if the value is successfully found in a JSON array,
/// <see langword="false"/> otherwise.
/// </returns>
public bool Contains(JsonNode value) => _list.Contains(value);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this method (and IndexOf) are doing reference equality, we should delete Contains. IndexOf, and LastIndexOf until we can come up with a stronger definition.

This is made worse by the implicit conversions, because array.Add(true), array.Contains(true) => false.

Comment thread src/System.Text.Json/src/System/Text/Json/Node/JsonString.cs Outdated
}
else
{
var jsonArray = (JsonArray)target._parent;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if I do the following:

JsonElement element = default;
foreach(var temp in element.EnumerateArray())
{
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will throw InvalidOperationException (the instance is invalid).

return jsonArray[_curIdx].AsJsonElement();
}

var document = (JsonDocument)_target._parent;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to measure the perf regression here, when enumerating a JSON array.

{
get
{
if (!_target.IsImmutable)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can we follow the existing pattern and use the if (target._parent is JsonDocument document) check rather than _target.IsImmutable, and do that up-front?

if (_curIdx < 0)
{
   return default;
}

if (target._parent is JsonDocument document)
{
   return new JsonProperty(new JsonElement(document, _curIdx));
}

Debug.Assert(!_target.IsImmutable);
//...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will be rewriting this enumerator when I will be adding API, but I will keep it in mind.


var jsonNode = (JsonNode)_parent;

if (jsonNode == null)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not be required once we have a JsonNull type, correct?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.


var jsonNode = (JsonNode)_parent;

if (jsonNode is JsonArray jsonArray)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be simplified as follows (and remove jsonNode):

if (_parent is JsonArray jsonArray)
{
 ...
}


var jsonNode = (JsonNode)_parent;

if (jsonNode is JsonObject jsonObject)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here/elsewhere. Seems like casting to JsonNode is unnecessary/redundant. Cast directly to JsonObject.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to cast it to JsonNode in order to call jsonNode.ValueKind and throw appropriate exception.

Copy link
Copy Markdown

@ahsonkhan ahsonkhan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@kasiabulat kasiabulat merged commit ad4fdd9 into dotnet:master Aug 26, 2019
JsonArray jsonArray = new JsonArray();
foreach (JsonElement element in jsonElement.EnumerateArray())
{
jsonArray.Add(DeepCopy(element));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it OK to have these unbounded recursive calls based on user input? What happens if I pass a payload containing 1_000 nested deep json array as a JsonElement to DeepCopy?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I can add depth check.

Copy link
Copy Markdown

@ahsonkhan ahsonkhan Aug 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently stack overflows:

var builder = new StringBuilder();
for (int i = 0; i < 2_000; i++)
{
    builder.Append("[");
}

for (int i = 0; i < 2_000; i++)
{
    builder.Append("]");
}

var options = new JsonDocumentOptions { MaxDepth = 5_000 };
using (JsonDocument dom = JsonDocument.Parse(builder.ToString(), options))
{
    JsonNode node = JsonNode.DeepCopy(dom.RootElement);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I can rewrite it to use my own stack instead of recursion.

/// </summary>
/// <param name="value">The value to represent as a JSON string.</param>
public JsonString(Guid value) => Value = value.ToString();
public JsonString(Guid value) => Value = value.ToString("D");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be CultureInfo.InvariantCulture as well?

}

[Fact]
public static void TestClean()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: TestClear

Suggested change
public static void TestClean()
public static void TestClear()

/// {
/// "software developers" :
/// {
/// "full time employees" : /JsonObject of 3 employees fromk database/
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Same below

Suggested change
/// "full time employees" : /JsonObject of 3 employees fromk database/
/// "full time employees" : /JsonObject of 3 employees from database/

public static IEnumerable<object[]> DateTimeData =>
new List<object[]>
{
new object[] { new DateTime(DateTime.MinValue.Ticks, DateTimeKind.Utc) },
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: spacing. Why is this indented 2 levels deep?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix it.

<Compile Include="JsonEncodedTextTests.cs" />
<Compile Include="JsonGuidTestData.cs" />
<Compile Include="JsonNodeToJsonElementTests.cs" />
<Compile Include="JsonNodeCloneTests.cs" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Move these to the other ItemGroup.

<ItemGroup>
<Compile Include="JsonArrayTests.cs" />
<Compile Include="JsonBooleanTests.cs" />
<Compile Include="JsonNodeTestData.cs" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Keep the similar filename structure as before, i.e. rename the file:
JsonNodeTests.TestData.cs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to reuse this class in both JsonArray and JsonObject tests.


namespace System.Text.Json.Tests
{
public static class JsonNodeToJsonElementTests
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: To me, these are all JsonNode tests. Why split it into separate classes: JsonNodeCloneTests, JsonNodeParseTests, JsonNodeToJsonElementTests.

Instead, make it a partial class JsonNodeTests and rename the files:
JsonNodeTests.Clone.cs
JsonNodeTests.Parse.cs
JsonNodeTests.ToJsonElement.cs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment: #40469 (comment), but right, I can make it one class.


/// <summary>
/// Initializes a new instance of the <see cref="JsonString"/> with a string representation of the <see cref="DateTime"/> structure.
/// Initializes a new instance of the <see cref="JsonString"/> with an ISO 8601 representation of the <see cref="DateTime"/> structure.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it matter what representation we store the DateTime in as, as long as it round-trips correctly when user tries to get the DateTime back? If so, maybe omit the ISO representations.

Also, is it accurate to state it's an ISO 8601 representation when we use "s"? I suppose "s" is a subset of the ISO support we have for other APIs, so it seems fine, but ISO allowed for a lot more representations than just "s".

https://github.com/dotnet/docs/blob/master/docs/standard/datetime/system-text-json-support.md#support-for-parsing

cc @layomia

@kasiabulat kasiabulat deleted the kasiabulat/transformations branch August 27, 2019 14:24
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
* JsonArray added
* Transformations API added
* Review Comments included

Commit migrated from dotnet/corefx@ad4fdd9
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants