Skip to content

Commit 4ea1713

Browse files
New JsonValue derived class for JSON primitives (#116798)
1 parent dda26de commit 4ea1713

File tree

8 files changed

+706
-61
lines changed

8 files changed

+706
-61
lines changed

src/libraries/System.Text.Json/src/System.Text.Json.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
8888
<Compile Include="System\Text\Json\Nodes\JsonValue.cs" />
8989
<Compile Include="System\Text\Json\Nodes\JsonValueOfTCustomized.cs" />
9090
<Compile Include="System\Text\Json\Nodes\JsonValueOfT.cs" />
91+
<Compile Include="System\Text\Json\Nodes\JsonValueOfJsonPrimitive.cs" />
9192
<Compile Include="System\Text\Json\Nodes\JsonValueOfTPrimitive.cs" />
9293
<Compile Include="System\Text\Json\Reader\ConsumeNumberResult.cs" />
9394
<Compile Include="System\Text\Json\Reader\ConsumeTokenResult.cs" />

src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -694,29 +694,7 @@ internal bool TryGetValue(int index, out Guid value)
694694
ReadOnlySpan<byte> data = _utf8Json.Span;
695695
ReadOnlySpan<byte> segment = data.Slice(row.Location, row.SizeOrLength);
696696

697-
if (segment.Length > JsonConstants.MaximumEscapedGuidLength)
698-
{
699-
value = default;
700-
return false;
701-
}
702-
703-
// Segment needs to be unescaped
704-
if (row.HasComplexChildren)
705-
{
706-
return JsonReaderHelper.TryGetEscapedGuid(segment, out value);
707-
}
708-
709-
Debug.Assert(segment.IndexOf(JsonConstants.BackSlash) == -1);
710-
711-
if (segment.Length == JsonConstants.MaximumFormatGuidLength
712-
&& Utf8Parser.TryParse(segment, out Guid tmp, out _, 'D'))
713-
{
714-
value = tmp;
715-
return true;
716-
}
717-
718-
value = default;
719-
return false;
697+
return JsonReaderHelper.TryGetValue(segment, row.HasComplexChildren, out value);
720698
}
721699

722700
internal string GetRawValueAsString(int index)
Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Buffers;
5+
using System.Buffers.Text;
6+
using System.Diagnostics;
7+
using System.Diagnostics.CodeAnalysis;
8+
using System.Text.Encodings.Web;
9+
10+
namespace System.Text.Json.Nodes
11+
{
12+
internal static class JsonValueOfJsonPrimitive
13+
{
14+
internal static JsonValue CreatePrimitiveValue(ref Utf8JsonReader reader, JsonNodeOptions options)
15+
{
16+
switch (reader.TokenType)
17+
{
18+
case JsonTokenType.False:
19+
case JsonTokenType.True:
20+
return new JsonValueOfJsonBool(reader.GetBoolean(), options);
21+
case JsonTokenType.String:
22+
byte[] buffer = new byte[reader.ValueLength];
23+
ReadOnlyMemory<byte> utf8String = buffer.AsMemory(0, reader.CopyString(buffer));
24+
return new JsonValueOfJsonString(utf8String, options);
25+
case JsonTokenType.Number:
26+
byte[] numberValue = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan.ToArray();
27+
return new JsonValueOfJsonNumber(numberValue, options);
28+
default:
29+
Debug.Fail("Only primitives allowed.");
30+
ThrowHelper.ThrowJsonException();
31+
return null!; // Unreachable, but required for compilation.
32+
}
33+
}
34+
35+
private sealed class JsonValueOfJsonString : JsonValue
36+
{
37+
private readonly ReadOnlyMemory<byte> _value;
38+
39+
internal JsonValueOfJsonString(ReadOnlyMemory<byte> utf8String, JsonNodeOptions? options)
40+
: base(options)
41+
{
42+
_value = utf8String;
43+
}
44+
45+
internal override JsonNode DeepCloneCore() => new JsonValueOfJsonString(_value, Options);
46+
private protected override JsonValueKind GetValueKindCore() => JsonValueKind.String;
47+
48+
public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
49+
{
50+
ArgumentNullException.ThrowIfNull(writer);
51+
52+
writer.WriteStringValue(_value.Span);
53+
}
54+
55+
public override T GetValue<T>()
56+
{
57+
if (!TryGetValue(out T? value))
58+
{
59+
ThrowHelper.ThrowInvalidOperationException_NodeUnableToConvertElement(JsonValueKind.String, typeof(T));
60+
}
61+
62+
return value;
63+
}
64+
65+
public override bool TryGetValue<T>([NotNullWhen(true)] out T? value)
66+
where T : default
67+
{
68+
if (typeof(T) == typeof(JsonElement))
69+
{
70+
value = (T)(object)JsonWriterHelper.WriteString(_value.Span, static serialized => JsonElement.Parse(serialized));
71+
return true;
72+
}
73+
74+
if (typeof(T) == typeof(string))
75+
{
76+
string? result = JsonReaderHelper.TranscodeHelper(_value.Span);
77+
78+
Debug.Assert(result != null);
79+
value = (T)(object)result;
80+
return true;
81+
}
82+
83+
bool success;
84+
85+
if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?))
86+
{
87+
success = JsonReaderHelper.TryGetValue(_value.Span, isEscaped: false, out DateTime result);
88+
value = (T)(object)result;
89+
return success;
90+
}
91+
92+
if (typeof(T) == typeof(DateTimeOffset) || typeof(T) == typeof(DateTimeOffset?))
93+
{
94+
success = JsonReaderHelper.TryGetValue(_value.Span, isEscaped: false, out DateTimeOffset result);
95+
value = (T)(object)result;
96+
return success;
97+
}
98+
99+
if (typeof(T) == typeof(Guid) || typeof(T) == typeof(Guid?))
100+
{
101+
success = JsonReaderHelper.TryGetValue(_value.Span, isEscaped: false, out Guid result);
102+
value = (T)(object)result;
103+
return success;
104+
}
105+
106+
if (typeof(T) == typeof(char) || typeof(T) == typeof(char?))
107+
{
108+
string? result = JsonReaderHelper.TranscodeHelper(_value.Span);
109+
110+
Debug.Assert(result != null);
111+
if (result.Length == 1)
112+
{
113+
value = (T)(object)result[0];
114+
return true;
115+
}
116+
}
117+
118+
value = default!;
119+
return false;
120+
}
121+
}
122+
123+
private sealed class JsonValueOfJsonBool : JsonValue
124+
{
125+
private readonly bool _value;
126+
127+
private JsonValueKind ValueKind => _value ? JsonValueKind.True : JsonValueKind.False;
128+
129+
internal JsonValueOfJsonBool(bool value, JsonNodeOptions? options)
130+
: base(options)
131+
{
132+
_value = value;
133+
}
134+
135+
public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null) => writer.WriteBooleanValue(_value);
136+
internal override JsonNode DeepCloneCore() => new JsonValueOfJsonBool(_value, Options);
137+
private protected override JsonValueKind GetValueKindCore() => ValueKind;
138+
139+
public override T GetValue<T>()
140+
{
141+
if (!TryGetValue(out T? value))
142+
{
143+
ThrowHelper.ThrowInvalidOperationException_NodeUnableToConvertElement(_value ? JsonValueKind.True : JsonValueKind.False, typeof(T));
144+
}
145+
146+
return value;
147+
}
148+
149+
public override bool TryGetValue<T>([NotNullWhen(true)] out T? value)
150+
where T : default
151+
{
152+
if (typeof(T) == typeof(JsonElement))
153+
{
154+
value = (T)(object)JsonElement.Parse(_value ? JsonConstants.TrueValue : JsonConstants.FalseValue);
155+
return true;
156+
}
157+
158+
if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?))
159+
{
160+
value = (T)(object)_value;
161+
return true;
162+
}
163+
164+
value = default!;
165+
return false;
166+
}
167+
}
168+
169+
private sealed class JsonValueOfJsonNumber : JsonValue
170+
{
171+
// This can be optimized to store the decimal point position and the exponent so that
172+
// conversion to different numeric types can be done without parsing the string again.
173+
// Utf8Parser uses an internal ref struct, Number.NumberBuffer, which is really the
174+
// same functionality that we would want here.
175+
private readonly byte[] _value;
176+
177+
internal JsonValueOfJsonNumber(byte[] number, JsonNodeOptions? options)
178+
: base(options)
179+
{
180+
_value = number;
181+
}
182+
183+
internal override JsonNode DeepCloneCore() => new JsonValueOfJsonNumber(_value, Options);
184+
private protected override JsonValueKind GetValueKindCore() => JsonValueKind.Number;
185+
186+
public override T GetValue<T>()
187+
{
188+
if (!TryGetValue(out T? value))
189+
{
190+
ThrowHelper.ThrowInvalidOperationException_NodeUnableToConvertElement(JsonValueKind.Number, typeof(T));
191+
}
192+
193+
return value;
194+
}
195+
196+
public override bool TryGetValue<T>([NotNullWhen(true)] out T? value)
197+
where T : default
198+
{
199+
if (typeof(T) == typeof(JsonElement))
200+
{
201+
value = (T)(object)JsonElement.Parse(_value);
202+
return true;
203+
}
204+
205+
bool success;
206+
207+
if (typeof(T) == typeof(int) || typeof(T) == typeof(int?))
208+
{
209+
success = Utf8Parser.TryParse(_value, out int result, out int consumed) &&
210+
consumed == _value.Length;
211+
212+
value = (T)(object)result;
213+
return success;
214+
}
215+
216+
if (typeof(T) == typeof(long) || typeof(T) == typeof(long?))
217+
{
218+
success = Utf8Parser.TryParse(_value, out long result, out int consumed) &&
219+
consumed == _value.Length;
220+
221+
value = (T)(object)result;
222+
return success;
223+
}
224+
225+
if (typeof(T) == typeof(double) || typeof(T) == typeof(double?))
226+
{
227+
success = Utf8Parser.TryParse(_value, out double result, out int consumed) &&
228+
consumed == _value.Length;
229+
230+
value = (T)(object)result;
231+
return success;
232+
}
233+
234+
if (typeof(T) == typeof(short) || typeof(T) == typeof(short?))
235+
{
236+
success = Utf8Parser.TryParse(_value, out short result, out int consumed) &&
237+
consumed == _value.Length;
238+
239+
value = (T)(object)result;
240+
return success;
241+
}
242+
243+
if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?))
244+
{
245+
success = Utf8Parser.TryParse(_value, out decimal result, out int consumed) &&
246+
consumed == _value.Length;
247+
248+
value = (T)(object)result;
249+
return success;
250+
}
251+
252+
if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?))
253+
{
254+
success = Utf8Parser.TryParse(_value, out byte result, out int consumed) &&
255+
consumed == _value.Length;
256+
257+
value = (T)(object)result;
258+
return success;
259+
}
260+
261+
if (typeof(T) == typeof(float) || typeof(T) == typeof(float?))
262+
{
263+
success = Utf8Parser.TryParse(_value, out float result, out int consumed) &&
264+
consumed == _value.Length;
265+
266+
value = (T)(object)result;
267+
return success;
268+
}
269+
270+
if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?))
271+
{
272+
success = Utf8Parser.TryParse(_value, out uint result, out int consumed) &&
273+
consumed == _value.Length;
274+
275+
value = (T)(object)result;
276+
return success;
277+
}
278+
279+
if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?))
280+
{
281+
success = Utf8Parser.TryParse(_value, out ushort result, out int consumed) &&
282+
consumed == _value.Length;
283+
284+
value = (T)(object)result;
285+
return success;
286+
}
287+
288+
if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?))
289+
{
290+
success = Utf8Parser.TryParse(_value, out ulong result, out int consumed) &&
291+
consumed == _value.Length;
292+
293+
value = (T)(object)result;
294+
return success;
295+
}
296+
297+
if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?))
298+
{
299+
success = Utf8Parser.TryParse(_value, out sbyte result, out int consumed) &&
300+
consumed == _value.Length;
301+
302+
value = (T)(object)result;
303+
return success;
304+
}
305+
306+
value = default!;
307+
return false;
308+
}
309+
310+
public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
311+
{
312+
ArgumentNullException.ThrowIfNull(writer);
313+
314+
writer.WriteNumberValue(_value);
315+
}
316+
}
317+
}
318+
}

src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,33 @@ public static bool TryGetEscapedDateTimeOffset(ReadOnlySpan<byte> source, out Da
175175
return false;
176176
}
177177

178+
public static bool TryGetValue(ReadOnlySpan<byte> segment, bool isEscaped, out Guid value)
179+
{
180+
if (segment.Length > JsonConstants.MaximumEscapedGuidLength)
181+
{
182+
value = default;
183+
return false;
184+
}
185+
186+
// Segment needs to be unescaped
187+
if (isEscaped)
188+
{
189+
return TryGetEscapedGuid(segment, out value);
190+
}
191+
192+
Debug.Assert(segment.IndexOf(JsonConstants.BackSlash) == -1);
193+
194+
if (segment.Length == JsonConstants.MaximumFormatGuidLength
195+
&& Utf8Parser.TryParse(segment, out Guid tmp, out _, 'D'))
196+
{
197+
value = tmp;
198+
return true;
199+
}
200+
201+
value = default;
202+
return false;
203+
}
204+
178205
public static bool TryGetEscapedGuid(ReadOnlySpan<byte> source, out Guid value)
179206
{
180207
Debug.Assert(source.Length <= JsonConstants.MaximumEscapedGuidLength);

0 commit comments

Comments
 (0)