|
2 | 2 | // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
|
3 | 3 | // See the LICENSE file in the project root for more information.
|
4 | 4 |
|
5 |
| -#pragma warning disable IDE0005 |
6 | 5 | using System;
|
| 6 | + |
| 7 | +#if NETCOREAPP |
| 8 | + |
7 | 9 | using System.Buffers.Text;
|
| 10 | + |
| 11 | +#endif |
| 12 | + |
8 | 13 | using System.Globalization;
|
| 14 | + |
| 15 | +#if !NETCOREAPP |
| 16 | + |
9 | 17 | using System.Text;
|
| 18 | + |
| 19 | +#endif |
| 20 | + |
10 | 21 | using System.Text.Json;
|
11 | 22 | using System.Text.Json.Serialization;
|
12 |
| -using static Elastic.Clients.Elasticsearch.Serialization.JsonConstants; |
13 |
| -#pragma warning restore IDE0005 |
14 | 23 |
|
15 | 24 | namespace Elastic.Clients.Elasticsearch.Serialization;
|
16 | 25 |
|
17 | 26 | internal sealed class DoubleWithFractionalPortionConverter : JsonConverter<double>
|
18 | 27 | {
|
19 |
| - // We don't handle floating point literals (NaN, etc.) because for source serialization because Elasticsearch only support finite values for numeric fields. |
20 |
| - // We must handle the possibility of numbers as strings in the source however. |
21 |
| - |
22 | 28 | public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
23 | 29 | {
|
24 |
| - if (reader.TokenType == JsonTokenType.String && options.NumberHandling.HasFlag(JsonNumberHandling.AllowReadingFromString)) |
| 30 | + if (reader.TokenType != JsonTokenType.String) |
| 31 | + return reader.GetDouble(); |
| 32 | + |
| 33 | + if (options.NumberHandling.HasFlag(JsonNumberHandling.AllowNamedFloatingPointLiterals)) |
| 34 | + { |
| 35 | + // TODO: Handle 'reader.HasValueSequence' |
| 36 | + if (reader.ValueSpan.SequenceEqual(JsonConstants.LiteralNaN)) |
| 37 | + return float.NaN; |
| 38 | + if (reader.ValueSpan.SequenceEqual(JsonConstants.LiteralPositiveInfinity)) |
| 39 | + return float.PositiveInfinity; |
| 40 | + if (reader.ValueSpan.SequenceEqual(JsonConstants.LiteralNegativeInfinity)) |
| 41 | + return float.NegativeInfinity; |
| 42 | + } |
| 43 | + |
| 44 | + if (options.NumberHandling.HasFlag(JsonNumberHandling.AllowReadingFromString)) |
25 | 45 | {
|
26 | 46 | var value = reader.GetString();
|
27 | 47 |
|
28 |
| - if (!double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var parsedValue)) |
| 48 | + if (!double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) |
29 | 49 | ThrowHelper.ThrowJsonException($"Unable to parse '{value}' as a double.");
|
30 | 50 |
|
31 |
| - return parsedValue; |
| 51 | + return result; |
32 | 52 | }
|
33 | 53 |
|
34 | 54 | return reader.GetDouble();
|
35 | 55 | }
|
36 | 56 |
|
37 | 57 | public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
|
38 | 58 | {
|
39 |
| - Span<byte> utf8bytes = stackalloc byte[128]; // This is the size used in STJ for future proofing. https://github.com/dotnet/runtime/blob/dae6c2472b699b7cff2efeb5ce06b75c9551bc40/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs#L79 |
| 59 | + if (options.NumberHandling.HasFlag(JsonNumberHandling.AllowNamedFloatingPointLiterals)) |
| 60 | + { |
| 61 | + switch (value) |
| 62 | + { |
| 63 | + case double.NaN: |
| 64 | + writer.WriteStringValue(JsonConstants.EncodedNaN); |
| 65 | + return; |
40 | 66 |
|
41 |
| - // NOTE: This code is based on https://github.com/dotnet/runtime/blob/dae6c2472b699b7cff2efeb5ce06b75c9551bc40/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs#L79 |
| 67 | + case double.PositiveInfinity: |
| 68 | + writer.WriteStringValue(JsonConstants.EncodedPositiveInfinity); |
| 69 | + return; |
| 70 | + |
| 71 | + case double.NegativeInfinity: |
| 72 | + writer.WriteStringValue(JsonConstants.EncodedNegativeInfinity); |
| 73 | + return; |
| 74 | + } |
| 75 | + } |
42 | 76 |
|
43 |
| - // Frameworks that are not .NET Core 3.0 or higher do not produce round-trippable strings by |
44 |
| - // default. Further, the Utf8Formatter on older frameworks does not support taking a precision |
45 |
| - // specifier for 'G' nor does it represent other formats such as 'R'. As such, we duplicate |
46 |
| - // the .NET Core 3.0 logic of forwarding to the UTF16 formatter and transcoding it back to UTF8, |
47 |
| - // with some additional changes to remove dependencies on Span APIs which don't exist downlevel. |
| 77 | + if (options.NumberHandling.HasFlag(JsonNumberHandling.WriteAsString)) |
| 78 | + { |
| 79 | + // TODO: Implement as needed |
| 80 | + throw new NotImplementedException("The 'JsonNumberHandling.WriteAsString' is currently not supported."); |
| 81 | + } |
48 | 82 |
|
49 |
| - // PERFORMANCE: This code could be benchmarked and tweaked to make it faster. |
| 83 | + // This code is based on: |
| 84 | + // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs#L101 |
50 | 85 |
|
51 | 86 | #if NETCOREAPP
|
52 |
| - if (Utf8Formatter.TryFormat(value, utf8bytes, out var bytesWritten)) |
| 87 | + Span<byte> utf8Text = stackalloc byte[JsonConstants.MaximumFormatDoubleLength]; |
| 88 | + |
| 89 | + if (Utf8Formatter.TryFormat(value, utf8Text, out var bytesWritten, JsonConstants.DoubleStandardFormat)) |
53 | 90 | {
|
54 |
| - if (utf8bytes.IndexOfAny(NonIntegerBytes) == -1) |
| 91 | + if (utf8Text.IndexOfAny(JsonConstants.NonIntegerChars) == -1) |
55 | 92 | {
|
56 |
| - utf8bytes[bytesWritten++] = (byte)'.'; |
57 |
| - utf8bytes[bytesWritten++] = (byte)'0'; |
| 93 | + utf8Text[bytesWritten++] = (byte)'.'; |
| 94 | + utf8Text[bytesWritten++] = (byte)'0'; |
58 | 95 | }
|
59 | 96 |
|
60 |
| -#pragma warning disable IDE0057 // Use range operator |
61 |
| - writer.WriteRawValue(utf8bytes.Slice(0, bytesWritten), skipInputValidation: true); |
62 |
| -#pragma warning restore IDE0057 // Use range operator |
63 |
| - |
| 97 | + writer.WriteRawValue(utf8Text[..bytesWritten], true); |
64 | 98 | return;
|
65 | 99 | }
|
66 | 100 | #else
|
67 |
| - var utf16Text = value.ToString("G17", CultureInfo.InvariantCulture); |
| 101 | + var utf16Text = value.ToString(JsonConstants.DoubleFormatString, CultureInfo.InvariantCulture); |
| 102 | + if (utf16Text.IndexOfAny(JsonConstants.NonIntegerChars) == -1) |
| 103 | + { |
| 104 | + utf16Text += ".0"; |
| 105 | + } |
68 | 106 |
|
69 |
| - if (utf16Text.Length < utf8bytes.Length) |
| 107 | + try |
70 | 108 | {
|
71 |
| - try |
72 |
| - { |
73 |
| - var bytes = Encoding.UTF8.GetBytes(utf16Text); |
| 109 | + var utf8Text = Encoding.UTF8.GetBytes(utf16Text); |
74 | 110 |
|
75 |
| - if (bytes.Length < utf8bytes.Length) |
76 |
| - { |
77 |
| - bytes.CopyTo(utf8bytes); |
78 |
| - return; |
79 |
| - } |
80 |
| - } |
81 |
| - catch |
82 |
| - { |
83 |
| - // Swallow this and fall through to our general exception. |
84 |
| - } |
| 111 | + writer.WriteRawValue(utf8Text, true); |
| 112 | + return; |
| 113 | + } |
| 114 | + catch |
| 115 | + { |
| 116 | + // Swallow this and fall through to our general exception. |
85 | 117 | }
|
86 | 118 | #endif
|
87 | 119 |
|
88 |
| - ThrowHelper.ThrowJsonException($"Unable to serialize double value."); |
| 120 | + ThrowHelper.ThrowJsonException("Unable to serialize double value."); |
89 | 121 | }
|
90 | 122 | }
|
0 commit comments