Add Encoder option to writer and serializer (dotnet/corefx#39524)
authorSteve Harter <steveharter@users.noreply.github.com>
Tue, 16 Jul 2019 16:29:20 +0000 (09:29 -0700)
committerGitHub <noreply@github.com>
Tue, 16 Jul 2019 16:29:20 +0000 (09:29 -0700)
Commit migrated from https://github.com/dotnet/corefx/commit/bc10b53838bc84ac2b626e16eee1338183ee55c8

35 files changed:
src/libraries/System.Text.Json/ref/System.Text.Json.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Escaping.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterOptions.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Bytes.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Helpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs
src/libraries/System.Text.Json/tests/JsonElementWriteTests.cs
src/libraries/System.Text.Json/tests/Serialization/OptionsTests.cs
src/libraries/System.Text.Json/tests/Utf8JsonWriterTests.cs

index b4120e2..36a42ce 100644 (file)
@@ -164,8 +164,8 @@ namespace System.Text.Json
         public bool NameEquals(System.ReadOnlySpan<byte> utf8Text) { throw null; }
         public bool NameEquals(System.ReadOnlySpan<char> text) { throw null; }
         public bool NameEquals(string text) { throw null; }
-        public void WriteTo(System.Text.Json.Utf8JsonWriter writer) { }
         public override string ToString() { throw null; }
+        public void WriteTo(System.Text.Json.Utf8JsonWriter writer) { }
     }
     public partial struct JsonReaderOptions
     {
@@ -185,20 +185,20 @@ namespace System.Text.Json
     {
         public static object Deserialize(System.ReadOnlySpan<byte> utf8Json, System.Type returnType, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
         public static object Deserialize(string json, System.Type returnType, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
-        public static TValue Deserialize<TValue>(System.ReadOnlySpan<byte> utf8Json, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
-        public static TValue Deserialize<TValue>(string json, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
+        public static object Deserialize(ref System.Text.Json.Utf8JsonReader reader, System.Type returnType, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
         public static System.Threading.Tasks.ValueTask<object> DeserializeAsync(System.IO.Stream utf8Json, System.Type returnType, System.Text.Json.JsonSerializerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.ValueTask<TValue> DeserializeAsync<TValue>(System.IO.Stream utf8Json, System.Text.Json.JsonSerializerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
-        public static object Deserialize(ref System.Text.Json.Utf8JsonReader reader, System.Type returnType, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
+        public static TValue Deserialize<TValue>(System.ReadOnlySpan<byte> utf8Json, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
+        public static TValue Deserialize<TValue>(string json, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
         public static TValue Deserialize<TValue>(ref System.Text.Json.Utf8JsonReader reader, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
-        public static string Serialize(object value, System.Type inputType, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
-        public static string Serialize<TValue>(TValue value, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
-        public static byte[] SerializeToUtf8Bytes(object value, System.Type inputType, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
-        public static byte[] SerializeToUtf8Bytes<TValue>(TValue value, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
-        public static System.Threading.Tasks.Task SerializeAsync(System.IO.Stream utf8Json, object value, System.Type inputType, System.Text.Json.JsonSerializerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public static string Serialize(object value, System.Type type, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
+        public static void Serialize(System.Text.Json.Utf8JsonWriter writer, object value, System.Type type, System.Text.Json.JsonSerializerOptions options = null) { }
+        public static System.Threading.Tasks.Task SerializeAsync(System.IO.Stream utf8Json, object value, System.Type type, System.Text.Json.JsonSerializerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public static System.Threading.Tasks.Task SerializeAsync<TValue>(System.IO.Stream utf8Json, TValue value, System.Text.Json.JsonSerializerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
-        public static void Serialize(System.Text.Json.Utf8JsonWriter writer, object value, System.Type inputType, System.Text.Json.JsonSerializerOptions options = null) { }
+        public static byte[] SerializeToUtf8Bytes(object value, System.Type type, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
+        public static byte[] SerializeToUtf8Bytes<TValue>(TValue value, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
         public static void Serialize<TValue>(System.Text.Json.Utf8JsonWriter writer, TValue value, System.Text.Json.JsonSerializerOptions options = null) { }
+        public static string Serialize<TValue>(TValue value, System.Text.Json.JsonSerializerOptions options = null) { throw null; }
     }
     public sealed partial class JsonSerializerOptions
     {
@@ -207,6 +207,7 @@ namespace System.Text.Json
         public System.Collections.Generic.IList<System.Text.Json.Serialization.JsonConverter> Converters { get { throw null; } }
         public int DefaultBufferSize { get { throw null; } set { } }
         public System.Text.Json.JsonNamingPolicy DictionaryKeyPolicy { get { throw null; } set { } }
+        public System.Text.Encodings.Web.JavaScriptEncoder Encoder { get { throw null; } set { } }
         public bool IgnoreNullValues { get { throw null; } set { } }
         public bool IgnoreReadOnlyProperties { get { throw null; } set { } }
         public int MaxDepth { get { throw null; } set { } }
@@ -244,7 +245,9 @@ namespace System.Text.Json
     }
     public partial struct JsonWriterOptions
     {
+        private object _dummy;
         private int _dummyPrimitive;
+        public System.Text.Encodings.Web.JavaScriptEncoder Encoder { readonly get { throw null; } set { } }
         public bool Indented { get { throw null; } set { } }
         public bool SkipValidation { get { throw null; } set { } }
     }
index fa679aa..d213e55 100644 (file)
@@ -6,6 +6,7 @@ using System.Collections.Generic;
 using System.Collections.Concurrent;
 using System.Diagnostics;
 using System.Text.Json.Serialization;
+using System.Text.Encodings.Web;
 
 namespace System.Text.Json
 {
@@ -25,6 +26,7 @@ namespace System.Text.Json
         private JsonNamingPolicy _dictionayKeyPolicy;
         private JsonNamingPolicy _jsonPropertyNamingPolicy;
         private JsonCommentHandling _readCommentHandling;
+        private JavaScriptEncoder _encoder;
         private int _defaultBufferSize = BufferSizeDefault;
         private int _maxDepth;
         private bool _allowTrailingCommas;
@@ -93,6 +95,23 @@ namespace System.Text.Json
         }
 
         /// <summary>
+        /// The encoder to use when escaping strings, or <see langword="null" /> to use the default encoder.
+        /// </summary>
+        public JavaScriptEncoder Encoder
+        {
+            get
+            {
+                return _encoder;
+            }
+            set
+            {
+                VerifyMutable();
+
+                _encoder = value;
+            }
+        }
+
+        /// <summary>
         /// Specifies the policy used to convert a <see cref="System.Collections.IDictionary"/> key's name to another format, such as camel-casing.
         /// </summary>
         /// <remarks>
@@ -324,6 +343,7 @@ namespace System.Text.Json
         {
             return new JsonWriterOptions
             {
+                Encoder = Encoder,
                 Indented = WriteIndented,
 #if !DEBUG
                 SkipValidation = true
index 891fcf6..4986bf0 100644 (file)
@@ -6,7 +6,9 @@ using System.Buffers;
 using System.Buffers.Text;
 using System.Diagnostics;
 using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 using System.Text.Encodings.Web;
+using System.Text.Unicode;
 
 namespace System.Text.Json
 {
@@ -40,13 +42,19 @@ namespace System.Text.Json
             return AllowList[value] == 0;
         }
 
+        private static bool NeedsEscapingNoBoundsCheck(char value)
+        {
+            Debug.Assert(value <= LastAsciiCharacter);
+            return AllowList[value] == 0;
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private static bool NeedsEscaping(byte value) => value > LastAsciiCharacter || AllowList[value] == 0;
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private static bool NeedsEscaping(char value) => value > LastAsciiCharacter || AllowList[value] == 0;
 
-        public static int NeedsEscaping(ReadOnlySpan<byte> value, JavaScriptEncoder encoder = null)
+        public static int NeedsEscaping(ReadOnlySpan<byte> value, JavaScriptEncoder encoder)
         {
             if (encoder != null)
             {
@@ -68,8 +76,13 @@ namespace System.Text.Json
             return idx;
         }
 
-        public static int NeedsEscaping(ReadOnlySpan<char> value)
+        public static int NeedsEscaping(ReadOnlySpan<char> value, JavaScriptEncoder encoder)
         {
+            if (encoder != null)
+            {
+                return encoder.FindFirstCharacterToEncodeUtf8(MemoryMarshal.Cast<char, byte>(value));
+            }
+
             int idx;
             for (idx = 0; idx < value.Length; idx++)
             {
@@ -92,6 +105,24 @@ namespace System.Text.Json
             return firstIndexToEscape + JsonConstants.MaxExpansionFactorWhileEscaping * (textLength - firstIndexToEscape);
         }
 
+        private static void EscapeString(ReadOnlySpan<byte> value, Span<byte> destination, JavaScriptEncoder encoder, ref int written)
+        {
+            Debug.Assert(encoder != null);
+
+            OperationStatus result = encoder.EncodeUtf8(value, destination, out int encoderBytesConsumed, out int encoderBytesWritten);
+
+            Debug.Assert(result != OperationStatus.DestinationTooSmall);
+            Debug.Assert(result != OperationStatus.NeedMoreData);
+            Debug.Assert(encoderBytesConsumed == value.Length);
+
+            if (result != OperationStatus.Done)
+            {
+                ThrowHelper.ThrowArgumentException_InvalidUTF8(value.Slice(encoderBytesWritten));
+            }
+
+            written += encoderBytesWritten;
+        }
+
         public static void EscapeString(ReadOnlySpan<byte> value, Span<byte> destination, int indexOfFirstByteToEscape, JavaScriptEncoder encoder, out int written)
         {
             Debug.Assert(indexOfFirstByteToEscape >= 0 && indexOfFirstByteToEscape < value.Length);
@@ -102,19 +133,9 @@ namespace System.Text.Json
 
             if (encoder != null)
             {
-                OperationStatus result = encoder.EncodeUtf8(
-                    value.Slice(consumed), destination.Slice(written), out int encoderBytesConsumed, out int encoderBytesWritten);
-
-                Debug.Assert(result != OperationStatus.DestinationTooSmall);
-                Debug.Assert(result != OperationStatus.NeedMoreData);
-                Debug.Assert(encoderBytesConsumed == value.Length - consumed);
-
-                if (result != OperationStatus.Done)
-                {
-                    ThrowHelper.ThrowArgumentException_InvalidUTF8(value.Slice(encoderBytesWritten));
-                }
-
-                written += encoderBytesWritten;
+                destination = destination.Slice(indexOfFirstByteToEscape);
+                value = value.Slice(indexOfFirstByteToEscape);
+                EscapeString(value, destination, encoder, ref written);
             }
             else
             {
@@ -139,21 +160,11 @@ namespace System.Text.Json
                     }
                     else
                     {
-                        // Fall back to default encoder
-                        OperationStatus result = JavaScriptEncoder.Default.EncodeUtf8(
-                            value.Slice(consumed), destination.Slice(written), out int encoderBytesConsumed, out int encoderBytesWritten);
-
-                        Debug.Assert(result != OperationStatus.DestinationTooSmall);
-                        Debug.Assert(result != OperationStatus.NeedMoreData);
-                        Debug.Assert(encoderBytesConsumed == value.Length - consumed);
-
-                        if (result != OperationStatus.Done)
-                        {
-                            ThrowHelper.ThrowArgumentException_InvalidUTF8(value.Slice(encoderBytesConsumed));
-                        }
-
-                        consumed += encoderBytesConsumed;
-                        written += encoderBytesWritten;
+                        // Fall back to default encoder.
+                        destination = destination.Slice(written);
+                        value = value.Slice(consumed);
+                        EscapeString(value, destination, JavaScriptEncoder.Default, ref written);
+                        break;
                     }
                 }
             }
@@ -203,6 +214,8 @@ namespace System.Text.Json
 
         private static bool IsAsciiValue(byte value) => value <= LastAsciiCharacter;
 
+        private static bool IsAsciiValue(char value) => value <= LastAsciiCharacter;
+
         /// <summary>
         /// Returns <see langword="true"/> if <paramref name="value"/> is a UTF-8 continuation byte.
         /// A UTF-8 continuation byte is a byte whose value is in the range 0x80-0xBF, inclusive.
@@ -410,49 +423,148 @@ namespace System.Text.Json
             return SequenceValidity.Incomplete;
         }
 
-        public static void EscapeString(ReadOnlySpan<char> value, Span<char> destination, int indexOfFirstByteToEscape, out int written)
+        private static void EscapeString(ReadOnlySpan<char> value, Span<char> destination, JavaScriptEncoder encoder, ref int written)
         {
-            Debug.Assert(indexOfFirstByteToEscape >= 0 && indexOfFirstByteToEscape < value.Length);
+            // todo: issue #39523: add an Encode(ReadOnlySpan<char>) decode API to System.Text.Encodings.Web.TextEncoding to avoid utf16->utf8->utf16 conversion.
 
-            value.Slice(0, indexOfFirstByteToEscape).CopyTo(destination);
-            written = indexOfFirstByteToEscape;
-            int consumed = indexOfFirstByteToEscape;
+            Debug.Assert(encoder != null);
 
-            while (consumed < value.Length)
+            // Convert char to byte.
+            byte[] utf8DestinationArray = null;
+            Span<byte> utf8Destination;
+            int length = checked((value.Length) * JsonConstants.MaxExpansionFactorWhileTranscoding);
+            if (length > JsonConstants.StackallocThreshold)
             {
-                char val = value[consumed];
-                if (NeedsEscaping(val))
+                utf8DestinationArray = ArrayPool<byte>.Shared.Rent(length);
+                utf8Destination = utf8DestinationArray;
+            }
+            else
+            {
+                unsafe
                 {
-                    EscapeNextChars(value, val, destination, ref consumed, ref written);
+                    byte* ptr = stackalloc byte[JsonConstants.StackallocThreshold];
+                    utf8Destination = new Span<byte>(ptr, JsonConstants.StackallocThreshold);
                 }
-                else
+            }
+
+            ReadOnlySpan<byte> utf16Value = MemoryMarshal.AsBytes(value);
+            OperationStatus toUtf8Status = ToUtf8(utf16Value, utf8Destination, out int bytesConsumed, out int bytesWritten);
+
+            Debug.Assert(toUtf8Status != OperationStatus.DestinationTooSmall);
+            Debug.Assert(toUtf8Status != OperationStatus.NeedMoreData);
+
+            if (toUtf8Status != OperationStatus.Done)
+            {
+                if (utf8DestinationArray != null)
                 {
-                    destination[written++] = val;
+                    utf8Destination.Slice(0, bytesWritten).Clear();
+                    ArrayPool<byte>.Shared.Return(utf8DestinationArray);
                 }
-                consumed++;
+
+                ThrowHelper.ThrowArgumentException_InvalidUTF8(utf16Value.Slice(bytesWritten));
             }
-        }
 
-        private static void EscapeNextChars(ReadOnlySpan<char> value, int firstChar, Span<char> destination, ref int consumed, ref int written)
-        {
-            int nextChar = -1;
-            if (JsonHelpers.IsInRangeInclusive(firstChar, JsonConstants.HighSurrogateStartValue, JsonConstants.LowSurrogateEndValue))
+            Debug.Assert(toUtf8Status == OperationStatus.Done);
+            Debug.Assert(bytesConsumed == utf16Value.Length);
+
+            // Escape the bytes.
+            byte[] utf8ConvertedDestinationArray = null;
+            Span<byte> utf8ConvertedDestination;
+            length = checked(bytesWritten * JsonConstants.MaxExpansionFactorWhileEscaping);
+            if (length > JsonConstants.StackallocThreshold)
+            {
+                utf8ConvertedDestinationArray = ArrayPool<byte>.Shared.Rent(length);
+                utf8ConvertedDestination = utf8ConvertedDestinationArray;
+            }
+            else
             {
-                consumed++;
-                if (value.Length <= consumed || firstChar >= JsonConstants.LowSurrogateStartValue)
+                unsafe
                 {
-                    ThrowHelper.ThrowArgumentException_InvalidUTF16(firstChar);
+                    byte* ptr = stackalloc byte[JsonConstants.StackallocThreshold];
+                    utf8ConvertedDestination = new Span<byte>(ptr, JsonConstants.StackallocThreshold);
                 }
+            }
+
+            EscapeString(utf8Destination.Slice(0, bytesWritten), utf8ConvertedDestination, indexOfFirstByteToEscape: 0, encoder, out int convertedBytesWritten);
 
-                nextChar = value[consumed];
-                if (!JsonHelpers.IsInRangeInclusive(nextChar, JsonConstants.LowSurrogateStartValue, JsonConstants.LowSurrogateEndValue))
+            if (utf8DestinationArray != null)
+            {
+                utf8Destination.Slice(0, bytesWritten).Clear();
+                ArrayPool<byte>.Shared.Return(utf8DestinationArray);
+            }
+
+            // Convert byte to char.
+#if BUILDING_INBOX_LIBRARY
+            OperationStatus toUtf16Status = Utf8.ToUtf16(utf8ConvertedDestination.Slice(0, convertedBytesWritten), destination, out int bytesRead, out int charsWritten);
+            Debug.Assert(toUtf16Status == OperationStatus.Done);
+            Debug.Assert(bytesRead == convertedBytesWritten);
+#else
+            string utf16 = JsonReaderHelper.GetTextFromUtf8(utf8ConvertedDestination.Slice(0, convertedBytesWritten));
+            utf16.AsSpan().CopyTo(destination);
+            int charsWritten = utf16.Length;
+#endif
+            written += charsWritten;
+
+            if (utf8ConvertedDestinationArray != null)
+            {
+                utf8ConvertedDestination.Slice(0, written).Clear();
+                ArrayPool<byte>.Shared.Return(utf8ConvertedDestinationArray);
+            }
+        }
+
+        public static void EscapeString(ReadOnlySpan<char> value, Span<char> destination, int indexOfFirstByteToEscape, JavaScriptEncoder encoder, out int written)
+        {
+            Debug.Assert(indexOfFirstByteToEscape >= 0 && indexOfFirstByteToEscape < value.Length);
+
+            value.Slice(0, indexOfFirstByteToEscape).CopyTo(destination);
+            written = indexOfFirstByteToEscape;
+            int consumed = indexOfFirstByteToEscape;
+
+            if (encoder != null)
+            {
+                destination = destination.Slice(indexOfFirstByteToEscape);
+                value = value.Slice(indexOfFirstByteToEscape);
+                EscapeString(value, destination, encoder, ref written);
+            }
+            else
+            {
+                // For performance when no encoder is specified, perform escaping here for Ascii and on the
+                // first occurrence of a non-Ascii character, then call into the default encoder.
+                while (consumed < value.Length)
                 {
-                    ThrowHelper.ThrowArgumentException_InvalidUTF16(nextChar);
+                    char val = value[consumed];
+                    if (IsAsciiValue(val))
+                    {
+                        if (NeedsEscapingNoBoundsCheck(val))
+                        {
+                            EscapeNextChars(val, destination, ref written);
+                            consumed++;
+                        }
+                        else
+                        {
+                            destination[written] = val;
+                            written++;
+                            consumed++;
+                        }
+                    }
+                    else
+                    {
+                        // Fall back to default encoder.
+                        destination = destination.Slice(written);
+                        value = value.Slice(consumed);
+                        EscapeString(value, destination, JavaScriptEncoder.Default, ref written);
+                        break;
+                    }
                 }
             }
+        }
+
+        private static void EscapeNextChars(char value, Span<char> destination, ref int written)
+        {
+            Debug.Assert(IsAsciiValue(value));
 
             destination[written++] = '\\';
-            switch (firstChar)
+            switch ((byte)value)
             {
                 case JsonConstants.Quote:
                     // Optimize for the common quote case.
@@ -483,24 +595,13 @@ namespace System.Text.Json
                 default:
                     destination[written++] = 'u';
 #if BUILDING_INBOX_LIBRARY
-                    firstChar.TryFormat(destination.Slice(written), out int charsWritten, HexFormatString);
+                    int intChar = value;
+                    intChar.TryFormat(destination.Slice(written), out int charsWritten, HexFormatString);
                     Debug.Assert(charsWritten == 4);
                     written += charsWritten;
 #else
-                    written = WriteHex(firstChar, destination, written);
-#endif
-                    if (nextChar != -1)
-                    {
-                        destination[written++] = '\\';
-                        destination[written++] = 'u';
-#if BUILDING_INBOX_LIBRARY
-                        nextChar.TryFormat(destination.Slice(written), out charsWritten, HexFormatString);
-                        Debug.Assert(charsWritten == 4);
-                        written += charsWritten;
-#else
-                        written = WriteHex(nextChar, destination, written);
+                    written = WriteHex(value, destination, written);
 #endif
-                    }
                     break;
             }
         }
index 2642899..26ca1df 100644 (file)
@@ -2,6 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Text.Encodings.Web;
+
 namespace System.Text.Json
 {
     /// <summary>
@@ -15,6 +17,11 @@ namespace System.Text.Json
         private int _optionsMask;
 
         /// <summary>
+        /// The encoder to use when escaping strings, or <see langword="null" /> to use the default encoder.
+        /// </summary>
+        public JavaScriptEncoder Encoder { get; set; }
+
+        /// <summary>
         /// Defines whether the <see cref="Utf8JsonWriter"/> should pretty print the JSON which includes:
         /// indenting nested JSON tokens, adding new lines, and adding white space between property names and values.
         /// By default, the JSON is written without any extra white space.
index d8dcec9..369d2b4 100644 (file)
@@ -106,7 +106,7 @@ namespace System.Text.Json
 
         private void WriteBase64Escape(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> bytes)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
@@ -122,7 +122,7 @@ namespace System.Text.Json
 
         private void WriteBase64Escape(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> bytes)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
@@ -149,7 +149,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (propertyArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteBase64ByOptions(escapedPropertyName.Slice(0, written), bytes);
 
@@ -172,7 +172,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteBase64ByOptions(escapedPropertyName.Slice(0, written), bytes);
 
@@ -185,7 +185,7 @@ namespace System.Text.Json
         private void WriteBase64ByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> bytes)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteBase64Indented(propertyName, bytes);
             }
@@ -198,7 +198,7 @@ namespace System.Text.Json
         private void WriteBase64ByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> bytes)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteBase64Indented(utf8PropertyName, bytes);
             }
@@ -304,7 +304,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -354,7 +354,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
index 8879cd9..fff19bb 100644 (file)
@@ -108,7 +108,7 @@ namespace System.Text.Json
 
         private void WriteStringEscape(ReadOnlySpan<char> propertyName, DateTime value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
@@ -124,7 +124,7 @@ namespace System.Text.Json
 
         private void WriteStringEscape(ReadOnlySpan<byte> utf8PropertyName, DateTime value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
@@ -151,7 +151,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (propertyArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -174,7 +174,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -187,7 +187,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<char> propertyName, DateTime value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringIndented(propertyName, value);
             }
@@ -200,7 +200,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, DateTime value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringIndented(utf8PropertyName, value);
             }
@@ -309,7 +309,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -361,7 +361,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
index 6bc2f1b..6462d89 100644 (file)
@@ -108,7 +108,7 @@ namespace System.Text.Json
 
         private void WriteStringEscape(ReadOnlySpan<char> propertyName, DateTimeOffset value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
@@ -124,7 +124,7 @@ namespace System.Text.Json
 
         private void WriteStringEscape(ReadOnlySpan<byte> utf8PropertyName, DateTimeOffset value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
@@ -151,7 +151,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (propertyArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -174,7 +174,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -187,7 +187,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<char> propertyName, DateTimeOffset value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringIndented(propertyName, value);
             }
@@ -200,7 +200,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, DateTimeOffset value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringIndented(utf8PropertyName, value);
             }
@@ -309,7 +309,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -361,7 +361,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
index 4e3203b..0b8722e 100644 (file)
@@ -108,7 +108,7 @@ namespace System.Text.Json
 
         private void WriteNumberEscape(ReadOnlySpan<char> propertyName, decimal value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
@@ -124,7 +124,7 @@ namespace System.Text.Json
 
         private void WriteNumberEscape(ReadOnlySpan<byte> utf8PropertyName, decimal value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
@@ -151,7 +151,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (propertyArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -174,7 +174,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -187,7 +187,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, decimal value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberIndented(propertyName, value);
             }
@@ -200,7 +200,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, decimal value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberIndented(utf8PropertyName, value);
             }
@@ -295,7 +295,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -340,7 +340,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
index 1b2078c..cd06e83 100644 (file)
@@ -112,7 +112,7 @@ namespace System.Text.Json
 
         private void WriteNumberEscape(ReadOnlySpan<char> propertyName, double value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
@@ -128,7 +128,7 @@ namespace System.Text.Json
 
         private void WriteNumberEscape(ReadOnlySpan<byte> utf8PropertyName, double value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
@@ -155,7 +155,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (propertyArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -178,7 +178,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -191,7 +191,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, double value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberIndented(propertyName, value);
             }
@@ -204,7 +204,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, double value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberIndented(utf8PropertyName, value);
             }
@@ -299,7 +299,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -344,7 +344,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
index 704b398..217d09a 100644 (file)
@@ -112,7 +112,7 @@ namespace System.Text.Json
 
         private void WriteNumberEscape(ReadOnlySpan<char> propertyName, float value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
@@ -128,7 +128,7 @@ namespace System.Text.Json
 
         private void WriteNumberEscape(ReadOnlySpan<byte> utf8PropertyName, float value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
@@ -155,7 +155,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (propertyArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -178,7 +178,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -191,7 +191,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, float value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberIndented(propertyName, value);
             }
@@ -204,7 +204,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, float value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberIndented(utf8PropertyName, value);
             }
@@ -299,7 +299,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -344,7 +344,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
index ddbf1db..9ed7bf5 100644 (file)
@@ -82,7 +82,7 @@ namespace System.Text.Json
 
         private void WriteNumberEscape(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
@@ -98,7 +98,7 @@ namespace System.Text.Json
 
         private void WriteNumberEscape(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
@@ -125,7 +125,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (propertyArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -148,7 +148,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -161,7 +161,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteLiteralIndented(propertyName, value);
             }
@@ -174,7 +174,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteLiteralIndented(utf8PropertyName, value);
             }
index 4d2f4fe..afee84c 100644 (file)
@@ -108,7 +108,7 @@ namespace System.Text.Json
 
         private void WriteStringEscape(ReadOnlySpan<char> propertyName, Guid value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
@@ -124,7 +124,7 @@ namespace System.Text.Json
 
         private void WriteStringEscape(ReadOnlySpan<byte> utf8PropertyName, Guid value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
@@ -151,7 +151,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (propertyArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -174,7 +174,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteStringByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -187,7 +187,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<char> propertyName, Guid value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringIndented(propertyName, value);
             }
@@ -200,7 +200,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, Guid value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringIndented(utf8PropertyName, value);
             }
@@ -303,7 +303,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -352,7 +352,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
index 27fa38c..96418aa 100644 (file)
@@ -35,7 +35,7 @@ namespace System.Text.Json
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private void ValidateWritingProperty()
         {
-            if (!Options.SkipValidation)
+            if (!_options.SkipValidation)
             {
                 if (!_inObject || _tokenType == JsonTokenType.PropertyName)
                 {
@@ -48,7 +48,7 @@ namespace System.Text.Json
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private void ValidateWritingProperty(byte token)
         {
-            if (!Options.SkipValidation)
+            if (!_options.SkipValidation)
             {
                 if (!_inObject || _tokenType == JsonTokenType.PropertyName)
                 {
@@ -109,7 +109,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -182,7 +182,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
index d17ee71..d08c468 100644 (file)
@@ -202,7 +202,7 @@ namespace System.Text.Json
 
         private void WriteLiteralEscape(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
@@ -218,7 +218,7 @@ namespace System.Text.Json
 
         private void WriteLiteralEscape(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
@@ -245,7 +245,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (propertyArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteLiteralByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -268,7 +268,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteLiteralByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -281,7 +281,7 @@ namespace System.Text.Json
         private void WriteLiteralByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteLiteralIndented(propertyName, value);
             }
@@ -294,7 +294,7 @@ namespace System.Text.Json
         private void WriteLiteralByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteLiteralIndented(utf8PropertyName, value);
             }
@@ -390,7 +390,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -435,7 +435,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
index eb9d10c..5b1360d 100644 (file)
@@ -180,7 +180,7 @@ namespace System.Text.Json
 
         private void WriteNumberEscape(ReadOnlySpan<char> propertyName, long value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
@@ -196,7 +196,7 @@ namespace System.Text.Json
 
         private void WriteNumberEscape(ReadOnlySpan<byte> utf8PropertyName, long value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
@@ -223,7 +223,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (propertyArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -246,7 +246,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -259,7 +259,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, long value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberIndented(propertyName, value);
             }
@@ -272,7 +272,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, long value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberIndented(utf8PropertyName, value);
             }
@@ -367,7 +367,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -412,7 +412,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
index bd92951..fee7418 100644 (file)
@@ -68,7 +68,7 @@ namespace System.Text.Json
         {
             JsonWriterHelper.ValidateProperty(propertyName);
 
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length && propertyIdx < int.MaxValue / 2);
 
@@ -110,7 +110,7 @@ namespace System.Text.Json
                     }
                 }
 
-                JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+                JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
                 propertyName = escapedPropertyName.Slice(0, written);
             }
 
@@ -125,7 +125,7 @@ namespace System.Text.Json
         private void WriteStringByOptionsPropertyName(ReadOnlySpan<char> propertyName)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringIndentedPropertyName(propertyName);
             }
@@ -221,7 +221,7 @@ namespace System.Text.Json
         {
             JsonWriterHelper.ValidateProperty(utf8PropertyName);
 
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length && propertyIdx < int.MaxValue / 2);
 
@@ -263,7 +263,7 @@ namespace System.Text.Json
                     }
                 }
 
-                JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+                JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
                 utf8PropertyName = escapedPropertyName.Slice(0, written);
             }
 
@@ -278,7 +278,7 @@ namespace System.Text.Json
         private void WriteStringByOptionsPropertyName(ReadOnlySpan<byte> utf8PropertyName)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringIndentedPropertyName(utf8PropertyName);
             }
@@ -339,7 +339,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -547,7 +547,7 @@ namespace System.Text.Json
 
             JsonWriterHelper.ValidateValue(value);
 
-            int valueIdx = JsonWriterHelper.NeedsEscaping(value);
+            int valueIdx = JsonWriterHelper.NeedsEscaping(value, _options.Encoder);
 
             Debug.Assert(valueIdx >= -1 && valueIdx < value.Length && valueIdx < int.MaxValue / 2);
 
@@ -631,7 +631,7 @@ namespace System.Text.Json
 
             JsonWriterHelper.ValidateValue(utf8Value);
 
-            int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value);
+            int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value, _options.Encoder);
 
             Debug.Assert(valueIdx >= -1 && valueIdx < utf8Value.Length && valueIdx < int.MaxValue / 2);
 
@@ -715,7 +715,7 @@ namespace System.Text.Json
 
             JsonWriterHelper.ValidateProperty(propertyName);
 
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length && propertyIdx < int.MaxValue / 2);
 
@@ -787,7 +787,7 @@ namespace System.Text.Json
 
             JsonWriterHelper.ValidateProperty(utf8PropertyName);
 
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length && propertyIdx < int.MaxValue / 2);
 
@@ -849,7 +849,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (valueArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndex, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndex, _options.Encoder, out int written);
 
             WriteStringByOptions(escapedPropertyName, escapedValue.Slice(0, written));
 
@@ -872,7 +872,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (valueArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndex, out int written);
+            JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndex, _options.Encoder, out int written);
 
             WriteStringByOptions(escapedPropertyName, escapedValue.Slice(0, written));
 
@@ -895,7 +895,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (propertyArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndex, out int written);
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndex, _options.Encoder, out int written);
 
             WriteStringByOptions(escapedPropertyName.Slice(0, written), escapedValue);
 
@@ -918,7 +918,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndex, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndex, _options.Encoder, out int written);
 
             WriteStringByOptions(escapedPropertyName.Slice(0, written), escapedValue);
 
@@ -930,8 +930,8 @@ namespace System.Text.Json
 
         private void WriteStringEscape(ReadOnlySpan<char> propertyName, ReadOnlySpan<char> value)
         {
-            int valueIdx = JsonWriterHelper.NeedsEscaping(value);
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int valueIdx = JsonWriterHelper.NeedsEscaping(value, _options.Encoder);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(valueIdx >= -1 && valueIdx < value.Length && valueIdx < int.MaxValue / 2);
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length && propertyIdx < int.MaxValue / 2);
@@ -949,8 +949,8 @@ namespace System.Text.Json
 
         private void WriteStringEscape(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> utf8Value)
         {
-            int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value);
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value, _options.Encoder);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(valueIdx >= -1 && valueIdx < utf8Value.Length && valueIdx < int.MaxValue / 2);
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length && propertyIdx < int.MaxValue / 2);
@@ -968,8 +968,8 @@ namespace System.Text.Json
 
         private void WriteStringEscape(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> utf8Value)
         {
-            int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value);
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value, _options.Encoder);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(valueIdx >= -1 && valueIdx < utf8Value.Length && valueIdx < int.MaxValue / 2);
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length && propertyIdx < int.MaxValue / 2);
@@ -987,8 +987,8 @@ namespace System.Text.Json
 
         private void WriteStringEscape(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<char> value)
         {
-            int valueIdx = JsonWriterHelper.NeedsEscaping(value);
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int valueIdx = JsonWriterHelper.NeedsEscaping(value, _options.Encoder);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(valueIdx >= -1 && valueIdx < value.Length && valueIdx < int.MaxValue / 2);
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length && propertyIdx < int.MaxValue / 2);
@@ -1032,7 +1032,7 @@ namespace System.Text.Json
                     }
                 }
 
-                JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written);
+                JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, _options.Encoder, out int written);
                 value = escapedValue.Slice(0, written);
             }
 
@@ -1056,7 +1056,7 @@ namespace System.Text.Json
                     }
                 }
 
-                JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+                JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
                 propertyName = escapedPropertyName.Slice(0, written);
             }
 
@@ -1101,7 +1101,7 @@ namespace System.Text.Json
                     }
                 }
 
-                JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder: null, out int written);
+                JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, _options.Encoder, out int written);
                 utf8Value = escapedValue.Slice(0, written);
             }
 
@@ -1125,7 +1125,7 @@ namespace System.Text.Json
                     }
                 }
 
-                JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+                JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
                 utf8PropertyName = escapedPropertyName.Slice(0, written);
             }
 
@@ -1170,7 +1170,7 @@ namespace System.Text.Json
                     }
                 }
 
-                JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder: null, out int written);
+                JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, _options.Encoder, out int written);
                 utf8Value = escapedValue.Slice(0, written);
             }
 
@@ -1194,7 +1194,7 @@ namespace System.Text.Json
                     }
                 }
 
-                JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+                JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
                 propertyName = escapedPropertyName.Slice(0, written);
             }
 
@@ -1239,7 +1239,7 @@ namespace System.Text.Json
                     }
                 }
 
-                JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written);
+                JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, _options.Encoder, out int written);
                 value = escapedValue.Slice(0, written);
             }
 
@@ -1263,7 +1263,7 @@ namespace System.Text.Json
                     }
                 }
 
-                JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+                JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
                 utf8PropertyName = escapedPropertyName.Slice(0, written);
             }
 
@@ -1283,7 +1283,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<char> value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringIndented(propertyName, value);
             }
@@ -1296,7 +1296,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> utf8Value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringIndented(utf8PropertyName, utf8Value);
             }
@@ -1309,7 +1309,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> utf8Value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringIndented(propertyName, utf8Value);
             }
@@ -1322,7 +1322,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<char> value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringIndented(utf8PropertyName, value);
             }
@@ -1500,7 +1500,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -1549,7 +1549,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -1601,7 +1601,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -1652,7 +1652,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
index 30b0fad..29d13cc 100644 (file)
@@ -188,7 +188,7 @@ namespace System.Text.Json
 
         private void WriteNumberEscape(ReadOnlySpan<char> propertyName, ulong value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
@@ -204,7 +204,7 @@ namespace System.Text.Json
 
         private void WriteNumberEscape(ReadOnlySpan<byte> utf8PropertyName, ulong value)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
@@ -231,7 +231,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (propertyArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -254,7 +254,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteNumberByOptions(escapedPropertyName.Slice(0, written), value);
 
@@ -267,7 +267,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<char> propertyName, ulong value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberIndented(propertyName, value);
             }
@@ -280,7 +280,7 @@ namespace System.Text.Json
         private void WriteNumberByOptions(ReadOnlySpan<byte> utf8PropertyName, ulong value)
         {
             ValidateWritingProperty();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberIndented(utf8PropertyName, value);
             }
@@ -375,7 +375,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
@@ -420,7 +420,7 @@ namespace System.Text.Json
                 output[BytesPending++] = JsonConstants.ListSeparator;
             }
 
-            Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
+            Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.PropertyName);
 
             if (_tokenType != JsonTokenType.None)
             {
index 35900c0..e83e3f2 100644 (file)
@@ -36,7 +36,7 @@ namespace System.Text.Json
         {
             ValidateWritingValue();
 
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteBase64Indented(bytes);
             }
index ef7ab6b..da0edc4 100644 (file)
@@ -53,7 +53,7 @@ namespace System.Text.Json
 
         private void WriteCommentByOptions(ReadOnlySpan<char> value)
         {
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteCommentIndented(value);
             }
@@ -152,7 +152,7 @@ namespace System.Text.Json
 
         private void WriteCommentByOptions(ReadOnlySpan<byte> utf8Value)
         {
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteCommentIndented(utf8Value);
             }
index f9cd8cb..ef901b4 100644 (file)
@@ -23,7 +23,7 @@ namespace System.Text.Json
         public void WriteStringValue(DateTime value)
         {
             ValidateWritingValue();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringValueIndented(value);
             }
index 1c99640..3b02456 100644 (file)
@@ -24,7 +24,7 @@ namespace System.Text.Json
         public void WriteStringValue(DateTimeOffset value)
         {
             ValidateWritingValue();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringValueIndented(value);
             }
index 412b395..65203c8 100644 (file)
@@ -23,7 +23,7 @@ namespace System.Text.Json
         public void WriteNumberValue(decimal value)
         {
             ValidateWritingValue();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberValueIndented(value);
             }
index be4f952..f57240f 100644 (file)
@@ -27,7 +27,7 @@ namespace System.Text.Json
             JsonWriterHelper.ValidateDouble(value);
 
             ValidateWritingValue();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberValueIndented(value);
             }
index 186f163..ae0c7a1 100644 (file)
@@ -27,7 +27,7 @@ namespace System.Text.Json
             JsonWriterHelper.ValidateSingle(value);
 
             ValidateWritingValue();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberValueIndented(value);
             }
index 4c468f3..6d260ab 100644 (file)
@@ -28,7 +28,7 @@ namespace System.Text.Json
             JsonWriterHelper.ValidateNumber(utf8FormattedNumber);
             ValidateWritingValue();
 
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberValueIndented(utf8FormattedNumber);
             }
index 0a80cdc..cb5729d 100644 (file)
@@ -23,7 +23,7 @@ namespace System.Text.Json
         public void WriteStringValue(Guid value)
         {
             ValidateWritingValue();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringValueIndented(value);
             }
index 6124a09..283a7c4 100644 (file)
@@ -13,7 +13,7 @@ namespace System.Text.Json
     {
         private void ValidateWritingValue()
         {
-            if (!Options.SkipValidation)
+            if (!_options.SkipValidation)
             {
                 if (_inObject)
                 {
@@ -60,7 +60,7 @@ namespace System.Text.Json
             else
             {
                 Debug.Assert(destination.Length >= written * JsonConstants.MaxExpansionFactorWhileEscaping);
-                JsonWriterHelper.EscapeString(encodedBytes, destination, firstEscapeIndexVal, encoder: null, out written);
+                JsonWriterHelper.EscapeString(encodedBytes, destination, firstEscapeIndexVal, _options.Encoder, out written);
                 BytesPending += written;
             }
 
index 2a718f1..836df56 100644 (file)
@@ -48,7 +48,7 @@ namespace System.Text.Json
         private void WriteLiteralByOptions(ReadOnlySpan<byte> utf8Value)
         {
             ValidateWritingValue();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteLiteralIndented(utf8Value);
             }
index 994aa78..35438b1 100644 (file)
@@ -36,7 +36,7 @@ namespace System.Text.Json
         public void WriteNumberValue(long value)
         {
             ValidateWritingValue();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberValueIndented(value);
             }
index 60d6803..36e27ad 100644 (file)
@@ -86,7 +86,7 @@ namespace System.Text.Json
 
         private void WriteStringEscape(ReadOnlySpan<char> value)
         {
-            int valueIdx = JsonWriterHelper.NeedsEscaping(value);
+            int valueIdx = JsonWriterHelper.NeedsEscaping(value, _options.Encoder);
 
             Debug.Assert(valueIdx >= -1 && valueIdx < value.Length);
 
@@ -103,7 +103,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<char> value)
         {
             ValidateWritingValue();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringIndented(value);
             }
@@ -194,7 +194,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (valueArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written);
+            JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, _options.Encoder, out int written);
 
             WriteStringByOptions(escapedValue.Slice(0, written));
 
@@ -229,7 +229,7 @@ namespace System.Text.Json
 
         private void WriteStringEscape(ReadOnlySpan<byte> utf8Value)
         {
-            int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value);
+            int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value, _options.Encoder);
 
             Debug.Assert(valueIdx >= -1 && valueIdx < utf8Value.Length);
 
@@ -246,7 +246,7 @@ namespace System.Text.Json
         private void WriteStringByOptions(ReadOnlySpan<byte> utf8Value)
         {
             ValidateWritingValue();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteStringIndented(utf8Value);
             }
@@ -337,7 +337,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (valueArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, _options.Encoder, out int written);
 
             WriteStringByOptions(escapedValue.Slice(0, written));
 
index e961cbd..f6cec3d 100644 (file)
@@ -38,7 +38,7 @@ namespace System.Text.Json
         public void WriteNumberValue(ulong value)
         {
             ValidateWritingValue();
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WriteNumberValueIndented(value);
             }
index c2c5414..ad421c1 100644 (file)
@@ -57,6 +57,8 @@ namespace System.Text.Json
         // else, no list separator is needed since we are writing the first item.
         private int _currentDepth;
 
+        private JsonWriterOptions _options; // Since JsonWriterOptions is a struct, use a field to avoid a copy for internal code.
+
         /// <summary>
         /// Returns the amount of bytes written by the <see cref="Utf8JsonWriter"/> so far
         /// that have not yet been flushed to the output and committed.
@@ -77,7 +79,13 @@ namespace System.Text.Json
         /// the <see cref="Utf8JsonWriter"/> which indicates whether to format the output
         /// while writing and whether to skip structural JSON validation or not.
         /// </summary>
-        public JsonWriterOptions Options { get; }
+        public JsonWriterOptions Options
+        {
+            get
+            {
+                return _options;
+            }
+        }
 
         private int Indentation => CurrentDepth * JsonConstants.SpacesPerIndent;
 
@@ -111,7 +119,7 @@ namespace System.Text.Json
             _isNotPrimitive = default;
             _tokenType = default;
             _currentDepth = default;
-            Options = options;
+            _options = options;
 
             // Only allocate if the user writes a JSON payload beyond the depth that the _allocationFreeContainer can handle.
             // This way we avoid allocations in the common, default cases, and allocate lazily.
@@ -147,7 +155,7 @@ namespace System.Text.Json
             _isNotPrimitive = default;
             _tokenType = default;
             _currentDepth = default;
-            Options = options;
+            _options = options;
 
             // Only allocate if the user writes a JSON payload beyond the depth that the _allocationFreeContainer can handle.
             // This way we avoid allocations in the common, default cases, and allocate lazily.
@@ -451,7 +459,7 @@ namespace System.Text.Json
             if (CurrentDepth >= JsonConstants.MaxWriterDepth)
                 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.DepthTooLarge, _currentDepth, token: default, tokenType: default);
 
-            if (Options.IndentedOrNotSkipValidation)
+            if (_options.IndentedOrNotSkipValidation)
             {
                 WriteStartSlow(token);
             }
@@ -482,11 +490,11 @@ namespace System.Text.Json
 
         private void WriteStartSlow(byte token)
         {
-            Debug.Assert(Options.Indented || !Options.SkipValidation);
+            Debug.Assert(_options.Indented || !_options.SkipValidation);
 
-            if (Options.Indented)
+            if (_options.Indented)
             {
-                if (!Options.SkipValidation)
+                if (!_options.SkipValidation)
                 {
                     ValidateStart();
                     UpdateBitStackOnStart(token);
@@ -495,7 +503,7 @@ namespace System.Text.Json
             }
             else
             {
-                Debug.Assert(!Options.SkipValidation);
+                Debug.Assert(!_options.SkipValidation);
                 ValidateStart();
                 UpdateBitStackOnStart(token);
                 WriteStartMinimized(token);
@@ -657,7 +665,7 @@ namespace System.Text.Json
 
         private void WriteStartEscape(ReadOnlySpan<byte> utf8PropertyName, byte token)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
 
@@ -675,7 +683,7 @@ namespace System.Text.Json
         {
             ValidateWritingProperty(token);
 
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WritePropertyNameIndented(utf8PropertyName, token);
             }
@@ -698,7 +706,7 @@ namespace System.Text.Json
                 stackalloc byte[length] :
                 (propertyArray = ArrayPool<byte>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, encoder: null, out int written);
+            JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteStartByOptions(escapedPropertyName.Slice(0, written), token);
 
@@ -802,7 +810,7 @@ namespace System.Text.Json
 
         private void WriteStartEscape(ReadOnlySpan<char> propertyName, byte token)
         {
-            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+            int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName, _options.Encoder);
 
             Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
 
@@ -820,7 +828,7 @@ namespace System.Text.Json
         {
             ValidateWritingProperty(token);
 
-            if (Options.Indented)
+            if (_options.Indented)
             {
                 WritePropertyNameIndented(propertyName, token);
             }
@@ -843,7 +851,7 @@ namespace System.Text.Json
                 stackalloc char[length] :
                 (propertyArray = ArrayPool<char>.Shared.Rent(length));
 
-            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+            JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
 
             WriteStartByOptions(escapedPropertyName.Slice(0, written), token);
 
@@ -879,7 +887,7 @@ namespace System.Text.Json
 
         private void WriteEnd(byte token)
         {
-            if (Options.IndentedOrNotSkipValidation)
+            if (_options.IndentedOrNotSkipValidation)
             {
                 WriteEndSlow(token);
             }
@@ -909,11 +917,11 @@ namespace System.Text.Json
 
         private void WriteEndSlow(byte token)
         {
-            Debug.Assert(Options.Indented || !Options.SkipValidation);
+            Debug.Assert(_options.Indented || !_options.SkipValidation);
 
-            if (Options.Indented)
+            if (_options.Indented)
             {
-                if (!Options.SkipValidation)
+                if (!_options.SkipValidation)
                 {
                     ValidateEnd(token);
                 }
@@ -921,7 +929,7 @@ namespace System.Text.Json
             }
             else
             {
-                Debug.Assert(!Options.SkipValidation);
+                Debug.Assert(!_options.SkipValidation);
                 ValidateEnd(token);
                 WriteEndMinimized(token);
             }
@@ -973,7 +981,7 @@ namespace System.Text.Json
                 }
 
                 Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
-                Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.None);
+                Debug.Assert(_options.SkipValidation || _tokenType != JsonTokenType.None);
 
                 int maxRequired = indent + 3; // 1 end token, 1-2 bytes for new line
 
index 744cc60..ce87015 100644 (file)
@@ -1228,7 +1228,7 @@ null,
 #endif
                     );
 
-            // Temporary hack until we can use the same escape algorithm throughout.
+            // Temporary hack until we can use the same escape algorithm on both sides and make sure we want uppercase hex.
             Assert.Equal(expectedValue.NormalizeToJsonNetFormat(), value.NormalizeToJsonNetFormat());
         }
     }
index 6dd4d0b..5dc5607 100644 (file)
@@ -2,6 +2,9 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Collections.Generic;
+using System.Text.Encodings.Web;
+using System.Text.Unicode;
 using Xunit;
 
 namespace System.Text.Json.Serialization.Tests
@@ -49,6 +52,7 @@ namespace System.Text.Json.Serialization.Tests
             Assert.False(options.AllowTrailingCommas);
             Assert.Equal(16 * 1024, options.DefaultBufferSize);
             Assert.Equal(null, options.DictionaryKeyPolicy);
+            Assert.Null(options.Encoder);
             Assert.False(options.IgnoreNullValues);
             Assert.Equal(0, options.MaxDepth);
             Assert.Equal(false, options.PropertyNameCaseInsensitive);
@@ -69,6 +73,7 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Throws<InvalidOperationException>(() => options.AllowTrailingCommas = options.AllowTrailingCommas);
             Assert.Throws<InvalidOperationException>(() => options.DefaultBufferSize = options.DefaultBufferSize);
             Assert.Throws<InvalidOperationException>(() => options.DictionaryKeyPolicy = options.DictionaryKeyPolicy);
+            Assert.Throws<InvalidOperationException>(() => options.Encoder = JavaScriptEncoder.Default);
             Assert.Throws<InvalidOperationException>(() => options.IgnoreNullValues = options.IgnoreNullValues);
             Assert.Throws<InvalidOperationException>(() => options.MaxDepth = options.MaxDepth);
             Assert.Throws<InvalidOperationException>(() => options.PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive);
@@ -235,5 +240,86 @@ namespace System.Text.Json.Serialization.Tests
 
             Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<BasicCompany>(BasicCompany.s_data, options));
         }
+
+        private class TestClassForEncoding
+        {
+            public string MyString { get; set; }
+        }
+
+        // This is a copy of the test data in System.Text.Json.Tests.JsonEncodedTextTests.JsonEncodedTextStringsCustom
+        public static IEnumerable<object[]> JsonEncodedTextStringsCustom
+        {
+            get
+            {
+                return new List<object[]>
+                {
+                    new object[] { "age", "\\u0061\\u0067\\u0065" },
+                    new object[] { "éééééêêêêê", "éééééêêêêê" },
+                    new object[] { "ééééé\"êêêêê", "ééééé\\u0022êêêêê" },
+                    new object[] { "ééééé\\u0022êêêêê", "ééééé\\\\\\u0075\\u0030\\u0030\\u0032\\u0032êêêêê" },
+                    new object[] { "ééééé>>>>>êêêêê", "ééééé\\u003E\\u003E\\u003E\\u003E\\u003Eêêêêê" },
+                    new object[] { "ééééé\\u003e\\u003eêêêêê", "ééééé\\\\\\u0075\\u0030\\u0030\\u0033\\u0065\\\\\\u0075\\u0030\\u0030\\u0033\\u0065êêêêê" },
+                    new object[] { "ééééé\\u003E\\u003Eêêêêê", "ééééé\\\\\\u0075\\u0030\\u0030\\u0033\\u0045\\\\\\u0075\\u0030\\u0030\\u0033\\u0045êêêêê" },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(JsonEncodedTextStringsCustom))]
+        public static void CustomEncoderAllowLatin1Supplement(string message, string expectedMessage)
+        {
+            // Latin-1 Supplement block starts from U+0080 and ends at U+00FF
+            JavaScriptEncoder encoder = JavaScriptEncoder.Create(UnicodeRanges.Latin1Supplement);
+
+            var options = new JsonSerializerOptions();
+            options.Encoder = encoder;
+
+            var obj = new TestClassForEncoding();
+            obj.MyString = message;
+
+            string baselineJson = JsonSerializer.Serialize(obj);
+            Assert.DoesNotContain(expectedMessage, baselineJson);
+
+            string json = JsonSerializer.Serialize(obj, options);
+            Assert.Contains(expectedMessage, json);
+
+            obj = JsonSerializer.Deserialize<TestClassForEncoding>(json);
+            Assert.Equal(obj.MyString, message);
+        }
+
+        public static IEnumerable<object[]> JsonEncodedTextStringsCustomAll
+        {
+            get
+            {
+                return new List<object[]>
+                {
+                    new object[] { "éééééêêêêê", "éééééêêêêê" },
+                    new object[] { "aѧѦa", "aѧѦa" }, // U0467, U0466
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(JsonEncodedTextStringsCustomAll))]
+        public static void JsonEncodedTextStringsCustomAllowAll(string message, string expectedMessage)
+        {
+            // Allow all unicode values (except forbidden characters which we don't have in test data here)
+            JavaScriptEncoder encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
+
+            var options = new JsonSerializerOptions();
+            options.Encoder = encoder;
+
+            var obj = new TestClassForEncoding();
+            obj.MyString = message;
+
+            string baselineJson = JsonSerializer.Serialize(obj);
+            Assert.DoesNotContain(expectedMessage, baselineJson);
+
+            string json = JsonSerializer.Serialize(obj, options);
+            Assert.Contains(expectedMessage, json);
+
+            obj = JsonSerializer.Deserialize<TestClassForEncoding>(json);
+            Assert.Equal(obj.MyString, message);
+        }
     }
 }
index e3f5415..13734aa 100644 (file)
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
@@ -10,6 +10,8 @@ using System.Globalization;
 using System.Threading.Tasks;
 using System.IO.Pipelines;
 using System.Collections.Generic;
+using System.Text.Encodings.Web;
+using System.Text.Unicode;
 
 namespace System.Text.Json.Tests
 {
@@ -61,6 +63,7 @@ namespace System.Text.Json.Tests
             Assert.Equal(0, writer.BytesCommitted);
             Assert.Equal(0, writer.BytesPending);
             Assert.Equal(0, writer.CurrentDepth);
+            Assert.Equal(null, writer.Options.Encoder);
             Assert.Equal(formatted, writer.Options.Indented);
             Assert.Equal(skipValidation, writer.Options.SkipValidation);
             Assert.Equal(0, stream.Position);
@@ -70,6 +73,7 @@ namespace System.Text.Json.Tests
             Assert.Equal(0, writer.BytesCommitted);
             Assert.Equal(0, writer.BytesPending);
             Assert.Equal(0, writer.CurrentDepth);
+            Assert.Equal(null, writer.Options.Encoder);
             Assert.Equal(formatted, writer.Options.Indented);
             Assert.Equal(skipValidation, writer.Options.SkipValidation);
             Assert.Equal(0, output.FormattedCount);
@@ -95,6 +99,7 @@ namespace System.Text.Json.Tests
             Assert.Equal(0, writeToStream.BytesCommitted);
             Assert.Equal(0, writeToStream.BytesPending);
             Assert.Equal(0, writeToStream.CurrentDepth);
+            Assert.Equal(null, writeToStream.Options.Encoder);
             Assert.Equal(formatted, writeToStream.Options.Indented);
             Assert.Equal(skipValidation, writeToStream.Options.SkipValidation);
             Assert.True(stream.Position != 0);
@@ -114,6 +119,7 @@ namespace System.Text.Json.Tests
             Assert.Equal(0, writeToIBW.BytesCommitted);
             Assert.Equal(0, writeToIBW.BytesPending);
             Assert.Equal(0, writeToIBW.CurrentDepth);
+            Assert.Equal(null, writeToIBW.Options.Encoder);
             Assert.Equal(formatted, writeToIBW.Options.Indented);
             Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation);
             Assert.True(output.FormattedCount != 0);
@@ -143,6 +149,7 @@ namespace System.Text.Json.Tests
             Assert.Equal(0, writeToStream.BytesCommitted);
             Assert.Equal(0, writeToStream.BytesPending);
             Assert.Equal(0, writeToStream.CurrentDepth);
+            Assert.Equal(null, writeToStream.Options.Encoder);
             Assert.Equal(formatted, writeToStream.Options.Indented);
             Assert.Equal(skipValidation, writeToStream.Options.SkipValidation);
             Assert.True(stream.Position != 0);
@@ -168,6 +175,7 @@ namespace System.Text.Json.Tests
             Assert.Equal(0, writeToIBW.BytesCommitted);
             Assert.Equal(0, writeToIBW.BytesPending);
             Assert.Equal(0, writeToIBW.CurrentDepth);
+            Assert.Equal(null, writeToIBW.Options.Encoder);
             Assert.Equal(formatted, writeToIBW.Options.Indented);
             Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation);
             Assert.True(output.FormattedCount != 0);
@@ -204,6 +212,7 @@ namespace System.Text.Json.Tests
             Assert.Equal(0, writeToStream.BytesCommitted);
             Assert.Equal(0, writeToStream.BytesPending);
             Assert.Equal(0, writeToStream.CurrentDepth);
+            Assert.Equal(null, writeToStream.Options.Encoder);
             Assert.Equal(formatted, writeToStream.Options.Indented);
             Assert.Equal(skipValidation, writeToStream.Options.SkipValidation);
             Assert.True(stream.Position != 0);
@@ -234,6 +243,7 @@ namespace System.Text.Json.Tests
             Assert.Equal(0, writeToIBW.BytesCommitted);
             Assert.Equal(0, writeToIBW.BytesPending);
             Assert.Equal(0, writeToIBW.CurrentDepth);
+            Assert.Equal(null, writeToIBW.Options.Encoder);
             Assert.Equal(formatted, writeToIBW.Options.Indented);
             Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation);
             Assert.True(output.FormattedCount != 0);
@@ -3087,54 +3097,58 @@ namespace System.Text.Json.Tests
             }
         }
 
-        [Theory(Skip = "Update test to match JavaScriptEncoder semantics.")]
-        [InlineData(true, true)]
-        [InlineData(true, false)]
-        [InlineData(false, true)]
-        [InlineData(false, false)]
-        public void InvalidUTF8(bool formatted, bool skipValidation)
-        {
-            var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+        private const string InvalidUtf8 = "\"\\uFFFD(\"";
+        private const string ValidUtf8 = "\"\\u00F1\"";
 
-            var output = new ArrayBufferWriter<byte>(1024);
-            var jsonUtf8 = new Utf8JsonWriter(output, options);
+        // todo: verify that we should be using replacement char and not throwing here if skipValidation=true
+        [Theory]
+        [InlineData(true,
+            "{" + InvalidUtf8 + ":" + InvalidUtf8 + "}",
+            "{" + InvalidUtf8 + ":" + ValidUtf8 + "}",
+            "{" + ValidUtf8 + ":" + InvalidUtf8 + "}",
+            "{" + ValidUtf8 + ":" + ValidUtf8 + "}")]
+        [InlineData(false,
+            "{" + InvalidUtf8 + ":" + InvalidUtf8 + "}",
+            "{" + InvalidUtf8 + ":" + ValidUtf8 + "}",
+            "{" + ValidUtf8 + ":" + InvalidUtf8 + "}",
+            "{" + ValidUtf8 + ":" + ValidUtf8 + "}")]
+        public void UTF8ReplacementCharacters(bool skipValidation, string expected0, string expected1, string expected2, string expected3)
+        {
+            var options = new JsonWriterOptions { SkipValidation = skipValidation };
 
             var validUtf8 = new byte[2] { 0xc3, 0xb1 }; // 0xF1
             var invalidUtf8 = new byte[2] { 0xc3, 0x28 };
 
-            jsonUtf8.WriteStartObject();
-            for (int i = 0; i < 6; i++)
+            string WriteProperty(byte[] propertyName, byte[] value)
             {
-                switch (i)
-                {
-                    case 0:
-                        Assert.Throws<ArgumentException>(() => jsonUtf8.WriteString(invalidUtf8, invalidUtf8));
-                        break;
-                    case 1:
-                        Assert.Throws<ArgumentException>(() => jsonUtf8.WriteString(invalidUtf8, validUtf8));
-                        break;
-                    case 2:
-                        Assert.Throws<ArgumentException>(() => jsonUtf8.WriteString(validUtf8, invalidUtf8));
-                        break;
-                    case 3:
-                        jsonUtf8.WriteString(validUtf8, validUtf8);
-                        break;
-                    case 4:
-                        Assert.Throws<ArgumentException>(() => jsonUtf8.WritePropertyName(invalidUtf8));
-                        break;
-                    case 5:
-                        jsonUtf8.WritePropertyName(validUtf8);
-                        Assert.Throws<ArgumentException>(() => jsonUtf8.WriteStringValue(invalidUtf8));
-                        jsonUtf8.WriteStringValue(validUtf8);
-                        break;
-                    case 6:
-                        jsonUtf8.WritePropertyName(validUtf8);
-                        jsonUtf8.WriteStringValue(validUtf8);
-                        break;
-                }
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
+                jsonUtf8.WriteStartObject();
+                jsonUtf8.WriteString(propertyName, value);
+                jsonUtf8.WriteEndObject();
+                jsonUtf8.Flush();
+                string result = Encoding.UTF8.GetString(
+                        output.WrittenSpan
+#if netfx
+                        .ToArray()
+#endif
+                    );
+                output.Clear();
+                return result;
             }
-            jsonUtf8.WriteEndObject();
-            jsonUtf8.Flush();
+
+            string result;
+            result = WriteProperty(invalidUtf8, invalidUtf8);
+            Assert.Equal(expected0, result);
+
+            result = WriteProperty(invalidUtf8, validUtf8);
+            Assert.Equal(expected1, result);
+
+            result = WriteProperty(validUtf8, invalidUtf8);
+            Assert.Equal(expected2, result);
+
+            result = WriteProperty(validUtf8, validUtf8);
+            Assert.Equal(expected3, result);
         }
 
         [Theory]
@@ -3187,6 +3201,76 @@ namespace System.Text.Json.Tests
             jsonUtf8.Flush();
         }
 
+        public static IEnumerable<object[]> JsonEncodedTextStringsCustomAll
+        {
+            get
+            {
+                return new List<object[]>
+                {
+                    new object[] { "éééééêêêêê", "{\"Prop\":\"éééééêêêêê\"}" },
+                    new object[] { "aѧѦa", "{\"Prop\":\"aѧѦa\"}" }, // U0467, U0466
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(JsonEncodedTextStringsCustomAll))]
+        public void CustomEscaper(string value, string expectedStr)
+        {
+            const string PropertyName = "Prop";
+
+            // Allow all unicode values (except forbidden characters which we don't have in test data here)
+            JavaScriptEncoder encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
+
+            var options = new JsonWriterOptions();
+            options.Encoder = encoder;
+
+            for (int i = 0; i < 6; i++)
+            {
+                var output = new ArrayBufferWriter<byte>(1024);
+                var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+                jsonUtf8.WriteStartObject();
+
+                switch (i)
+                {
+                    case 0:
+                        jsonUtf8.WriteString(PropertyName, value);
+                        break;
+                    case 1:
+                        jsonUtf8.WriteString(Encoding.UTF8.GetBytes(PropertyName), Encoding.UTF8.GetBytes(value));
+                        break;
+                    case 2:
+                        jsonUtf8.WriteString(JsonEncodedText.Encode(PropertyName), JsonEncodedText.Encode(value, encoder));
+                        break;
+                    case 3:
+                        jsonUtf8.WritePropertyName(PropertyName);
+                        jsonUtf8.WriteStringValue(value);
+                        break;
+                    case 4:
+                        jsonUtf8.WritePropertyName(Encoding.UTF8.GetBytes(PropertyName));
+                        jsonUtf8.WriteStringValue(Encoding.UTF8.GetBytes(value));
+                        break;
+                    case 5:
+                        jsonUtf8.WritePropertyName(JsonEncodedText.Encode(PropertyName, encoder));
+                        jsonUtf8.WriteStringValue(JsonEncodedText.Encode(value, encoder));
+                        break;
+                }
+
+                jsonUtf8.WriteEndObject();
+                jsonUtf8.Flush();
+
+                string result = Encoding.UTF8.GetString(
+                        output.WrittenSpan
+#if netfx
+                        .ToArray()
+#endif
+                    );
+
+                Assert.Equal(expectedStr, result);
+            }
+        }
+
         [Theory]
         [InlineData(true, true)]
         [InlineData(true, false)]
@@ -5828,7 +5912,7 @@ namespace System.Text.Json.Tests
 #endif
                     );
 
-            // Temporary hack until we can use the same escape algorithm throughout.
+            // Temporary hack until we can use the same escape algorithm on both sides and make sure we want uppercase hex.
             Assert.Equal(expectedValue.NormalizeToJsonNetFormat(), value.NormalizeToJsonNetFormat());
         }
 
@@ -5857,7 +5941,7 @@ namespace System.Text.Json.Tests
         {
             var sb = new StringBuilder(json.Length);
             int i = 0;
-            while (i < json.Length - 1)
+            while (i < json.Length)
             {
                 if (json[i] == '\\')
                 {