From 10aa9f3364c0003028217f7260de932d92a7e7ed Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Sat, 13 Jul 2019 07:20:12 -0700 Subject: [PATCH] Change (string)null behaviors in Utf8JsonWriter ArgumentNullException when used as a property name, empty string as a property name if it's already in ReadOnlySpan. * WriteNull(string) * WriteBoolean(string, bool) * WriteNumber(string, *) * WriteString(string, *) * WriteStartArray(string) * WriteStartObject(string) * WritePropertyName(string) Emits the null literal (vs the empty string): * WriteStringValue(string) * WriteString(*, string) Since the writer will write null via WriteStringValue, the reader GetString() now will return null if TokenType is null (matches JsonElement behavior). Commit migrated from https://github.com/dotnet/corefx/commit/adce92338911eec22e5c8f9f071ece857de7d624 --- .../Text/Json/Reader/Utf8JsonReader.TryGet.cs | 11 +- .../Writer/Utf8JsonWriter.WriteProperties.Bytes.cs | 5 +- .../Utf8JsonWriter.WriteProperties.DateTime.cs | 5 +- ...tf8JsonWriter.WriteProperties.DateTimeOffset.cs | 5 +- .../Utf8JsonWriter.WriteProperties.Decimal.cs | 5 +- .../Utf8JsonWriter.WriteProperties.Double.cs | 5 +- .../Writer/Utf8JsonWriter.WriteProperties.Float.cs | 5 +- .../Writer/Utf8JsonWriter.WriteProperties.Guid.cs | 5 +- .../Utf8JsonWriter.WriteProperties.Literal.cs | 10 +- .../Utf8JsonWriter.WriteProperties.SignedNumber.cs | 10 +- .../Utf8JsonWriter.WriteProperties.String.cs | 103 ++++- ...tf8JsonWriter.WriteProperties.UnsignedNumber.cs | 10 +- .../Writer/Utf8JsonWriter.WriteValues.Comment.cs | 5 +- .../Writer/Utf8JsonWriter.WriteValues.String.cs | 17 +- .../src/System/Text/Json/Writer/Utf8JsonWriter.cs | 10 +- .../tests/Serialization/Value.WriteTests.cs | 5 + .../tests/Utf8JsonReaderTests.TryGet.cs | 13 +- .../System.Text.Json/tests/Utf8JsonReaderTests.cs | 1 + .../System.Text.Json/tests/Utf8JsonWriterTests.cs | 474 +++++++++++++++++++++ 19 files changed, 668 insertions(+), 36 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs index a4ff13a..c6700c5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs @@ -13,14 +13,23 @@ namespace System.Text.Json /// /// Parses the current JSON token value from the source, unescaped, and transcoded as a . /// + /// + /// Returns when is . + /// /// /// Thrown if trying to get the value of the JSON token that is not a string - /// (i.e. other than or ). + /// (i.e. other than , or + /// ). /// /// It will also throw when the JSON string contains invalid UTF-8 bytes, or invalid UTF-16 surrogates. /// public string GetString() { + if (TokenType == JsonTokenType.Null) + { + return null; + } + if (TokenType != JsonTokenType.String && TokenType != JsonTokenType.PropertyName) { throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.cs index cf10dcd..75ac62a6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.cs @@ -47,11 +47,14 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// public void WriteBase64String(string propertyName, ReadOnlySpan bytes) - => WriteBase64String(propertyName.AsSpan(), bytes); + => WriteBase64String((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), bytes); /// /// Writes the property name and raw bytes value (as a base 64 encoded JSON string) as part of a name/value pair of a JSON object. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs index b944e4a..12a90b7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs @@ -43,6 +43,9 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// @@ -51,7 +54,7 @@ namespace System.Text.Json /// The property name is escaped before writing. /// public void WriteString(string propertyName, DateTime value) - => WriteString(propertyName.AsSpan(), value); + => WriteString((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), value); /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs index 0e3f907..556e962 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs @@ -43,6 +43,9 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// @@ -51,7 +54,7 @@ namespace System.Text.Json /// The property name is escaped before writing. /// public void WriteString(string propertyName, DateTimeOffset value) - => WriteString(propertyName.AsSpan(), value); + => WriteString((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), value); /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs index 0b27787..39f2f24 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs @@ -43,6 +43,9 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// @@ -51,7 +54,7 @@ namespace System.Text.Json /// The property name is escaped before writing. /// public void WriteNumber(string propertyName, decimal value) - => WriteNumber(propertyName.AsSpan(), value); + => WriteNumber((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs index 0fb4ee4..c6f1d97 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs @@ -45,6 +45,9 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// @@ -53,7 +56,7 @@ namespace System.Text.Json /// The property name is escaped before writing. /// public void WriteNumber(string propertyName, double value) - => WriteNumber(propertyName.AsSpan(), value); + => WriteNumber((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs index 2c8898c..7553e38 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs @@ -45,6 +45,9 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// @@ -53,7 +56,7 @@ namespace System.Text.Json /// The property name is escaped before writing. /// public void WriteNumber(string propertyName, float value) - => WriteNumber(propertyName.AsSpan(), value); + => WriteNumber((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs index db80b2b..694f8ec 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs @@ -43,6 +43,9 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// @@ -51,7 +54,7 @@ namespace System.Text.Json /// The property name is escaped before writing. /// public void WriteString(string propertyName, Guid value) - => WriteString(propertyName.AsSpan(), value); + => WriteString((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), value); /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs index 7034339..d0db932 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs @@ -44,11 +44,14 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// public void WriteNull(string propertyName) - => WriteNull(propertyName.AsSpan()); + => WriteNull((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan()); /// /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object. @@ -136,11 +139,14 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// public void WriteBoolean(string propertyName, bool value) - => WriteBoolean(propertyName.AsSpan(), value); + => WriteBoolean((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), value); /// /// Writes the property name and value (as a JSON literal "true" or "false") as part of a name/value pair of a JSON object. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs index 3a64538..e651ffd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs @@ -43,6 +43,9 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// @@ -51,7 +54,7 @@ namespace System.Text.Json /// The property name is escaped before writing. /// public void WriteNumber(string propertyName, long value) - => WriteNumber(propertyName.AsSpan(), value); + => WriteNumber((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. @@ -126,6 +129,9 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// @@ -134,7 +140,7 @@ namespace System.Text.Json /// The property name is escaped before writing. /// public void WriteNumber(string propertyName, int value) - => WriteNumber(propertyName.AsSpan(), (long)value); + => WriteNumber((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), (long)value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs index cea0c82..5d77fd6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs @@ -42,11 +42,14 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// public void WritePropertyName(string propertyName) - => WritePropertyName(propertyName.AsSpan()); + => WritePropertyName((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan()); /// /// Writes the property name (as a JSON string) as the first part of a name/value pair of a JSON object. @@ -391,11 +394,14 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// public void WriteString(string propertyName, JsonEncodedText value) - => WriteString(propertyName.AsSpan(), value); + => WriteString((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), value); /// /// Writes the property name and string text value (as a JSON string) as part of a name/value pair of a JSON object. @@ -403,16 +409,39 @@ namespace System.Text.Json /// The property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. /// + /// /// The property name and value is escaped before writing. + /// + /// + /// If is the JSON null value is written, + /// as if were called. + /// /// /// /// Thrown when the specified property name or value is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// public void WriteString(string propertyName, string value) - => WriteString(propertyName.AsSpan(), value.AsSpan()); + { + if (propertyName == null) + { + throw new ArgumentNullException(nameof(propertyName)); + } + + if (value == null) + { + WriteNull(propertyName.AsSpan()); + } + else + { + WriteString(propertyName.AsSpan(), value.AsSpan()); + } + } /// /// Writes the property name and text value (as a JSON string) as part of a name/value pair of a JSON object. @@ -468,7 +497,14 @@ namespace System.Text.Json /// The JSON encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. /// - /// The property name should already be escaped when the instance of was created. The value is escaped before writing. + /// + /// The property name should already be escaped when the instance of was created. + /// The value is escaped before writing. + /// + /// + /// If is the JSON null value is written, + /// as if was called. + /// /// /// /// Thrown when the specified value is too large. @@ -477,7 +513,16 @@ namespace System.Text.Json /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// public void WriteString(JsonEncodedText propertyName, string value) - => WriteString(propertyName, value.AsSpan()); + { + if (value == null) + { + WriteNull(propertyName); + } + else + { + WriteString(propertyName, value.AsSpan()); + } + } /// /// Writes the pre-encoded property name and text value (as a JSON string) as part of a name/value pair of a JSON object. @@ -530,11 +575,14 @@ namespace System.Text.Json /// /// Thrown when the specified property name or value is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// public void WriteString(string propertyName, ReadOnlySpan value) - => WriteString(propertyName.AsSpan(), value); + => WriteString((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), value); /// /// Writes the UTF-8 property name and text value (as a JSON string) as part of a name/value pair of a JSON object. @@ -611,11 +659,14 @@ namespace System.Text.Json /// /// Thrown when the specified property name or value is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// public void WriteString(string propertyName, ReadOnlySpan utf8Value) - => WriteString(propertyName.AsSpan(), utf8Value); + => WriteString((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), utf8Value); /// /// Writes the property name and UTF-8 text value (as a JSON string) as part of a name/value pair of a JSON object. @@ -687,7 +738,13 @@ namespace System.Text.Json /// The property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. /// - /// The property name and value is escaped before writing. + /// + /// The property name and value are escaped before writing. + /// + /// + /// If is the JSON null value is written, + /// as if was called. + /// /// /// /// Thrown when the specified property name or value is too large. @@ -696,7 +753,16 @@ namespace System.Text.Json /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// public void WriteString(ReadOnlySpan propertyName, string value) - => WriteString(propertyName, value.AsSpan()); + { + if (value == null) + { + WriteNull(propertyName); + } + else + { + WriteString(propertyName, value.AsSpan()); + } + } /// /// Writes the UTF-8 property name and pre-encoded value (as a JSON string) as part of a name/value pair of a JSON object. @@ -744,7 +810,13 @@ namespace System.Text.Json /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. /// - /// The property name and value is escaped before writing. + /// + /// The property name and value are escaped before writing. + /// + /// + /// If is the JSON null value is written, + /// as if was called. + /// /// /// /// Thrown when the specified property name or value is too large. @@ -753,7 +825,16 @@ namespace System.Text.Json /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// public void WriteString(ReadOnlySpan utf8PropertyName, string value) - => WriteString(utf8PropertyName, value.AsSpan()); + { + if (value == null) + { + WriteNull(utf8PropertyName); + } + else + { + WriteString(utf8PropertyName, value.AsSpan()); + } + } private void WriteStringEscapeValueOnly(ReadOnlySpan escapedPropertyName, ReadOnlySpan utf8Value, int firstEscapeIndex) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs index b57ebea..94706ec 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs @@ -44,6 +44,9 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// @@ -53,7 +56,7 @@ namespace System.Text.Json /// [CLSCompliant(false)] public void WriteNumber(string propertyName, ulong value) - => WriteNumber(propertyName.AsSpan(), value); + => WriteNumber((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. @@ -131,6 +134,9 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// @@ -140,7 +146,7 @@ namespace System.Text.Json /// [CLSCompliant(false)] public void WriteNumber(string propertyName, uint value) - => WriteNumber(propertyName.AsSpan(), (ulong)value); + => WriteNumber((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan(), (ulong)value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs index baee4d2..ef7ab6b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs @@ -23,8 +23,11 @@ namespace System.Text.Json /// /// Thrown when the specified value is too large OR if the given string text value contains a comment delimiter (i.e. */). /// + /// + /// The parameter is . + /// public void WriteCommentValue(string value) - => WriteCommentValue(value.AsSpan()); + => WriteCommentValue((value ?? throw new ArgumentNullException(nameof(value))).AsSpan()); /// /// Writes the text value (as a JSON comment). diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs index 797b40d..6623b0c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs @@ -37,7 +37,11 @@ namespace System.Text.Json /// /// The value to be written as a UTF-8 transcoded JSON string element of a JSON array. /// - /// The value is escaped before writing. + /// The value is escaped before writing. + /// + /// If is the JSON null value is written, + /// as if was called. + /// /// /// /// Thrown when the specified value is too large. @@ -46,7 +50,16 @@ namespace System.Text.Json /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// public void WriteStringValue(string value) - => WriteStringValue(value.AsSpan()); + { + if (value == null) + { + WriteNullValue(); + } + else + { + WriteStringValue(value.AsSpan()); + } + } /// /// Writes the text value (as a JSON string) as an element of a JSON array. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs index 0954db1..29728fd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs @@ -718,12 +718,15 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 /// OR if this would result in an invalid JSON to be written (while validation is enabled). /// public void WriteStartArray(string propertyName) - => WriteStartArray(propertyName.AsSpan()); + => WriteStartArray((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan()); /// /// Writes the beginning of a JSON object with a property name as the key. @@ -735,12 +738,15 @@ namespace System.Text.Json /// /// Thrown when the specified property name is too large. /// + /// + /// The parameter is . + /// /// /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 /// OR if this would result in an invalid JSON to be written (while validation is enabled). /// public void WriteStartObject(string propertyName) - => WriteStartObject(propertyName.AsSpan()); + => WriteStartObject((propertyName ?? throw new ArgumentNullException(nameof(propertyName))).AsSpan()); /// /// Writes the beginning of a JSON array with a property name as the key. diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.cs index 2267749..222cd3b 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.cs @@ -29,6 +29,11 @@ namespace System.Text.Json.Serialization.Tests } { + string json = JsonSerializer.Serialize((string)null); + Assert.Equal("null", json); + } + + { Span json = JsonSerializer.SerializeToUtf8Bytes(1); Assert.Equal(Encoding.UTF8.GetBytes("1"), json.ToArray()); } diff --git a/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.TryGet.cs b/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.TryGet.cs index 555162e..a6fb1ce 100644 --- a/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.TryGet.cs +++ b/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.TryGet.cs @@ -722,7 +722,7 @@ namespace System.Text.Json.Tests [Fact] public static void InvalidConversion() { - string jsonString = "[\"stringValue\", true, /* Comment within */ 1234] // Comment outside"; + string jsonString = "[\"stringValue\", true, /* Comment within */ 1234, null] // Comment outside"; byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString); var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = JsonCommentHandling.Allow }); @@ -731,13 +731,14 @@ namespace System.Text.Json.Tests { if (json.TokenType != JsonTokenType.String) { - try + if (json.TokenType == JsonTokenType.Null) { - string value = json.GetString(); - Assert.True(false, "Expected GetString to throw InvalidOperationException due to mismatch token type."); + Assert.Null(json.GetString()); + } + else + { + JsonTestHelper.AssertThrows(json, (jsonReader) => jsonReader.GetString()); } - catch (InvalidOperationException) - { } try { diff --git a/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.cs b/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.cs index 532b0ea..a0cc38b 100644 --- a/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.cs +++ b/src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.cs @@ -3128,6 +3128,7 @@ namespace System.Text.Json.Tests { case JsonTokenType.Null: Assert.Equal(expectedString, Encoding.UTF8.GetString(json.ValueSpan.ToArray())); + Assert.Null(json.GetString()); foundPrimitiveValue = true; break; case JsonTokenType.Number: diff --git a/src/libraries/System.Text.Json/tests/Utf8JsonWriterTests.cs b/src/libraries/System.Text.Json/tests/Utf8JsonWriterTests.cs index 8b85008..c99e238 100644 --- a/src/libraries/System.Text.Json/tests/Utf8JsonWriterTests.cs +++ b/src/libraries/System.Text.Json/tests/Utf8JsonWriterTests.cs @@ -4821,6 +4821,480 @@ namespace System.Text.Json.Tests Assert.Equal(1, jsonUtf8.BytesCommitted); } + [Fact] + public static void WriteBase64String_NullPropertyName() + { + WriteNullPropertyName_Simple( + new byte[] { 0x01, 0x00, 0x01 }, + "\"AQAB\"", + (writer, name, value) => writer.WriteBase64String(name, value), + (writer, name, value) => writer.WriteBase64String(name, value), + (writer, name, value) => writer.WriteBase64String(name, value)); + } + + [Fact] + public static void WriteBoolean_NullPropertyName() + { + WriteNullPropertyName_Simple( + false, + "false", + (writer, name, value) => writer.WriteBoolean(name, value), + (writer, name, value) => writer.WriteBoolean(name, value), + (writer, name, value) => writer.WriteBoolean(name, value)); + } + + [Fact] + public static void WriteNull_NullPropertyName() + { + WriteNullPropertyName_NoValue( + "null", + cleanupAction: null, + (writer, name) => writer.WriteNull(name), + (writer, name) => writer.WriteNull(name), + (writer, name) => writer.WriteNull(name)); + } + + [Fact] + public static void WriteNumber_NullPropertyName_Decimal() + { + decimal numericValue = 1.04m; + + WriteNullPropertyName_Simple( + numericValue, + "1.04", + (writer, name, value) => writer.WriteNumber(name, value), + (writer, name, value) => writer.WriteNumber(name, value), + (writer, name, value) => writer.WriteNumber(name, value)); + } + + [Fact] + public static void WriteNumber_NullPropertyName_Double() + { + double numericValue = 1.05d; + + WriteNullPropertyName_Simple( + numericValue, + "1.05", + (writer, name, value) => writer.WriteNumber(name, value), + (writer, name, value) => writer.WriteNumber(name, value), + (writer, name, value) => writer.WriteNumber(name, value)); + } + + [Fact] + public static void WriteNumber_NullPropertyName_Int32() + { + int numericValue = 1048576; + + WriteNullPropertyName_Simple( + numericValue, + "1048576", + (writer, name, value) => writer.WriteNumber(name, value), + (writer, name, value) => writer.WriteNumber(name, value), + (writer, name, value) => writer.WriteNumber(name, value)); + } + + [Fact] + public static void WriteNumber_NullPropertyName_Int64() + { + long numericValue = 0x0100_0000_0000; + + WriteNullPropertyName_Simple( + numericValue, + "1099511627776", + (writer, name, value) => writer.WriteNumber(name, value), + (writer, name, value) => writer.WriteNumber(name, value), + (writer, name, value) => writer.WriteNumber(name, value)); + } + + [Fact] + public static void WriteNumber_NullPropertyName_Single() + { + float numericValue = 1e3f; + + WriteNullPropertyName_Simple( + numericValue, + "1000", + (writer, name, value) => writer.WriteNumber(name, value), + (writer, name, value) => writer.WriteNumber(name, value), + (writer, name, value) => writer.WriteNumber(name, value)); + } + + [Fact] + public static void WriteNumber_NullPropertyName_UInt32() + { + uint numericValue = 0x8000_0000; + + WriteNullPropertyName_Simple( + numericValue, + "2147483648", + (writer, name, value) => writer.WriteNumber(name, value), + (writer, name, value) => writer.WriteNumber(name, value), + (writer, name, value) => writer.WriteNumber(name, value)); + } + + [Fact] + public static void WriteNumber_NullPropertyName_UInt64() + { + ulong numericValue = ulong.MaxValue; + + WriteNullPropertyName_Simple( + numericValue, + "18446744073709551615", + (writer, name, value) => writer.WriteNumber(name, value), + (writer, name, value) => writer.WriteNumber(name, value), + (writer, name, value) => writer.WriteNumber(name, value)); + } + + [Fact] + public static void WritePropertyName_NullPropertyName() + { + WriteNullPropertyName_NoValue( + "null", + writer => writer.WriteNullValue(), + (writer, name) => writer.WritePropertyName(name), + (writer, name) => writer.WritePropertyName(name), + (writer, name) => writer.WritePropertyName(name)); + } + + [Fact] + public static void WriteStartArray_NullPropertyName() + { + WriteNullPropertyName_NoValue( + "[]", + writer => writer.WriteEndArray(), + (writer, name) => writer.WriteStartArray(name), + (writer, name) => writer.WriteStartArray(name), + (writer, name) => writer.WriteStartArray(name)); + } + + [Fact] + public static void WriteStartObject_NullPropertyName() + { + WriteNullPropertyName_NoValue( + "{}", + writer => writer.WriteEndObject(), + (writer, name) => writer.WriteStartObject(name), + (writer, name) => writer.WriteStartObject(name), + (writer, name) => writer.WriteStartObject(name)); + } + + [Fact] + public static void WriteString_NullPropertyName_DateTime() + { + WriteNullPropertyName_Simple( + DateTime.MinValue, + "\"0001-01-01T00:00:00\"", + (writer, name, value) => writer.WriteString(name, value), + (writer, name, value) => writer.WriteString(name, value), + (writer, name, value) => writer.WriteString(name, value)); + } + + [Fact] + public static void WriteString_NullPropertyName_DateTimeOffset() + { + WriteNullPropertyName_Simple( + DateTimeOffset.MinValue, + "\"0001-01-01T00:00:00+00:00\"", + (writer, name, value) => writer.WriteString(name, value), + (writer, name, value) => writer.WriteString(name, value), + (writer, name, value) => writer.WriteString(name, value)); + } + + [Fact] + public static void WriteString_NullPropertyName_Guid() + { + WriteNullPropertyName_Simple( + Guid.Empty, + "\"00000000-0000-0000-0000-000000000000\"", + (writer, name, value) => writer.WriteString(name, value), + (writer, name, value) => writer.WriteString(name, value), + (writer, name, value) => writer.WriteString(name, value)); + } + + [Fact] + public static void WriteString_NullPropertyName_ReadOnlySpan_Byte() + { + WriteNullPropertyName_Simple( + Encoding.UTF8.GetBytes("utf8"), + "\"utf8\"", + (writer, name, value) => writer.WriteString(name, value), + (writer, name, value) => writer.WriteString(name, value), + (writer, name, value) => writer.WriteString(name, value)); + } + + [Fact] + public static void WriteString_NullPropertyName_ReadOnlySpan_Char() + { + WriteNullPropertyName_Simple( + "utf16", + "\"utf16\"", + (writer, name, value) => writer.WriteString(name, value.AsSpan()), + (writer, name, value) => writer.WriteString(name, value.AsSpan()), + (writer, name, value) => writer.WriteString(name, value.AsSpan())); + } + + [Fact] + public static void WriteString_NullPropertyName_String() + { + WriteNullPropertyName_Simple( + "string", + "\"string\"", + (writer, name, value) => writer.WriteString(name, value), + (writer, name, value) => writer.WriteString(name, value), + (writer, name, value) => writer.WriteString(name, value)); + } + + [Fact] + public static void WriteString_NullPropertyName_JsonEncodedText() + { + WriteNullPropertyName_Simple( + JsonEncodedText.Encode("jet"), + "\"jet\"", + (writer, name, value) => writer.WriteString(name, value), + (writer, name, value) => writer.WriteString(name, value), + (writer, name, value) => writer.WriteString(name, value)); + } + + [Fact] + public static void WriteCommentValue_NullString() + { + // WriteCommentValue is sufficiently different (no comma after a legal value) + // that it doesn't warrant a helper for expansion. + var output = new ArrayBufferWriter(1024); + string nullString = null; + + using (var writer = new Utf8JsonWriter(output)) + { + writer.WriteStartArray(); + + AssertExtensions.Throws( + "value", + () => writer.WriteCommentValue(nullString)); + + ReadOnlySpan nullStringSpan = nullString.AsSpan(); + writer.WriteCommentValue(nullStringSpan); + + writer.WriteCommentValue(ReadOnlySpan.Empty); + + writer.WriteEndArray(); + writer.Flush(); + } + + AssertContents("[/**//**/]", output); + } + + [Fact] + public static void WriteStringValue_NullString() + { + WriteNullValue_InArray( + "\"\"", + "null", + (writer, value) => writer.WriteStringValue(value), + (writer, value) => writer.WriteStringValue(value), + (writer, value) => writer.WriteStringValue(value)); + } + + [Fact] + public static void WriteStringValue_StringProperty_NullString() + { + WriteNullValue_InObject( + "\"propStr\":\"\"", + "\"propStr\":null", + (writer, value) => writer.WriteString("propStr", value), + (writer, value) => writer.WriteString("propStr", value), + (writer, value) => writer.WriteString("propStr", value)); + } + + [Fact] + public static void WriteStringValue_ReadOnlySpanCharProperty_NullString() + { + WriteNullValue_InObject( + "\"propUtf16\":\"\"", + "\"propUtf16\":null", + (writer, value) => writer.WriteString("propUtf16".AsSpan(), value), + (writer, value) => writer.WriteString("propUtf16".AsSpan(), value), + (writer, value) => writer.WriteString("propUtf16".AsSpan(), value)); + } + + [Fact] + public static void WriteStringValue_ReadOnlySpanBytesProperty_NullString() + { + byte[] propertyName = Encoding.UTF8.GetBytes("propUtf8"); + + WriteNullValue_InObject( + "\"propUtf8\":\"\"", + "\"propUtf8\":null", + (writer, value) => writer.WriteString(propertyName, value), + (writer, value) => writer.WriteString(propertyName, value), + (writer, value) => writer.WriteString(propertyName, value)); + } + + [Fact] + public static void WriteStringValue_JsonEncodedTextProperty_NullString() + { + JsonEncodedText jet = JsonEncodedText.Encode("propJet"); + + WriteNullValue_InObject( + "\"propJet\":\"\"", + "\"propJet\":null", + (writer, value) => writer.WriteString(jet, value), + (writer, value) => writer.WriteString(jet, value), + (writer, value) => writer.WriteString(jet, value)); + } + + private delegate void WriteValueSpanAction( + Utf8JsonWriter writer, + ReadOnlySpan value); + + private delegate void WritePropertySpanAction( + Utf8JsonWriter writer, + ReadOnlySpan propertyName); + + private delegate void WritePropertySpanAction( + Utf8JsonWriter writer, + ReadOnlySpan propertyName, + T2 value); + + private static void WriteNullPropertyName_Simple( + T value, + string wireValue, + Action stringAction, + WritePropertySpanAction charSpanAction, + WritePropertySpanAction byteSpanAction) + { + var output = new ArrayBufferWriter(1024); + string nullString = null; + + using (var writer = new Utf8JsonWriter(output)) + { + writer.WriteStartObject(); + + AssertExtensions.Throws( + "propertyName", + () => stringAction(writer, nullString, value)); + + writer.WriteEndObject(); + writer.Flush(); + } + + AssertContents("{}", output); + output.Clear(); + + using (var writer = new Utf8JsonWriter(output)) + { + writer.WriteStartObject(); + + ReadOnlySpan nullStringSpan = nullString.AsSpan(); + charSpanAction(writer, nullStringSpan, value); + + byteSpanAction(writer, ReadOnlySpan.Empty, value); + + writer.WriteEndObject(); + writer.Flush(); + } + + AssertContents($"{{\"\":{wireValue},\"\":{wireValue}}}", output); + } + + private static void WriteNullPropertyName_NoValue( + string wireValue, + Action cleanupAction, + Action stringAction, + WritePropertySpanAction charSpanAction, + WritePropertySpanAction byteSpanAction) + { + var output = new ArrayBufferWriter(1024); + string nullString = null; + + using (var writer = new Utf8JsonWriter(output)) + { + writer.WriteStartObject(); + + AssertExtensions.Throws( + "propertyName", + () => stringAction(writer, nullString)); + + writer.WriteEndObject(); + writer.Flush(); + } + + AssertContents("{}", output); + output.Clear(); + + using (var writer = new Utf8JsonWriter(output)) + { + writer.WriteStartObject(); + + ReadOnlySpan nullStringSpan = nullString.AsSpan(); + charSpanAction(writer, nullStringSpan); + cleanupAction?.Invoke(writer); + + byteSpanAction(writer, ReadOnlySpan.Empty); + cleanupAction?.Invoke(writer); + + writer.WriteEndObject(); + writer.Flush(); + } + + AssertContents($"{{\"\":{wireValue},\"\":{wireValue}}}", output); + } + + private static void WriteNullValue_InObject( + string wireValue, + string nullValue, + Action stringAction, + WriteValueSpanAction charSpanAction, + WriteValueSpanAction byteSpanAction) + { + var output = new ArrayBufferWriter(1024); + string nullString = null; + + using (var writer = new Utf8JsonWriter(output)) + { + writer.WriteStartObject(); + + stringAction(writer, nullString); + + ReadOnlySpan nullStringSpan = nullString.AsSpan(); + charSpanAction(writer, nullStringSpan); + + byteSpanAction(writer, ReadOnlySpan.Empty); + + writer.WriteEndObject(); + writer.Flush(); + } + + AssertContents($"{{{nullValue},{wireValue},{wireValue}}}", output); + } + + private static void WriteNullValue_InArray( + string wireValue, + string nullValue, + Action stringAction, + WriteValueSpanAction charSpanAction, + WriteValueSpanAction byteSpanAction) + { + var output = new ArrayBufferWriter(1024); + string nullString = null; + + using (var writer = new Utf8JsonWriter(output)) + { + writer.WriteStartArray(); + + stringAction(writer, nullString); + + ReadOnlySpan nullStringSpan = nullString.AsSpan(); + charSpanAction(writer, nullStringSpan); + + byteSpanAction(writer, ReadOnlySpan.Empty); + + writer.WriteEndArray(); + writer.Flush(); + } + + AssertContents($"[{nullValue},{wireValue},{wireValue}]", output); + } + private static string GetHelloWorldExpectedString(bool prettyPrint, string propertyName, string value) { var ms = new MemoryStream(); -- 2.7.4