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 JsonMarshal.GetRawUtf8Value #104595

Merged
merged 5 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
// Changes to this file must follow the https://aka.ms/api-review process.
// ------------------------------------------------------------------------------

namespace System.Runtime.InteropServices
{
public static partial class JsonMarshal
{
public static System.ReadOnlySpan<byte> GetRawUtf8Value(System.Text.Json.JsonElement element) { throw null; }
}
}
namespace System.Text.Json
{
public enum JsonCommentHandling : byte
Expand Down
1 change: 1 addition & 0 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
<Compile Include="..\Common\JsonUnknownTypeHandling.cs" Link="Common\System\Text\Json\Serialization\JsonUnknownTypeHandling.cs" />
<Compile Include="..\Common\ReflectionExtensions.cs" Link="Common\System\Text\Json\Serialization\ReflectionExtensions.cs" />
<Compile Include="..\Common\ThrowHelper.cs" Link="Common\System\Text\Json\ThrowHelper.cs" />
<Compile Include="System\Runtime\InteropServices\JsonMarshal.cs" />
<Compile Include="System\Text\Json\AppContextSwitchHelper.cs" />
<Compile Include="System\Text\Json\BitStack.cs" />
<Compile Include="System\Text\Json\Document\JsonDocument.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;

namespace System.Runtime.InteropServices
{
/// <summary>
/// An unsafe class that provides a set of methods to access the underlying data representations of JSON types.
/// </summary>
public static class JsonMarshal
{
/// <summary>
/// Gets a <see cref="ReadOnlySpan{T}"/> view over the raw JSON data of the given <see cref="JsonElement"/>.
/// </summary>
/// <param name="element">The JSON element from which to extract the span.</param>
/// <returns>The span containing the raw JSON data of<paramref name="element"/>.</returns>
/// <exception cref="ObjectDisposedException">The underlying <see cref="JsonDocument"/> has been disposed.</exception>
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
/// <remarks>
/// While the method itself does check for disposal of the underlying <see cref="JsonDocument"/>,
/// it is possible that it could be disposed after the method returns, which would result in
/// the span pointing to a buffer that has been returned to the shared pool. Callers should take
/// extra care to make sure that such a scenario isn't possible to avoid potential data corruption.
/// </remarks>
public static ReadOnlySpan<byte> GetRawUtf8Value(JsonElement element)
{
return element.GetRawValue().Span;
}
}
}
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Xunit;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace System.Text.Json.Tests
{
Expand Down Expand Up @@ -225,5 +226,134 @@ public static void TryParseValueInvalidDataFail(string json)

Assert.Equal(0, reader.BytesConsumed);
}

[Theory]
[InlineData("null")]
[InlineData("\r\n null ")]
[InlineData("false")]
[InlineData("true ")]
[InlineData(" 42.0 ")]
[InlineData(" \"str\" \r\n")]
[InlineData(" \"string with escaping: \\u0041\\u0042\\u0043\" \r\n")]
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
[InlineData(" [ ]")]
[InlineData(" [null, true, 42.0, \"str\", [], {}, ]")]
[InlineData(" { } ")]
[InlineData("""
{
/* I am a comment */
"key1" : 1,
"key2" : null,
"key3" : true,
}
""")]
public static void JsonMarshal_GetRawUtf8Value_RootValue_ReturnsFullValue(string json)
{
JsonDocumentOptions options = new JsonDocumentOptions { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip };
using JsonDocument jDoc = JsonDocument.Parse(json, options);
JsonElement element = jDoc.RootElement;

ReadOnlySpan<byte> rawValue = JsonMarshal.GetRawUtf8Value(element);
Assert.Equal(json.Trim(), Encoding.UTF8.GetString(rawValue.ToArray()));
}

[Fact]
public static void JsonMarshal_GetRawUtf8Value_NestedValues_ReturnsExpectedValue()
{
const string json = """
{
"date": "2021-06-01T00:00:00Z",
"temperatureC": 25,
"summary": "Hot",
/* The next property is a JSON object */
"nested": {
/* This is a nested JSON object */
"nestedDate": "2021-06-01T00:00:00Z",
"nestedTemperatureC": 25,
"nestedSummary": "Hot"
},
/* The next property is a JSON array */
"nestedArray": [
/* This is a JSON array */
{
"nestedDate": "2021-06-01T00:00:00Z",
"nestedTemperatureC": 25,
"nestedSummary": "Hot"
},
]
}
""";

JsonDocumentOptions options = new JsonDocumentOptions { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip };
using JsonDocument jDoc = JsonDocument.Parse(json, options);
JsonElement element = jDoc.RootElement;

AssertGetRawValue(json, element);
AssertGetRawValue("\"2021-06-01T00:00:00Z\"", element.GetProperty("date"));
AssertGetRawValue("25", element.GetProperty("temperatureC"));
AssertGetRawValue("\"Hot\"", element.GetProperty("summary"));

JsonElement nested = element.GetProperty("nested");
AssertGetRawValue("""
{
/* This is a nested JSON object */
"nestedDate": "2021-06-01T00:00:00Z",
"nestedTemperatureC": 25,
"nestedSummary": "Hot"
}
""", nested);

AssertGetRawValue("\"2021-06-01T00:00:00Z\"", nested.GetProperty("nestedDate"));
AssertGetRawValue("25", nested.GetProperty("nestedTemperatureC"));
AssertGetRawValue("\"Hot\"", nested.GetProperty("nestedSummary"));

JsonElement nestedArray = element.GetProperty("nestedArray");
AssertGetRawValue("""
[
/* This is a JSON array */
{
"nestedDate": "2021-06-01T00:00:00Z",
"nestedTemperatureC": 25,
"nestedSummary": "Hot"
},
]
""", nestedArray);

JsonElement nestedArrayElement = nestedArray[0];
AssertGetRawValue("""
{
"nestedDate": "2021-06-01T00:00:00Z",
"nestedTemperatureC": 25,
"nestedSummary": "Hot"
}
""", nestedArrayElement);

AssertGetRawValue("\"2021-06-01T00:00:00Z\"", nestedArrayElement.GetProperty("nestedDate"));
AssertGetRawValue("25", nestedArrayElement.GetProperty("nestedTemperatureC"));
AssertGetRawValue("\"Hot\"", nestedArrayElement.GetProperty("nestedSummary"));

static void AssertGetRawValue(string expectedJson, JsonElement element)
{
ReadOnlySpan<byte> rawValue = JsonMarshal.GetRawUtf8Value(element);
Assert.Equal(expectedJson.Trim(), Encoding.UTF8.GetString(rawValue.ToArray()));
}
}

[Fact]
public static void JsonMarshal_GetRawUtf8Value_DisposedDocument_ThrowsObjectDisposedException()
{
JsonDocument jDoc = JsonDocument.Parse("{}");
JsonElement element = jDoc.RootElement;
jDoc.Dispose();

Assert.Throws<ObjectDisposedException>(() => JsonMarshal.GetRawUtf8Value(element));
}
}
}
Loading