JsonArray and transformations API implementation#40469
JsonArray and transformations API implementation#40469kasiabulat merged 30 commits intodotnet:masterfrom
Conversation
bartonjs
left a comment
There was a problem hiding this comment.
I reviewed the ref and src changes. Test changes to follow.
|
I addressed review comments, made fixes and added documentation to |
|
Do you think I can merge this PR? @ahsonkhan @bartonjs @joperezr |
| } | ||
|
|
||
| var jsonNode = (JsonNode)_parent; | ||
| return jsonNode.ToString(); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
| } | ||
| else | ||
| { | ||
| var jsonArray = (JsonArray)target._parent; |
There was a problem hiding this comment.
What happens if I do the following:
JsonElement element = default;
foreach(var temp in element.EnumerateArray())
{
}There was a problem hiding this comment.
It will throw InvalidOperationException (the instance is invalid).
| return jsonArray[_curIdx].AsJsonElement(); | ||
| } | ||
|
|
||
| var document = (JsonDocument)_target._parent; |
There was a problem hiding this comment.
It would be good to measure the perf regression here, when enumerating a JSON array.
| { | ||
| get | ||
| { | ||
| if (!_target.IsImmutable) |
There was a problem hiding this comment.
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);
//...There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
This will not be required once we have a JsonNull type, correct?
|
|
||
| var jsonNode = (JsonNode)_parent; | ||
|
|
||
| if (jsonNode is JsonArray jsonArray) |
There was a problem hiding this comment.
Can this be simplified as follows (and remove jsonNode):
if (_parent is JsonArray jsonArray)
{
...
}|
|
||
| var jsonNode = (JsonNode)_parent; | ||
|
|
||
| if (jsonNode is JsonObject jsonObject) |
There was a problem hiding this comment.
Same here/elsewhere. Seems like casting to JsonNode is unnecessary/redundant. Cast directly to JsonObject.
There was a problem hiding this comment.
I need to cast it to JsonNode in order to call jsonNode.ValueKind and throw appropriate exception.
| JsonArray jsonArray = new JsonArray(); | ||
| foreach (JsonElement element in jsonElement.EnumerateArray()) | ||
| { | ||
| jsonArray.Add(DeepCopy(element)); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Right, I can add depth check.
There was a problem hiding this comment.
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);
}There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
Should this be CultureInfo.InvariantCulture as well?
| } | ||
|
|
||
| [Fact] | ||
| public static void TestClean() |
There was a problem hiding this comment.
nit: TestClear
| public static void TestClean() | |
| public static void TestClear() |
| /// { | ||
| /// "software developers" : | ||
| /// { | ||
| /// "full time employees" : /JsonObject of 3 employees fromk database/ |
There was a problem hiding this comment.
nit: Same below
| /// "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) }, |
There was a problem hiding this comment.
nit: spacing. Why is this indented 2 levels deep?
| <Compile Include="JsonEncodedTextTests.cs" /> | ||
| <Compile Include="JsonGuidTestData.cs" /> | ||
| <Compile Include="JsonNodeToJsonElementTests.cs" /> | ||
| <Compile Include="JsonNodeCloneTests.cs" /> |
There was a problem hiding this comment.
nit: Move these to the other ItemGroup.
| <ItemGroup> | ||
| <Compile Include="JsonArrayTests.cs" /> | ||
| <Compile Include="JsonBooleanTests.cs" /> | ||
| <Compile Include="JsonNodeTestData.cs" /> |
There was a problem hiding this comment.
nit: Keep the similar filename structure as before, i.e. rename the file:
JsonNodeTests.TestData.cs
There was a problem hiding this comment.
I wanted to reuse this class in both JsonArray and JsonObject tests.
|
|
||
| namespace System.Text.Json.Tests | ||
| { | ||
| public static class JsonNodeToJsonElementTests |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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".
cc @layomia
* JsonArray added * Transformations API added * Review Comments included Commit migrated from dotnet/corefx@ad4fdd9
I implemented
JsonArrayand transformations API forJsonNode. 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):
JsonElementdoes not? (and currentlyJsonNode.Parseimplementation usesJsonDocument.Parse)JsonObjectinto JSON string? ShouldToStringbehave this way or do we want an additional method - e.g.GetJsonString(it might be confusing as we have a typeJsonString) orGetJsonRepresenation?AsJsonElementfunction onJsonNodecurrently does not work fornullnode. Do we want to support null case somehow?JsonElementimplementations forJsonNodeandJsonDocumentor can the implementation be mixed as it is now (with a lot of ifs in each method)?JsonElement.ArrayEnumeratorandJsonElement.ObjectEnumeratorimplementations forJsonNodeandJsonDocument? Do we want separate methods inJsonElementreturning strongly typed different implementations?JsonElement.GetRawTextdoes not return the raw text forJsonNodeparent?JsonElement.WriteToforJsonNodeparent?JsonElement.TokenTypeis currently throwing anInvalidCastexception forJsonNodeparent in debugger. Is it OK?CloneandDeepCopymethods forJsonNode?JsonArraytoJsonNodein order to add it toJsonObject? (there's an ambiguous call betweenJsonArrayandIEnumerable<JsonNode>right now)IsImmutableproperty forJsonElement?DateTimeforJsonStringhandled correctly?addresses: #39922
cc: @joperezr @bartonjs @ericstj @ahsonkhan @terrajobst @JamesNK @stephentoub