Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
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 @@ -88,6 +88,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
<Compile Include="System\Text\Json\Nodes\JsonValue.cs" />
<Compile Include="System\Text\Json\Nodes\JsonValueOfTCustomized.cs" />
<Compile Include="System\Text\Json\Nodes\JsonValueOfT.cs" />
<Compile Include="System\Text\Json\Nodes\JsonValueOfJsonPrimitive.cs" />
<Compile Include="System\Text\Json\Nodes\JsonValueOfTPrimitive.cs" />
<Compile Include="System\Text\Json\Reader\ConsumeNumberResult.cs" />
<Compile Include="System\Text\Json\Reader\ConsumeTokenResult.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -694,29 +694,7 @@ internal bool TryGetValue(int index, out Guid value)
ReadOnlySpan<byte> data = _utf8Json.Span;
ReadOnlySpan<byte> segment = data.Slice(row.Location, row.SizeOrLength);

if (segment.Length > JsonConstants.MaximumEscapedGuidLength)
{
value = default;
return false;
}

// Segment needs to be unescaped
if (row.HasComplexChildren)
{
return JsonReaderHelper.TryGetEscapedGuid(segment, out value);
}

Debug.Assert(segment.IndexOf(JsonConstants.BackSlash) == -1);

if (segment.Length == JsonConstants.MaximumFormatGuidLength
&& Utf8Parser.TryParse(segment, out Guid tmp, out _, 'D'))
{
value = tmp;
return true;
}

value = default;
return false;
return JsonReaderHelper.TryGetValue(segment, row.HasComplexChildren, out value);
}

internal string GetRawValueAsString(int index)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Buffers.Text;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization.Metadata;

namespace System.Text.Json.Nodes
{
internal static class JsonValueOfJsonPrimitive
{
internal static JsonValue CreatePrimitiveValue(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.False:
case JsonTokenType.True:
return new JsonValueOfJsonBool(reader.GetBoolean(), options);
case JsonTokenType.String:
byte[] quotedStringValue;

if (reader.HasValueSequence)
{
// Throw if the long to int conversion fails. This can be accommodated in the future if needed by
// adding another derived class that has a backing ReadOnlySequence<byte> instead of a byte[].
int bufferLength = checked((int)(2 + reader.ValueSequence.Length));
quotedStringValue = new byte[bufferLength];
reader.ValueSequence.CopyTo(quotedStringValue.AsSpan().Slice(1, bufferLength - 2));
}
else
{
quotedStringValue = new byte[2 + reader.ValueSpan.Length];
reader.ValueSpan.CopyTo(quotedStringValue.AsSpan(1, reader.ValueSpan.Length));
}

quotedStringValue[0] = quotedStringValue[quotedStringValue.Length - 1] = JsonConstants.Quote;
return new JsonValueOfJsonString(quotedStringValue, reader.ValueIsEscaped, options);
case JsonTokenType.Number:
byte[] numberValue = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan.ToArray();
return new JsonValueOfJsonNumber(new JsonValueOfJsonNumber.JsonNumber(numberValue), options);
default:
Debug.Fail("Only primitives allowed.");
ThrowHelper.ThrowJsonException();
return null!; // Unreachable, but required for compilation.
}
}

private sealed class JsonValueOfJsonString : JsonValue
{
private readonly byte[] _value;
private readonly bool _isEscaped;
private readonly JsonSerializerOptions _serializerOptions;

internal JsonValueOfJsonString(byte[] value, bool isEscaped, JsonSerializerOptions options)
: base(options.GetNodeOptions())
{
_value = value;
_isEscaped = isEscaped;
_serializerOptions = options;
}

private ReadOnlySpan<byte> ValueWithoutQuotes => _value.AsSpan(1, _value.Length - 2);

internal override JsonNode DeepCloneCore() => new JsonValueOfJsonString(_value, _isEscaped, _serializerOptions);
private protected override JsonValueKind GetValueKindCore() => JsonValueKind.String;

public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
{
ArgumentNullException.ThrowIfNull(writer);

if (_isEscaped)
{
writer.WriteStringValue(JsonReaderHelper.GetUnescapedString(ValueWithoutQuotes));
}
else
{
writer.WriteStringValue(ValueWithoutQuotes);
}
}

public override T GetValue<T>()
{
if (!_serializerOptions.TryGetTypeInfo(typeof(T), out JsonTypeInfo? ti))
{
ThrowHelper.ThrowInvalidOperationException_NodeUnableToConvertElement(JsonValueKind.String, typeof(T));
}

JsonTypeInfo<T> typeInfo = (JsonTypeInfo<T>)ti;

// TODO how do we handle null?
return JsonSerializer.Deserialize(new ReadOnlySpan<byte>(_value), typeInfo)!;
}

public override bool TryGetValue<T>([NotNullWhen(true)] out T? value)
where T : default
{
if (!_serializerOptions.TryGetTypeInfo(typeof(T), out JsonTypeInfo? ti))
{
value = default!;
return false;
}

JsonTypeInfo<T> typeInfo = (JsonTypeInfo<T>)ti;

try
{
// TODO how do we handle null?
value = JsonSerializer.Deserialize(new ReadOnlySpan<byte>(_value), typeInfo)!;
return value != null;
}
catch (JsonException)
{
value = default!;
return false;
}
}
}

private sealed class JsonValueOfJsonBool : JsonValue
{
private readonly bool _value;
private readonly JsonSerializerOptions _serializerOptions;

private JsonValueKind ValueKind => _value ? JsonValueKind.True : JsonValueKind.False;

internal JsonValueOfJsonBool(bool value, JsonSerializerOptions options)
: base(options.GetNodeOptions())
{
_value = value;
_serializerOptions = options;
}

public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null) => writer.WriteBooleanValue(_value);
internal override JsonNode DeepCloneCore() => new JsonValueOfJsonBool(_value, _serializerOptions);
private protected override JsonValueKind GetValueKindCore() => ValueKind;

public override T GetValue<T>()
{
if (!_serializerOptions.TryGetTypeInfo(typeof(T), out JsonTypeInfo? ti))
{
ThrowHelper.ThrowInvalidOperationException_NodeUnableToConvertElement(_value ? JsonValueKind.True : JsonValueKind.False, typeof(T));
}

JsonTypeInfo<T> typeInfo = (JsonTypeInfo<T>)ti;

// TODO how do we handle null?
return JsonSerializer.Deserialize(_value ? JsonConstants.TrueValue : JsonConstants.FalseValue, typeInfo)!;
}

public override bool TryGetValue<T>([NotNullWhen(true)] out T? value)
where T : default
{
if (!_serializerOptions.TryGetTypeInfo(typeof(T), out JsonTypeInfo? ti))
{
value = default!;
return false;
}

JsonTypeInfo<T> typeInfo = (JsonTypeInfo<T>)ti;

try
{
// TODO how do we handle null?
value = JsonSerializer.Deserialize(_value ? JsonConstants.TrueValue : JsonConstants.FalseValue, typeInfo)!;
return value != null;
}
catch (JsonException)
{
value = default!;
return false;
}
}
}

private sealed class JsonValueOfJsonNumber : JsonValue
{
private readonly JsonNumber _value;
private readonly JsonSerializerOptions _serializerOptions;

internal JsonValueOfJsonNumber(JsonNumber number, JsonSerializerOptions options)
: base(options.GetNodeOptions())
{
_value = number;
_serializerOptions = options;
}

internal override JsonNode DeepCloneCore() => new JsonValueOfJsonNumber(_value, _serializerOptions);
private protected override JsonValueKind GetValueKindCore() => JsonValueKind.Number;

public override T GetValue<T>()
{
if (!_serializerOptions.TryGetTypeInfo(typeof(T), out JsonTypeInfo? ti))
{
ThrowHelper.ThrowInvalidOperationException_NodeUnableToConvertElement(JsonValueKind.Number, typeof(T));
}

JsonTypeInfo<T> typeInfo = (JsonTypeInfo<T>)ti;

// TODO how do we handle null?
return JsonSerializer.Deserialize(new ReadOnlySpan<byte>(_value.Bytes), typeInfo)!;
}

public override bool TryGetValue<T>([NotNullWhen(true)] out T? value)
where T : default
{
if (!_serializerOptions.TryGetTypeInfo(typeof(T), out JsonTypeInfo? ti))
{
value = default!;
return false;
}

JsonTypeInfo<T> typeInfo = (JsonTypeInfo<T>)ti;

try
{
// TODO how do we handle null?
value = JsonSerializer.Deserialize(new ReadOnlySpan<byte>(_value.Bytes), typeInfo)!;
return value != null;
}
catch (JsonException)
{
value = default!;
return false;
}
}

public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
{
ArgumentNullException.ThrowIfNull(writer);

writer.WriteNumberValue(_value.Bytes);
}

// This can be optimized to also store the decimal point position and the exponent so that
// conversion to different numeric types can be done without parsing the string again.
// Utf8Parser uses an internal ref struct, Number.NumberBuffer, which is really the
// same functionality that we would want here.
internal readonly struct JsonNumber
{
internal byte[] Bytes { get; }

internal JsonNumber(byte[] bytes)
{
Bytes = bytes;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,33 @@ public static bool TryGetEscapedDateTimeOffset(ReadOnlySpan<byte> source, out Da
return false;
}

public static bool TryGetValue(ReadOnlySpan<byte> segment, bool isEscaped, out Guid value)
{
if (segment.Length > JsonConstants.MaximumEscapedGuidLength)
{
value = default;
return false;
}

// Segment needs to be unescaped
if (isEscaped)
{
return TryGetEscapedGuid(segment, out value);
}

Debug.Assert(segment.IndexOf(JsonConstants.BackSlash) == -1);

if (segment.Length == JsonConstants.MaximumFormatGuidLength
&& Utf8Parser.TryParse(segment, out Guid tmp, out _, 'D'))
{
value = tmp;
return true;
}

value = default;
return false;
}

public static bool TryGetEscapedGuid(ReadOnlySpan<byte> source, out Guid value)
{
Debug.Assert(source.Length <= JsonConstants.MaximumEscapedGuidLength);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1360,22 +1360,7 @@ internal bool TryGetGuidCore(out Guid value)
span = ValueSpan;
}

if (ValueIsEscaped)
{
return JsonReaderHelper.TryGetEscapedGuid(span, out value);
}

Debug.Assert(span.IndexOf(JsonConstants.BackSlash) == -1);

if (span.Length == JsonConstants.MaximumFormatGuidLength
&& Utf8Parser.TryParse(span, out Guid tmp, out _, 'D'))
{
value = tmp;
return true;
}

value = default;
return false;
return JsonReaderHelper.TryGetValue(span, ValueIsEscaped, out value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public override void Write(Utf8JsonWriter writer, JsonArray? value, JsonSerializ
case JsonTokenType.StartArray:
return options.AllowDuplicateProperties
? ReadAsJsonElement(ref reader, options.GetNodeOptions())
: ReadAsJsonNode(ref reader, options.GetNodeOptions());
: ReadAsJsonNode(ref reader, options);
case JsonTokenType.Null:
return null;
default:
Expand All @@ -41,11 +41,11 @@ internal static JsonArray ReadAsJsonElement(ref Utf8JsonReader reader, JsonNodeO
return new JsonArray(jElement, options);
}

internal static JsonArray ReadAsJsonNode(ref Utf8JsonReader reader, JsonNodeOptions options)
internal static JsonArray ReadAsJsonNode(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
Debug.Assert(reader.TokenType == JsonTokenType.StartArray);

JsonArray jArray = new JsonArray(options);
JsonArray jArray = new JsonArray(options.GetNodeOptions());

while (reader.Read())
{
Expand Down
Loading
Loading