Initial Json serialization functionality (dotnet/corefx#35609)
authorSteve Harter <steveharter@users.noreply.github.com>
Sat, 2 Mar 2019 19:20:34 +0000 (13:20 -0600)
committerGitHub <noreply@github.com>
Sat, 2 Mar 2019 19:20:34 +0000 (13:20 -0600)
Commit migrated from https://github.com/dotnet/corefx/commit/20657b549ab4de422c3f7a9c50e22bf4d7f789a5

94 files changed:
src/libraries/System.Text.Json/ref/System.Text.Json.cs
src/libraries/System.Text.Json/ref/System.Text.Json.csproj
src/libraries/System.Text.Json/src/JsonReflectionEmitMaterializer.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System.Text.Json.csproj
src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderException.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ArrayBufferWriter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassMaterializer.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultArrayConverter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultConverters.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumConverter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBoolean.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBooleanNullable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByte.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByteNullable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterChar.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterCharNullable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTime.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTimeNullable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimal.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimalNullable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDouble.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDoubleNullable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInfoInt16.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt16Nullable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32Nullable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64Nullable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByte.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByteNullable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingle.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingleNullable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterString.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16Nullable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32Nullable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64Nullable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfTClassTProperty.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfTProperty.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleValue.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Policies/JsonValueConverter.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/PropertyRef.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMaterializer.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMaterializer.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs
src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/Array.WriteTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/CyclicTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/EnumTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/Null.ReadTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/Null.WriteTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/SpanTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/Stream.ReadTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/Stream.WriteTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/String.ReadTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/String.WriteTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/TestData.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj

index 7b96b0a..24d6bb8 100644 (file)
@@ -28,6 +28,7 @@ namespace System.Text.Json
     public readonly partial struct JsonElement
     {
         private readonly object _dummy;
+        private readonly int _dummyPrimitive;
         public System.Text.Json.JsonElement this[int index] { get { throw null; } }
         public System.Text.Json.JsonValueType Type { get { throw null; } }
         public System.Text.Json.JsonElement.ArrayEnumerator EnumerateArray() { throw null; }
@@ -64,6 +65,7 @@ namespace System.Text.Json
         public partial struct ArrayEnumerator : System.Collections.Generic.IEnumerable<System.Text.Json.JsonElement>, System.Collections.Generic.IEnumerator<System.Text.Json.JsonElement>, System.Collections.IEnumerable, System.Collections.IEnumerator, System.IDisposable
         {
             private object _dummy;
+            private int _dummyPrimitive;
             public System.Text.Json.JsonElement Current { get { throw null; } }
             object System.Collections.IEnumerator.Current { get { throw null; } }
             public void Dispose() { }
@@ -76,6 +78,7 @@ namespace System.Text.Json
         public partial struct ObjectEnumerator : System.Collections.Generic.IEnumerable<System.Text.Json.JsonProperty>, System.Collections.Generic.IEnumerator<System.Text.Json.JsonProperty>, System.Collections.IEnumerable, System.Collections.IEnumerator, System.IDisposable
         {
             private object _dummy;
+            private int _dummyPrimitive;
             public System.Text.Json.JsonProperty Current { get { throw null; } }
             object System.Collections.IEnumerator.Current { get { throw null; } }
             public void Dispose() { }
@@ -102,7 +105,7 @@ namespace System.Text.Json
     }
     public partial struct JsonReaderOptions
     {
-        private object _dummy;
+        private int _dummyPrimitive;
         public System.Text.Json.JsonCommentHandling CommentHandling { get { throw null; } set { } }
         public int MaxDepth { get { throw null; } set { } }
     }
@@ -142,13 +145,14 @@ namespace System.Text.Json
     }
     public partial struct JsonWriterOptions
     {
-        private object _dummy;
+        private int _dummyPrimitive;
         public bool Indented { get { throw null; } set { } }
         public bool SkipValidation { get { throw null; } set { } }
     }
     public partial struct JsonWriterState
     {
         private object _dummy;
+        private int _dummyPrimitive;
         public JsonWriterState(System.Text.Json.JsonWriterOptions options = default(System.Text.Json.JsonWriterOptions)) { throw null; }
         public long BytesCommitted { get { throw null; } }
         public long BytesWritten { get { throw null; } }
@@ -157,6 +161,7 @@ namespace System.Text.Json
     public ref partial struct Utf8JsonReader
     {
         private object _dummy;
+        private int _dummyPrimitive;
         public Utf8JsonReader(in System.Buffers.ReadOnlySequence<byte> jsonData, bool isFinalBlock, System.Text.Json.JsonReaderState state) { throw null; }
         public Utf8JsonReader(System.ReadOnlySpan<byte> jsonData, bool isFinalBlock, System.Text.Json.JsonReaderState state) { throw null; }
         public long BytesConsumed { get { throw null; } }
@@ -192,6 +197,7 @@ namespace System.Text.Json
     public ref partial struct Utf8JsonWriter
     {
         private object _dummy;
+        private int _dummyPrimitive;
         public Utf8JsonWriter(System.Buffers.IBufferWriter<byte> bufferWriter, System.Text.Json.JsonWriterState state = default(System.Text.Json.JsonWriterState)) { throw null; }
         public long BytesCommitted { get { throw null; } }
         public long BytesWritten { get { throw null; } }
@@ -281,3 +287,30 @@ namespace System.Text.Json
         public void WriteStringValue(string value, bool escape = true) { }
     }
 }
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        public static object Parse(System.ReadOnlySpan<byte> utf8Json, System.Type returnType, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
+        public static object Parse(string json, System.Type returnType, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
+        public static TValue Parse<TValue>(System.ReadOnlySpan<byte> utf8Json, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
+        public static TValue Parse<TValue>(string json, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
+        public static System.Threading.Tasks.ValueTask<object> ReadAsync(System.IO.Stream utf8Json, System.Type returnType, System.Text.Json.Serialization.JsonSerializerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public static System.Threading.Tasks.ValueTask<TValue> ReadAsync<TValue>(System.IO.Stream utf8Json, System.Text.Json.Serialization.JsonSerializerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public static byte[] ToBytes(object value, System.Type type, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
+        public static byte[] ToBytes<TValue>(TValue value, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
+        public static string ToString(object value, System.Type type, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
+        public static string ToString<TValue>(TValue value, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
+        public static System.Threading.Tasks.Task WriteAsync(object value, System.Type type, System.IO.Stream utf8Json, System.Text.Json.Serialization.JsonSerializerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public static System.Threading.Tasks.Task WriteAsync<TValue>(TValue value, System.IO.Stream utf8Json, System.Text.Json.Serialization.JsonSerializerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+    }
+    public sealed partial class JsonSerializerOptions
+    {
+        public JsonSerializerOptions() { }
+        public int DefaultBufferSize { get { throw null; } set { } }
+        public bool IgnoreNullPropertyValueOnRead { get { throw null; } set { } }
+        public bool IgnoreNullPropertyValueOnWrite { get { throw null; } set { } }
+        public System.Text.Json.JsonReaderOptions ReaderOptions { get { throw null; } set { } }
+        public System.Text.Json.JsonWriterOptions WriterOptions { get { throw null; } set { } }
+    }
+}
index 6c66717..002d0f8 100644 (file)
@@ -12,5 +12,6 @@
   </ItemGroup>
   <ItemGroup Condition="'$(TargetGroup)' == 'netstandard'">
     <Reference Include="System.Memory" />
+    <Reference Include="System.Threading.Tasks.Extensions" />
   </ItemGroup>
 </Project>
diff --git a/src/libraries/System.Text.Json/src/JsonReflectionEmitMaterializer.cs b/src/libraries/System.Text.Json/src/JsonReflectionEmitMaterializer.cs
new file mode 100644 (file)
index 0000000..75897da
--- /dev/null
@@ -0,0 +1,104 @@
+// 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.
+
+using System.Diagnostics;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace System.Text.Json.Serialization
+{
+    internal class JsonReflectionEmitMaterializer : JsonMemberBasedClassMaterializer
+    {
+        public override JsonClassInfo.ConstructorDelegate CreateConstructor(Type type)
+        {
+            ConstructorInfo realMethod = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic| BindingFlags.Instance, binder: null, Type.EmptyTypes, modifiers: null);
+            if (realMethod == null)
+                return null;
+
+            var dynamicMethod = new DynamicMethod(
+                realMethod.Name,
+                type,
+                Type.EmptyTypes,
+                typeof(JsonReflectionEmitMaterializer).Module,
+                skipVisibility: true);
+
+            if (dynamicMethod != null)
+            {
+                ILGenerator generator = dynamicMethod?.GetILGenerator();
+                if (generator != null)
+                {
+
+                    generator.Emit(OpCodes.Newobj, realMethod);
+                    generator.Emit(OpCodes.Ret);
+
+                    var result = (JsonClassInfo.ConstructorDelegate)dynamicMethod.CreateDelegate(typeof(JsonClassInfo.ConstructorDelegate));
+                    return result;
+                }
+            }
+
+            throw new InvalidOperationException(SR.Format(SR.SerializationUnableToCreateDynamicMethod, $"{type.FullName}.{realMethod.Name}"));
+        }
+
+        public override JsonPropertyInfo<TValue>.GetterDelegate CreateGetter<TValue>(PropertyInfo propertyInfo)
+        {
+            MethodInfo realMethod = propertyInfo.GetGetMethod();
+            Debug.Assert(realMethod != null);
+
+            var dynamicMethod = new DynamicMethod(
+                realMethod.Name,
+                typeof(TValue),
+                new Type[] { typeof(object) },
+                typeof(JsonReflectionEmitMaterializer).Module,
+                skipVisibility: true);
+
+            if (dynamicMethod != null)
+            {
+
+                ILGenerator generator = dynamicMethod?.GetILGenerator();
+                if (generator != null)
+                {
+                    generator.Emit(OpCodes.Ldarg_0);
+                    generator.EmitCall(OpCodes.Callvirt, realMethod, null);
+                    generator.Emit(OpCodes.Ret);
+
+                    var result = (JsonPropertyInfo<TValue>.GetterDelegate)dynamicMethod.CreateDelegate(typeof(JsonPropertyInfo<TValue>.GetterDelegate));
+                    return result;
+                }
+            }
+
+            throw new InvalidOperationException(SR.Format(SR.SerializationUnableToCreateDynamicMethod, $"{propertyInfo.Name}.{realMethod.Name}"));
+        }
+
+        public override JsonPropertyInfo<TValue>.SetterDelegate CreateSetter<TValue>(PropertyInfo propertyInfo)
+        {
+            MethodInfo realMethod = propertyInfo.GetSetMethod();
+            Debug.Assert(realMethod != null);
+
+            var dynamicMethod = new DynamicMethod(
+                realMethod.Name,
+                typeof(void),
+                new Type[] { typeof(object), typeof(TValue) },
+                typeof(JsonReflectionEmitMaterializer).Module,
+                skipVisibility: true);
+
+            if (dynamicMethod != null)
+            {
+                ILGenerator generator = dynamicMethod?.GetILGenerator();
+                if (generator != null)
+                {
+
+                    generator.Emit(OpCodes.Ldarg_0);
+                    generator.Emit(OpCodes.Ldarg_1);
+                    generator.EmitCall(OpCodes.Callvirt, realMethod, null);
+                    generator.Emit(OpCodes.Ret);
+
+                    var result = (JsonPropertyInfo<TValue>.SetterDelegate)dynamicMethod.CreateDelegate(typeof(JsonPropertyInfo<TValue>.SetterDelegate));
+                    return result;
+                }
+            }
+
+            throw new InvalidOperationException(SR.Format(SR.SerializationUnableToCreateDynamicMethod, $"{propertyInfo.Name}.{realMethod.Name}"));
+        }
+    }
+}
index ddab22e..0d00205 100644 (file)
   <data name="ZeroDepthAtEnd" xml:space="preserve">
     <value>Expected CurrentDepth ({0}) to be zero at the end of the JSON payload. There is an open JSON object or array that should be closed.</value>
   </data>
-</root>
+  <data name="DeserializeCannotBeNull" xml:space="preserve">
+    <value>The JSON value from {0} cannot be null.</value>
+  </data>
+  <data name="DeserializeDataRemaining" xml:space="preserve">
+    <value>The provided data of length {0} has remaining bytes {1}.</value>
+  </data>
+  <data name="DeserializeUnableToConvertValue" xml:space="preserve">
+    <value>The JSON value from {0} could not be converted to {1}.</value>
+  </data>
+  <data name="DeserializeWrongType" xml:space="preserve">
+    <value>The specified type {0} must derive from the specific value's type {1}.</value>
+  </data>
+  <data name="SerializationInvalidBufferSize" xml:space="preserve">
+    <value>The value must be greater than zero or equal to -1.</value>
+  </data>
+  <data name="BufferWriterAdvancedTooFar" xml:space="preserve">
+    <value>Cannot advance past the end of the buffer, which has a size of {0}.</value>
+  </data>
+  <data name="EnumConverterNotImplemented" xml:space="preserve">
+    <value>EnumConverter is not yet supported on .NET Standard 2.0.</value>
+  </data>
+</root>
\ No newline at end of file
index ec0e780..1b8ac67 100644 (file)
@@ -17,6 +17,7 @@
     <Compile Include="System\Text\Json\JsonHelpers.cs" />
     <Compile Include="System\Text\Json\JsonTokenType.cs" />
     <Compile Include="System\Text\Json\ThrowHelper.cs" />
+    <Compile Include="System\Text\Json\ThrowHelper.Serialization.cs" />
     <Compile Include="System\Text\Json\Document\JsonDocument.cs" />
     <Compile Include="System\Text\Json\Document\JsonDocument.DbRow.cs" />
     <Compile Include="System\Text\Json\Document\JsonDocument.MetadataDb.cs" />
     <Compile Include="System\Text\Json\Reader\Utf8JsonReader.cs" />
     <Compile Include="System\Text\Json\Reader\Utf8JsonReader.MultiSegment.cs" />
     <Compile Include="System\Text\Json\Reader\Utf8JsonReader.TryGet.cs" />
+    <Compile Include="System\Text\Json\Serialization\ArrayBufferWriter.cs" />
+    <Compile Include="System\Text\Json\Serialization\ClassMaterializer.cs" />
+    <Compile Include="System\Text\Json\Serialization\ClassType.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\DefaultArrayConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\DefaultConverters.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\DefaultEnumConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterBoolean.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterBooleanNullable.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterByte.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterByteNullable.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterChar.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterCharNullable.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterDateTime.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterDateTimeNullable.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterDecimal.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterDecimalNullable.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterDouble.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterDoubleNullable.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterInfoInt16.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterInt16Nullable.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterInt32.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterInt32Nullable.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterInt64.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterInt64Nullable.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterSByte.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterSByteNullable.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterSingle.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterSingleNullable.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterString.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt16.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt16Nullable.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt32.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt32Nullable.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt64.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt64Nullable.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonClassInfo.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonClassInfo.AddProperty.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonEnumerableConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonPropertyInfo.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonPropertyInfoOfTClassTProperty.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonPropertyInfoOfTProperty.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleArray.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Stream.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleValue.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleObject.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.String.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Helpers.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleNull.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Span.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.ByteArray.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Stream.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.HandleEnumerable.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.HandleObject.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.String.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.HandleValue.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializerOptions.cs" />
+    <Compile Include="System\Text\Json\Serialization\Policies\JsonValueConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\PropertyRef.cs" />
+    <Compile Include="System\Text\Json\Serialization\ReadStack.cs" />
+    <Compile Include="System\Text\Json\Serialization\ReadStackFrame.cs" />
+    <Compile Include="System\Text\Json\Serialization\ReflectionEmitMaterializer.cs" />
+    <Compile Include="System\Text\Json\Serialization\ReflectionMaterializer.cs" />
+    <Compile Include="System\Text\Json\Serialization\WriteStack.cs" />
+    <Compile Include="System\Text\Json\Serialization\WriteStackFrame.cs" />
     <Compile Include="System\Text\Json\Writer\JsonWriterHelper.cs" />
     <Compile Include="System\Text\Json\Writer\JsonWriterHelper.Escaping.cs" />
     <Compile Include="System\Text\Json\Writer\JsonWriterHelper.Transcoding.cs" />
     <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.UnsignedNumber.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetsNETStandard)' != 'true'">
+    <Reference Include="System.Collections" />
     <Reference Include="System.Diagnostics.Debug" />
+    <Reference Include="System.Reflection.Primitives" />
+    <Reference Include="System.Reflection.Emit.ILGeneration" />
+    <Reference Include="System.Reflection.Emit.Lightweight" />
     <Reference Include="System.Resources.ResourceManager" />
     <Reference Include="System.Runtime" />
     <Reference Include="System.Runtime.Extensions" />
     <Reference Include="System.Text.Encoding.Extensions" />
-    <Reference Include="System.Threading.Tasks" />
   </ItemGroup>
   <ItemGroup>
     <Reference Include="System.Buffers" />
+    <Reference Include="System.Collections.Concurrent" />
+    <Reference Include="System.Linq" />
     <Reference Include="System.Memory" />
     <Reference Include="System.Numerics.Vectors" />
     <Reference Include="System.Runtime.CompilerServices.Unsafe" />
+    <Reference Include="System.Threading.Tasks" />
+    <Reference Include="System.Threading.Tasks.Extensions" />
   </ItemGroup>
 
   <!-- The Source Package will call into this target in order to retrieve the compiled resources file as
        it will need to be part of the source package contents. -->
   <Target Name="GetSourcesToPackage" Returns="@(_sourceToPackage)" DependsOnTargets="Compile">
-
       <PropertyGroup>
         <_sourcePackageName>Microsoft.Bcl.Json.Sources</_sourcePackageName>
         <_sourcePackageTargetLines><![CDATA[<?xml version="1.0" encoding="utf-8"?>
 
       <ItemGroup>
         <_sourceToExclude Include="@(Compile)" Condition="$([System.String]::Copy('%(Compile.Identity)').ToLower().EndsWith('assemblyinfo.cs'))" />
-        <_sourceToPackage Include="@(Compile->'%(FullPath)')" PackagePath="contentFiles/cs/netstandard2.0/$(_sourcePackageName)/%(FileName)%(Extension)" Exclude="@(_sourceToExclude)"/>
-        <_sourceToPackage Condition="'%(Extension)' == '.resx'" Include="@(EmbeddedResource->'%(FullPath)')"  PackagePath="build/netstandard2.0/%(FileName)%(Extension)" />
+        <_sourceToPackage Include="@(Compile->'%(FullPath)')" PackagePath="contentFiles/cs/netstandard2.0/$(_sourcePackageName)/%(FileName)%(Extension)" Exclude="@(_sourceToExclude)" />
+        <_sourceToPackage Condition="'%(Extension)' == '.resx'" Include="@(EmbeddedResource->'%(FullPath)')" PackagePath="build/netstandard2.0/%(FileName)%(Extension)" />
         <_sourceToPackage Include="$(_generatedSourcePackageTargetPath)" PackagePath="build/netstandard2.0/$(_targetsFileName)" />
         <_sourceToPackage Include="$(MSBuildThisFileDirectory)../source_package/README.md" PackagePath="build/netstandard2.0/README.md" />
       </ItemGroup>
index d8998ee..6184b0e 100644 (file)
@@ -46,5 +46,22 @@ namespace System.Text.Json
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static bool IsInRangeInclusive(int value, int lowerBound, int upperBound)
             => (uint)(value - lowerBound) <= (uint)(upperBound - lowerBound);
+
+        /// <summary>
+        /// Returns <see langword="true"/> iff <paramref name="value"/> is between
+        /// <paramref name="lowerBound"/> and <paramref name="upperBound"/>, inclusive.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool IsInRangeInclusive(double value, double lowerBound, double upperBound)
+            // For floating-point, do a direct comparison as it is more accurate than subtracting.
+            => (value >= lowerBound) && (value <= upperBound);
+
+        /// <summary>
+        /// Returns <see langword="true"/> iff <paramref name="value"/> is between
+        /// <paramref name="lowerBound"/> and <paramref name="upperBound"/>, inclusive.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool IsInRangeInclusive(JsonTokenType value, JsonTokenType lowerBound, JsonTokenType upperBound)
+            => (value - lowerBound) <= (upperBound - lowerBound);
     }
 }
index 9b6910b..f724941 100644 (file)
@@ -29,6 +29,12 @@ namespace System.Text.Json
             BytePositionInLine = bytePositionInLine;
         }
 
+        internal JsonReaderException(string message, in JsonReaderState state) : base(message)
+        {
+            LineNumber = state._lineNumber;
+            BytePositionInLine = state._bytePositionInLine;
+        }
+
         private JsonReaderException(SerializationInfo info, StreamingContext context) : base(info, context)
         {
             LineNumber = info.GetInt64("LineNumber");
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ArrayBufferWriter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ArrayBufferWriter.cs
new file mode 100644 (file)
index 0000000..7524f36
--- /dev/null
@@ -0,0 +1,178 @@
+// 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.
+
+using System.Buffers;
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization
+{
+    // Note: this is currently an internal class that will be replaced with a shared version.
+    internal sealed class ArrayBufferWriter<T> : IBufferWriter<T>, IDisposable
+    {
+        private T[] _rentedBuffer;
+        private int _index;
+
+        private const int MinimumBufferSize = 256;
+
+        public ArrayBufferWriter()
+        {
+            _rentedBuffer = ArrayPool<T>.Shared.Rent(MinimumBufferSize);
+            _index = 0;
+        }
+
+        public ArrayBufferWriter(int initialCapacity)
+        {
+            if (initialCapacity <= 0)
+                throw new ArgumentException(nameof(initialCapacity));
+
+            _rentedBuffer = ArrayPool<T>.Shared.Rent(initialCapacity);
+            _index = 0;
+        }
+
+        public ReadOnlyMemory<T> WrittenMemory
+        {
+            get
+            {
+                CheckIfDisposed();
+
+                return _rentedBuffer.AsMemory(0, _index);
+            }
+        }
+
+        public int WrittenCount
+        {
+            get
+            {
+                CheckIfDisposed();
+
+                return _index;
+            }
+        }
+
+        public int Capacity
+        {
+            get
+            {
+                CheckIfDisposed();
+
+                return _rentedBuffer.Length;
+            }
+        }
+
+        public int FreeCapacity
+        {
+            get
+            {
+                CheckIfDisposed();
+
+                return _rentedBuffer.Length - _index;
+            }
+        }
+
+        public void Clear()
+        {
+            CheckIfDisposed();
+
+            ClearHelper();
+        }
+
+        private void ClearHelper()
+        {
+            Debug.Assert(_rentedBuffer != null);
+
+            _rentedBuffer.AsSpan(0, _index).Clear();
+            _index = 0;
+        }
+
+        // Returns the rented buffer back to the pool
+        public void Dispose()
+        {
+            if (_rentedBuffer == null)
+            {
+                return;
+            }
+
+            ClearHelper();
+            ArrayPool<T>.Shared.Return(_rentedBuffer);
+            _rentedBuffer = null;
+        }
+
+        private void CheckIfDisposed()
+        {
+            if (_rentedBuffer == null)
+                ThrowHelper.ThrowObjectDisposedException(nameof(ArrayBufferWriter<T>));
+        }
+
+        public void Advance(int count)
+        {
+            CheckIfDisposed();
+
+            if (count < 0)
+                throw new ArgumentException(nameof(count));
+
+            if (_index > _rentedBuffer.Length - count)
+                ThrowInvalidOperationException(_rentedBuffer.Length);
+
+            _index += count;
+        }
+
+        public Memory<T> GetMemory(int sizeHint = 0)
+        {
+            CheckIfDisposed();
+
+            CheckAndResizeBuffer(sizeHint);
+            return _rentedBuffer.AsMemory(_index);
+        }
+
+        public Span<T> GetSpan(int sizeHint = 0)
+        {
+            CheckIfDisposed();
+
+            CheckAndResizeBuffer(sizeHint);
+            return _rentedBuffer.AsSpan(_index);
+        }
+
+        private void CheckAndResizeBuffer(int sizeHint)
+        {
+            Debug.Assert(_rentedBuffer != null);
+
+            if (sizeHint < 0)
+                throw new ArgumentException(nameof(sizeHint));
+
+            if (sizeHint == 0)
+            {
+                sizeHint = MinimumBufferSize;
+            }
+
+            int availableSpace = _rentedBuffer.Length - _index;
+
+            if (sizeHint > availableSpace)
+            {
+                int growBy = Math.Max(sizeHint, _rentedBuffer.Length);
+
+                int newSize = checked(_rentedBuffer.Length + growBy);
+
+                T[] oldBuffer = _rentedBuffer;
+
+                _rentedBuffer = ArrayPool<T>.Shared.Rent(newSize);
+
+                Debug.Assert(oldBuffer.Length >= _index);
+                Debug.Assert(_rentedBuffer.Length >= _index);
+
+                Span<T> previousBuffer = oldBuffer.AsSpan(0, _index);
+                previousBuffer.CopyTo(_rentedBuffer);
+                previousBuffer.Clear();
+                ArrayPool<T>.Shared.Return(oldBuffer);
+            }
+
+            Debug.Assert(_rentedBuffer.Length - _index > 0);
+            Debug.Assert(_rentedBuffer.Length - _index >= sizeHint);
+        }
+
+        private static void ThrowInvalidOperationException(int capacity)
+        {
+            throw new InvalidOperationException(SR.Format(SR.BufferWriterAdvancedTooFar, capacity));
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassMaterializer.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassMaterializer.cs
new file mode 100644 (file)
index 0000000..75d1f8c
--- /dev/null
@@ -0,0 +1,11 @@
+// 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.
+
+namespace System.Text.Json.Serialization
+{
+    internal abstract class ClassMaterializer
+    {
+        public abstract JsonClassInfo.ConstructorDelegate CreateConstructor(Type classType);
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ClassType.cs
new file mode 100644 (file)
index 0000000..9fa9eed
--- /dev/null
@@ -0,0 +1,16 @@
+// 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.
+
+namespace System.Text.Json.Serialization
+{
+    /// <summary>
+    /// Determines how a given class is treated when it is (de)serialized.
+    /// </summary>
+    internal enum ClassType
+    {
+        Object = 0,
+        Value = 1,
+        Enumerable = 2,
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultArrayConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultArrayConverter.cs
new file mode 100644 (file)
index 0000000..f48d347
--- /dev/null
@@ -0,0 +1,38 @@
+// 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.
+
+using System.Collections;
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class DefaultArrayConverter : JsonEnumerableConverter
+    {
+        public override IEnumerable CreateFromList(Type elementType, IList sourceList)
+        {
+            Array array;
+
+            if (sourceList.Count > 0 && sourceList[0] is Array probe)
+            {
+                array = Array.CreateInstance(probe.GetType(), sourceList.Count);
+
+                int i = 0;
+                foreach (IList child in sourceList)
+                {
+                    if (child is Array childArray)
+                    {
+                        array.SetValue(childArray, i++);
+                    }
+                }
+            }
+            else
+            {
+                array = Array.CreateInstance(elementType, sourceList.Count);
+                sourceList.CopyTo(array, 0);
+            }
+
+            return array;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultConverters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultConverters.cs
new file mode 100644 (file)
index 0000000..1f35c5d
--- /dev/null
@@ -0,0 +1,76 @@
+// 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.
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal static class DefaultConverters
+    {
+        private const int MaxTypeCode = 18;
+
+        private static readonly object[] s_Converters = new object[MaxTypeCode + 1] {
+            null,   // Empty 
+            null,   // Object
+            null,   // DBNull
+            new JsonValueConverterBoolean(),
+            new JsonValueConverterChar(),
+            new JsonValueConverterSByte(),
+            new JsonValueConverterByte(),
+            new JsonValueConverterInt16(),
+            new JsonValueConverterUInt16(),
+            new JsonValueConverterInt32(),
+            new JsonValueConverterUInt32(),
+            new JsonValueConverterInt64(),
+            new JsonValueConverterUInt64(),
+            new JsonValueConverterSingle(),
+            new JsonValueConverterDouble(),
+            new JsonValueConverterDecimal(),
+            new JsonValueConverterDateTime(),
+            null,   // (not a value)
+            new JsonValueConverterString()
+        };
+
+        private static readonly object[] s_NullableConverters = new object[MaxTypeCode + 1]
+        {
+            null,
+            null,
+            null,
+            new JsonValueConverterBooleanNullable(),
+            new JsonValueConverterCharNullable(),
+            new JsonValueConverterSByteNullable(),
+            new JsonValueConverterByteNullable(),
+            new JsonValueConverterInt16Nullable(),
+            new JsonValueConverterUInt16Nullable(),
+            new JsonValueConverterInt32Nullable(),
+            new JsonValueConverterUInt32Nullable(),
+            new JsonValueConverterInt64Nullable(),
+            new JsonValueConverterUInt64Nullable(),
+            new JsonValueConverterSingleNullable(),
+            new JsonValueConverterDoubleNullable(),
+            new JsonValueConverterDecimalNullable(),
+            new JsonValueConverterDateTimeNullable(),
+            null,
+            new JsonValueConverterString()
+        };
+
+        internal static object GetDefaultPropertyValueConverter(Type propertyType, bool isNullable)
+        {
+            object converter = null;
+
+            int typeCode = (int)Type.GetTypeCode(propertyType);
+            if (typeCode <= MaxTypeCode)
+            {
+                if (isNullable)
+                {
+                    converter = s_NullableConverters[typeCode];
+                }
+                else
+                {
+                    converter = s_Converters[typeCode];
+                }
+            }
+
+            return converter;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumConverter.cs
new file mode 100644 (file)
index 0000000..2007fa9
--- /dev/null
@@ -0,0 +1,106 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class DefaultEnumConverter<TValue> : JsonValueConverter<TValue>
+    {
+        public bool TreatAsString { get; private set; }
+
+        internal DefaultEnumConverter(bool treatAsString)
+        {
+            TreatAsString = treatAsString;
+        }
+
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out TValue value)
+        {
+            if (TreatAsString)
+            {
+                // Assume the token is a string
+                if (reader.TokenType != JsonTokenType.String)
+                {
+                    value = default;
+                    return false;
+                }
+
+#if !BUILDING_INBOX_LIBRARY 
+                // todo: add code here to handle NS2.0
+                throw new NotImplementedException(SR.EnumConverterNotImplemented);
+#else
+                string enumString = reader.GetString();
+                if (!Enum.TryParse(valueType, enumString, out object objValue))
+                {
+                    value = default;
+                    return false;
+                }
+
+                value = (TValue)objValue;
+                return true;
+#endif
+            }
+
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetUInt64(out ulong ulongValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = (TValue)Enum.ToObject(valueType, ulongValue);
+            return true;
+        }
+
+        public override void Write(TValue value, ref Utf8JsonWriter writer)
+        {
+            if (TreatAsString)
+            {
+                writer.WriteStringValue(value.ToString());
+            }
+            else
+            {
+                Type underlyingType = Enum.GetUnderlyingType(value.GetType());
+
+                if (underlyingType == typeof(ulong))
+                {
+                    // Keep +sign
+                    ulong ulongValue = Convert.ToUInt64(value);
+                    writer.WriteNumberValue(ulongValue);
+                }
+                else
+                {
+                    // long can hold the signed\unsigned values of other integer types
+                    long longValue = Convert.ToInt64(value);
+                    writer.WriteNumberValue(longValue);
+                }
+            }
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, TValue value, ref Utf8JsonWriter writer)
+        {
+            if (TreatAsString)
+            {
+                writer.WriteString(escapedPropertyName, value.ToString());
+            }
+            else
+            {
+                Type underlyingType = Enum.GetUnderlyingType(value.GetType());
+
+                if (underlyingType == typeof(ulong))
+                {
+                    // Use the ulong converter to prevent conversion into a signed\long value.
+                    ulong ulongValue = Convert.ToUInt64(value);
+                    writer.WriteNumber(escapedPropertyName, ulongValue);
+                }
+                else
+                {
+                    // long can hold the signed\unsigned values of other integer types.
+                    long longValue = Convert.ToInt64(value);
+                    writer.WriteNumber(escapedPropertyName, longValue);
+                }
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBoolean.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBoolean.cs
new file mode 100644 (file)
index 0000000..918c1e4
--- /dev/null
@@ -0,0 +1,33 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterBoolean : JsonValueConverter<bool>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out bool value)
+        {
+            if (reader.TokenType != JsonTokenType.True && reader.TokenType != JsonTokenType.False)
+            {
+                value = default;
+                return false;
+            }
+
+            value = reader.GetBoolean();
+            return true;
+        }
+
+        public override void Write(bool value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteBooleanValue(value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, bool value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteBoolean(escapedPropertyName, value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBooleanNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBooleanNullable.cs
new file mode 100644 (file)
index 0000000..64e6b51
--- /dev/null
@@ -0,0 +1,33 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterBooleanNullable : JsonValueConverter<bool?>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out bool? value)
+        {
+            if (reader.TokenType != JsonTokenType.True && reader.TokenType != JsonTokenType.False)
+            {
+                value = default;
+                return false;
+            }
+
+            value = reader.GetBoolean();
+            return true;
+        }
+
+        public override void Write(bool? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteBooleanValue(value.Value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, bool? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteBoolean(escapedPropertyName, value.Value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByte.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByte.cs
new file mode 100644 (file)
index 0000000..ce2a95f
--- /dev/null
@@ -0,0 +1,35 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterByte : JsonValueConverter<byte>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out byte value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetInt32(out int rawValue) ||
+                !JsonHelpers.IsInRangeInclusive(rawValue, byte.MinValue, byte.MaxValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = (byte)rawValue;
+            return true;
+        }
+
+        public override void Write(byte value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, byte value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByteNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByteNullable.cs
new file mode 100644 (file)
index 0000000..3178ae4
--- /dev/null
@@ -0,0 +1,35 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterByteNullable : JsonValueConverter<byte?>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out byte? value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetInt32(out int rawValue) ||
+                !JsonHelpers.IsInRangeInclusive(rawValue, byte.MinValue, byte.MaxValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = (byte?)rawValue;
+            return true;
+        }
+
+        public override void Write(byte? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value.Value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, byte? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value.Value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterChar.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterChar.cs
new file mode 100644 (file)
index 0000000..30aa4e7
--- /dev/null
@@ -0,0 +1,44 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+using System.Runtime.InteropServices;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterChar : JsonValueConverter<char>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out char value)
+        {
+            if (reader.TokenType != JsonTokenType.String)
+            {
+                value = default;
+                return false;
+            }
+
+            value = reader.GetString()[0];
+            return true;
+        }
+
+        public override void Write(char value, ref Utf8JsonWriter writer)
+        {
+#if BUILDING_INBOX_LIBRARY
+            Span<char> temp = MemoryMarshal.CreateSpan<char>(ref value, 1);
+            writer.WriteStringValue(temp);
+#else
+            writer.WriteStringValue(value.ToString());
+#endif
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, char value, ref Utf8JsonWriter writer)
+        {
+#if BUILDING_INBOX_LIBRARY
+            Span<char> temp = MemoryMarshal.CreateSpan<char>(ref value, 1);
+            writer.WriteString(escapedPropertyName, temp);
+#else
+            writer.WriteString(escapedPropertyName, value.ToString());
+#endif
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterCharNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterCharNullable.cs
new file mode 100644 (file)
index 0000000..0f9e87c
--- /dev/null
@@ -0,0 +1,49 @@
+// 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.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterCharNullable : JsonValueConverter<char?>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out char? value)
+        {
+            if (reader.TokenType != JsonTokenType.String)
+            {
+                value = default;
+                return false;
+            }
+
+            value = reader.GetString()[0];
+            return true;
+        }
+
+        public override void Write(char? value, ref Utf8JsonWriter writer)
+        {
+            Debug.Assert(value.HasValue); // nulls are filtered before calling here
+#if BUILDING_INBOX_LIBRARY
+            char tempChar = value.Value;
+            Span<char> tempSpan = MemoryMarshal.CreateSpan<char>(ref tempChar, 1);
+            writer.WriteStringValue(tempSpan);
+#else
+            writer.WriteStringValue(value.ToString());
+#endif
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, char? value, ref Utf8JsonWriter writer)
+        {
+            Debug.Assert(value.HasValue); // nulls are filtered before calling here
+#if BUILDING_INBOX_LIBRARY
+            char tempChar = value.Value;
+            Span<char> tempSpan = MemoryMarshal.CreateSpan<char>(ref tempChar, 1);
+            writer.WriteString(escapedPropertyName, tempSpan);
+#else
+            writer.WriteString(escapedPropertyName, value.ToString());
+#endif
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTime.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTime.cs
new file mode 100644 (file)
index 0000000..0ddfbb8
--- /dev/null
@@ -0,0 +1,37 @@
+// 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.
+
+using System.Buffers;
+using System.Buffers.Text;
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterDateTime : JsonValueConverter<DateTime>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out DateTime value)
+        {
+            if (reader.TokenType != JsonTokenType.String)
+            {
+                value = default;
+                return false;
+            }
+
+            ReadOnlySpan<byte> span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
+            return Utf8Parser.TryParse(span, out value, out int bytesConsumed, 'O') && span.Length == bytesConsumed;
+        }
+
+        public override void Write(DateTime value, ref Utf8JsonWriter writer)
+        {
+            // todo: use the appropriate DateTime method once available (https://github.com/dotnet/corefx/issues/34690)
+            writer.WriteStringValue(value.ToString("O"));
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, DateTime value, ref Utf8JsonWriter writer)
+        {
+            // todo: use the appropriate DateTime method once available (https://github.com/dotnet/corefx/issues/34690)
+            writer.WriteString(escapedPropertyName, value.ToString("O"));
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTimeNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTimeNullable.cs
new file mode 100644 (file)
index 0000000..7ef5183
--- /dev/null
@@ -0,0 +1,44 @@
+// 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.
+
+using System.Buffers;
+using System.Buffers.Text;
+using System.Diagnostics;
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterDateTimeNullable : JsonValueConverter<DateTime?>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out DateTime? value)
+        {
+            if (reader.TokenType != JsonTokenType.String)
+            {
+                value = default;
+                return false;
+            }
+
+            ReadOnlySpan<byte> span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
+            bool success = Utf8Parser.TryParse(span, out DateTime tempValue, out int bytesConsumed, 'O') && span.Length == bytesConsumed;
+            value = tempValue;
+            return success;
+        }
+
+        public override void Write(DateTime? value, ref Utf8JsonWriter writer)
+        {
+            Debug.Assert(value.HasValue); // nulls are filtered before calling here
+
+            // todo: use the appropriate DateTime method once available.
+            writer.WriteStringValue(value.Value.ToString("O"));
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, DateTime? value, ref Utf8JsonWriter writer)
+        {
+            Debug.Assert(value.HasValue); // nulls are filtered before calling here
+
+            // todo: use the appropriate DateTime method once available.
+            writer.WriteString(escapedPropertyName, value.Value.ToString("O"));
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimal.cs
new file mode 100644 (file)
index 0000000..b5dcb88
--- /dev/null
@@ -0,0 +1,32 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterDecimal : JsonValueConverter<decimal>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out decimal value)
+        {
+            if (reader.TokenType != JsonTokenType.Number)
+            {
+                value = default;
+                return false;
+            }
+
+            return reader.TryGetDecimal(out value);
+        }
+
+        public override void Write(decimal value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, decimal value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimalNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimalNullable.cs
new file mode 100644 (file)
index 0000000..376bac9
--- /dev/null
@@ -0,0 +1,34 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterDecimalNullable : JsonValueConverter<decimal?>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out decimal? value)
+        {
+            if (reader.TokenType != JsonTokenType.Number
+                || !reader.TryGetDecimal(out decimal rawValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = rawValue;
+            return true;
+        }
+
+        public override void Write(decimal? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value.Value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, decimal? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value.Value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDouble.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDouble.cs
new file mode 100644 (file)
index 0000000..91c7896
--- /dev/null
@@ -0,0 +1,32 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterDouble : JsonValueConverter<double>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out double value)
+        {
+            if (reader.TokenType != JsonTokenType.Number)
+            {
+                value = default;
+                return false;
+            }
+
+            return reader.TryGetDouble(out value);
+        }
+
+        public override void Write(double value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, double value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDoubleNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDoubleNullable.cs
new file mode 100644 (file)
index 0000000..5ecf94b
--- /dev/null
@@ -0,0 +1,34 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterDoubleNullable : JsonValueConverter<double?>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out double? value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetDouble(out double rawValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = rawValue;
+            return true;
+        }
+
+        public override void Write(double? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value.Value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, double? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value.Value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInfoInt16.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInfoInt16.cs
new file mode 100644 (file)
index 0000000..0ddac2d
--- /dev/null
@@ -0,0 +1,35 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterInt16 : JsonValueConverter<short>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out short value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetInt32(out int rawValue) ||
+                !JsonHelpers.IsInRangeInclusive(rawValue, short.MinValue, short.MaxValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = (short)rawValue;
+            return true;
+        }
+
+        public override void Write(short value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, short value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt16Nullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt16Nullable.cs
new file mode 100644 (file)
index 0000000..94e3fb6
--- /dev/null
@@ -0,0 +1,35 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterInt16Nullable : JsonValueConverter<short?>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out short? value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetInt32(out int rawValue) ||
+                !JsonHelpers.IsInRangeInclusive(rawValue, short.MinValue, short.MaxValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = (short?)rawValue;
+            return true;
+        }
+
+        public override void Write(short? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value.Value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, short? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value.Value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32.cs
new file mode 100644 (file)
index 0000000..420d6b4
--- /dev/null
@@ -0,0 +1,32 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterInt32 : JsonValueConverter<int>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out int value)
+        {
+            if (reader.TokenType != JsonTokenType.Number)
+            {
+                value = default;
+                return false;
+            }
+
+            return reader.TryGetInt32(out value);
+        }
+
+        public override void Write(int value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, int value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32Nullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32Nullable.cs
new file mode 100644 (file)
index 0000000..040eaee
--- /dev/null
@@ -0,0 +1,34 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterInt32Nullable : JsonValueConverter<int?>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out int? value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetInt32(out int rawValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = rawValue;
+            return true;
+        }
+
+        public override void Write(int? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value.Value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, int? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value.Value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64.cs
new file mode 100644 (file)
index 0000000..d5ca306
--- /dev/null
@@ -0,0 +1,32 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterInt64 : JsonValueConverter<long>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out long value)
+        {
+            if (reader.TokenType != JsonTokenType.Number)
+            {
+                value = default;
+                return false;
+            }
+
+            return reader.TryGetInt64(out value);
+        }
+
+        public override void Write(long value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, long value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64Nullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64Nullable.cs
new file mode 100644 (file)
index 0000000..15473a7
--- /dev/null
@@ -0,0 +1,34 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterInt64Nullable : JsonValueConverter<long?>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out long? value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetInt64(out long rawValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = rawValue;
+            return true;
+        }
+
+        public override void Write(long? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value.Value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, long? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value.Value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByte.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByte.cs
new file mode 100644 (file)
index 0000000..4bbb0ea
--- /dev/null
@@ -0,0 +1,35 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterSByte : JsonValueConverter<sbyte>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out sbyte value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetInt32(out int rawValue) ||
+                !JsonHelpers.IsInRangeInclusive(rawValue, sbyte.MinValue, sbyte.MaxValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = (sbyte)reader.GetInt32();
+            return true;
+        }
+
+        public override void Write(sbyte value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, sbyte value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByteNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByteNullable.cs
new file mode 100644 (file)
index 0000000..775c8b3
--- /dev/null
@@ -0,0 +1,35 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterSByteNullable : JsonValueConverter<sbyte?>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out sbyte? value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetInt32(out int rawValue) ||
+                !JsonHelpers.IsInRangeInclusive(rawValue, sbyte.MinValue, sbyte.MaxValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = (sbyte?)rawValue;
+            return true;
+        }
+
+        public override void Write(sbyte? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value.Value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, sbyte? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value.Value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingle.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingle.cs
new file mode 100644 (file)
index 0000000..48f75b7
--- /dev/null
@@ -0,0 +1,35 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterSingle : JsonValueConverter<float>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out float value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetDouble(out double rawValue) ||
+                !JsonHelpers.IsInRangeInclusive(rawValue, float.MinValue, float.MaxValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = (float)rawValue;
+            return true;
+        }
+
+        public override void Write(float value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, float value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingleNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingleNullable.cs
new file mode 100644 (file)
index 0000000..d38af42
--- /dev/null
@@ -0,0 +1,35 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterSingleNullable : JsonValueConverter<float?>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out float? value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetDouble(out double rawValue) ||
+                !JsonHelpers.IsInRangeInclusive(rawValue, float.MinValue, float.MaxValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = (float?)rawValue;
+            return true;
+        }
+
+        public override void Write(float? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value.Value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, float? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value.Value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterString.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterString.cs
new file mode 100644 (file)
index 0000000..e892602
--- /dev/null
@@ -0,0 +1,33 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterString : JsonValueConverter<string>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out string value)
+        {
+            if (reader.TokenType != JsonTokenType.String)
+            {
+                value = default;
+                return false;
+            }
+
+            value = reader.GetString();
+            return true;
+        }
+
+        public override void Write(string value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteStringValue(value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, string value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteString(escapedPropertyName, value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16.cs
new file mode 100644 (file)
index 0000000..a169731
--- /dev/null
@@ -0,0 +1,35 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterUInt16 : JsonValueConverter<ushort>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out ushort value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetInt32(out int rawValue) ||
+                !JsonHelpers.IsInRangeInclusive(rawValue, ushort.MinValue, ushort.MaxValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = (ushort)rawValue;
+            return true;
+        }
+
+        public override void Write(ushort value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, ushort value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16Nullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16Nullable.cs
new file mode 100644 (file)
index 0000000..b29b424
--- /dev/null
@@ -0,0 +1,35 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterUInt16Nullable : JsonValueConverter<ushort?>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out ushort? value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetInt32(out int rawValue) ||
+                !JsonHelpers.IsInRangeInclusive(rawValue, ushort.MinValue, ushort.MaxValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = (ushort?)rawValue;
+            return true;
+        }
+
+        public override void Write(ushort? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value.Value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, ushort? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value.Value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32.cs
new file mode 100644 (file)
index 0000000..1337c56
--- /dev/null
@@ -0,0 +1,32 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterUInt32 : JsonValueConverter<uint>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out uint value)
+        {
+            if (reader.TokenType != JsonTokenType.Number)
+            {
+                value = default;
+                return false;
+            }
+
+            return reader.TryGetUInt32(out value);
+        }
+
+        public override void Write(uint value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, uint value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32Nullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32Nullable.cs
new file mode 100644 (file)
index 0000000..f40d016
--- /dev/null
@@ -0,0 +1,34 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterUInt32Nullable : JsonValueConverter<uint?>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out uint? value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetUInt32(out uint rawValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = rawValue;
+            return true;
+        }
+
+        public override void Write(uint? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value.Value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, uint? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value.Value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64.cs
new file mode 100644 (file)
index 0000000..60ff3d1
--- /dev/null
@@ -0,0 +1,32 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterUInt64 : JsonValueConverter<ulong>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out ulong value)
+        {
+            if (reader.TokenType != JsonTokenType.Number)
+            {
+                value = default;
+                return false;
+            }
+
+            return reader.TryGetUInt64(out value);
+        }
+
+        public override void Write(ulong value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, ulong value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64Nullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64Nullable.cs
new file mode 100644 (file)
index 0000000..5c1a6fd
--- /dev/null
@@ -0,0 +1,34 @@
+// 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.
+
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization.Converters
+{
+    internal sealed class JsonValueConverterUInt64Nullable : JsonValueConverter<ulong?>
+    {
+        public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out ulong? value)
+        {
+            if (reader.TokenType != JsonTokenType.Number ||
+                !reader.TryGetUInt64(out ulong rawValue))
+            {
+                value = default;
+                return false;
+            }
+
+            value = rawValue;
+            return true;
+        }
+
+        public override void Write(ulong? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumberValue(value.Value);
+        }
+
+        public override void Write(Span<byte> escapedPropertyName, ulong? value, ref Utf8JsonWriter writer)
+        {
+            writer.WriteNumber(escapedPropertyName, value.Value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs
new file mode 100644 (file)
index 0000000..16d7313
--- /dev/null
@@ -0,0 +1,68 @@
+// 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.
+
+using System.Buffers;
+using System.Reflection;
+
+namespace System.Text.Json.Serialization
+{
+    internal partial class JsonClassInfo
+    {
+        private void AddProperty(Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options)
+        {
+            Type collectionElementType = null;
+            ClassType propertyClassType = GetClassType(propertyType);
+            if (propertyClassType == ClassType.Enumerable)
+            {
+                collectionElementType = GetElementType(propertyType);
+                // todo: if collectionElementType is object, create loosely-typed collection (JsonArray).
+            }
+
+            // Create the JsonPropertyInfo<TType, TProperty>
+            Type genericPropertyType = typeof(JsonPropertyInfo<,>).MakeGenericType(classType, propertyType);
+            JsonPropertyInfo jsonInfo = (JsonPropertyInfo)Activator.CreateInstance(
+                genericPropertyType,
+                BindingFlags.Instance | BindingFlags.NonPublic,
+                binder: null,
+                new object[] { classType, propertyType, propertyInfo, collectionElementType, options },
+                culture: null);
+
+            if (propertyInfo != null)
+            {
+                string propertyName = propertyInfo.Name;
+
+                // At this point propertyName is valid UTF16, so just call the simple UTF16->UTF8 encoder.
+                byte[] propertyNameBytes = Encoding.UTF8.GetBytes(propertyName);
+                jsonInfo._name = propertyNameBytes;
+
+                // Cache the escaped name.
+                int valueIdx = JsonWriterHelper.NeedsEscaping(propertyNameBytes);
+                if (valueIdx == -1)
+                {
+                    jsonInfo._escapedName = propertyNameBytes;
+                }
+                else
+                {
+                    int length = JsonWriterHelper.GetMaxEscapedLength(propertyNameBytes.Length, valueIdx);
+
+                    byte[] tempArray = ArrayPool<byte>.Shared.Rent(length);
+
+                    JsonWriterHelper.EscapeString(propertyNameBytes, tempArray, valueIdx, out int written);
+                    jsonInfo._escapedName = new byte[written];
+                    tempArray.CopyTo(jsonInfo._escapedName, 0);
+
+                    // We clear the array because it is "user data" (although a property name).
+                    ArrayPool<byte>.Shared.Return(tempArray, clearArray: true);
+                }
+
+                _property_refs.Add(new PropertyRef(GetKey(propertyNameBytes), jsonInfo));
+            }
+            else
+            {
+                // A single property or an IEnumerable
+                _property_refs.Add(new PropertyRef(0, jsonInfo));
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs
new file mode 100644 (file)
index 0000000..94d48d5
--- /dev/null
@@ -0,0 +1,237 @@
+// 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.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace System.Text.Json.Serialization
+{
+    internal sealed partial class JsonClassInfo
+    {
+        // The length of the property name embedded in the key (in bytes).
+        private const int PropertyNameKeyLength = 6;
+
+        private readonly List<PropertyRef> _property_refs = new List<PropertyRef>();
+        private readonly List<PropertyRef> _property_refs_sorted = new List<PropertyRef>();
+
+        internal delegate object ConstructorDelegate();
+        internal ConstructorDelegate CreateObject { get; private set; }
+
+        internal ClassType ClassType { get; private set; }
+
+        // If enumerable, the JsonClassInfo for the element type.
+        internal JsonClassInfo ElementClassInfo { get; private set; }
+
+        public Type Type { get; private set; }
+
+        internal JsonClassInfo(Type type, JsonSerializerOptions options)
+        {
+            Type = type;
+            ClassType = GetClassType(type);
+
+            CreateObject = options.ClassMaterializerStrategy.CreateConstructor(type);
+
+            // Ignore properties on enumerable.
+            if (ClassType == ClassType.Object)
+            {
+                foreach (PropertyInfo propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
+                {
+                    // For now we only support public getters\setters
+                    if (propertyInfo.GetMethod?.IsPublic == true ||
+                        propertyInfo.SetMethod?.IsPublic == true)
+                    {
+                        AddProperty(propertyInfo.PropertyType, propertyInfo, type, options);
+                    }
+                }
+            }
+            else if (ClassType == ClassType.Enumerable)
+            {
+                // Add a single property that maps to the class type so we can have policies applied.
+                AddProperty(type, propertyInfo : null, type, options);
+
+                // Create a ClassInfo that maps to the element type which is used for (de)serialization and policies.
+                Type elementType = GetElementType(type);
+                ElementClassInfo = options.GetOrAddClass(elementType);
+            }
+            else
+            {
+                Debug.Assert(ClassType == ClassType.Value);
+
+                // Add a single property that maps to the class type so we can have policies applied.
+                AddProperty(type, propertyInfo: null, type, options);
+            }
+        }
+
+        internal JsonPropertyInfo GetProperty(ReadOnlySpan<byte> propertyName, int propertyIndex)
+        {
+            ulong key = GetKey(propertyName);
+            JsonPropertyInfo info = null;
+
+            // First try sorted lookup.
+            int count = _property_refs_sorted.Count;
+            if (count != 0)
+            {
+                int iForward = propertyIndex;
+                int iBackward = propertyIndex - 1;
+                while (iForward < count || (iBackward >= 0 && iBackward < count))
+                {
+                    if (iForward < count)
+                    {
+                        if (TryIsPropertyRefEqual(_property_refs_sorted, propertyName, key, iForward, out info))
+                        {
+                            return info;
+                        }
+                        ++iForward;
+                    }
+
+                    if (iBackward >= 0)
+                    {
+                        if (TryIsPropertyRefEqual(_property_refs_sorted, propertyName, key, iBackward, out info))
+                        {
+                            return info;
+                        }
+                        --iBackward;
+                    }
+                }
+            }
+
+            // Then try fallback
+            for (int i = 0; i < _property_refs.Count; i++)
+            {
+                if (TryIsPropertyRefEqual(_property_refs, propertyName, key, i, out info))
+                {
+                    break;
+                }
+            }
+
+            if (info != null)
+            {
+                _property_refs_sorted.Add(new PropertyRef(key, info));
+            }
+
+            return info;
+        }
+
+        internal JsonPropertyInfo GetPolicyProperty()
+        {
+            Debug.Assert(_property_refs.Count == 1);
+            return _property_refs[0].Info;
+        }
+
+        internal JsonPropertyInfo GetProperty(int index)
+        {
+            Debug.Assert(index < _property_refs.Count);
+            return _property_refs[index].Info;
+        }
+
+        internal int PropertyCount
+        {
+            get
+            {
+                return _property_refs.Count;
+            }
+        }
+
+        private static bool TryIsPropertyRefEqual(List<PropertyRef> list, ReadOnlySpan<byte> propertyName, ulong key, int index, out JsonPropertyInfo info)
+        {
+            if (key == list[index].Key)
+            {
+                if (propertyName.Length <= PropertyNameKeyLength ||
+                    // We compare the whole name, although we could skip the first 6 bytes (but it's likely not any faster)
+                    propertyName.SequenceEqual((ReadOnlySpan<byte>)list[index].Info._name))
+                {
+                    info = list[index].Info;
+                    return true;
+                }
+            }
+
+            info = null;
+            return false;
+        }
+
+        private static ulong GetKey(ReadOnlySpan<byte> propertyName)
+        {
+            Debug.Assert(propertyName.Length > 0);
+
+            ulong key;
+            int length = propertyName.Length;
+
+            // Embed the propertyName in the first 6 bytes of the key.
+            if (length > 3)
+            {
+                key = MemoryMarshal.Read<uint>(propertyName);
+                if (length > 4)
+                {
+                    key |= (ulong)propertyName[4] << 32;
+                }
+                if (length > 5)
+                {
+                    key |= (ulong)propertyName[5] << 40;
+                }
+            }
+            else if (length > 1)
+            {
+                key = MemoryMarshal.Read<ushort>(propertyName);
+                if (length > 2)
+                {
+                    key |= (ulong)propertyName[2] << 16;
+                }
+            }
+            else
+            {
+                key = propertyName[0];
+            }
+
+            // Embed the propertyName length in the last two bytes.
+            key |= (ulong)propertyName.Length << 48;
+            return key;
+
+        }
+
+        private static Type GetElementType(Type propertyType)
+        {
+            Type elementType = null;
+            if (typeof(IEnumerable).IsAssignableFrom(propertyType))
+            {
+                elementType = propertyType.GetElementType();
+                if (elementType == null)
+                {
+                    if (propertyType.IsGenericType)
+                    {
+                        elementType = propertyType.GetGenericArguments()[0];
+                    }
+                    else
+                    {
+                        // Unable to determine collection type; attempt to use object which will be used to create loosely-typed collection.
+                        return typeof(object);
+                    }
+                }
+            }
+
+            return elementType;
+        }
+
+        internal static ClassType GetClassType(Type type)
+        {
+            Debug.Assert(type != null);
+
+            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
+            {
+                type = Nullable.GetUnderlyingType(type);
+            }
+
+            // A Type is considered a value if it implements IConvertible.
+            if (typeof(IConvertible).IsAssignableFrom(type))
+                return ClassType.Value;
+
+            if (typeof(IEnumerable).IsAssignableFrom(type))
+                return ClassType.Enumerable;
+
+            return ClassType.Object;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonEnumerableConverter.cs
new file mode 100644 (file)
index 0000000..dd92cec
--- /dev/null
@@ -0,0 +1,13 @@
+// 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.
+
+using System.Collections;
+
+namespace System.Text.Json.Serialization.Policies
+{
+    internal abstract class JsonEnumerableConverter
+    {
+        public abstract IEnumerable CreateFromList(Type elementType, IList sourceList);
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
new file mode 100644 (file)
index 0000000..23b7a42
--- /dev/null
@@ -0,0 +1,91 @@
+// 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.
+
+using System.Reflection;
+using System.Text.Json.Serialization.Converters;
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization
+{
+    internal abstract class JsonPropertyInfo
+    {
+        // For now, just a global converter.
+        private static JsonEnumerableConverter s_jsonEnumerableConverter = new DefaultArrayConverter();
+
+        internal ClassType ClassType;
+
+        internal byte[] _name = default;
+        internal byte[] _escapedName = default;
+
+        internal bool HasGetter { get; set; }
+        internal bool HasSetter { get; set; }
+
+        public ReadOnlySpan<byte> EscapedName => _escapedName;
+        public ReadOnlySpan<byte> Name => _name;
+
+        // todo: to minimize hashtable lookups, cache JsonClassInfo:
+        //public JsonClassInfo ClassInfo;
+
+        // Constructor used for internal identifiers
+        internal JsonPropertyInfo() { }
+
+        internal JsonPropertyInfo(Type parentClassType, Type propertyType, PropertyInfo propertyInfo, Type elementType, JsonSerializerOptions options)
+        { 
+            ParentClassType = parentClassType;
+            PropertyType = propertyType;
+            PropertyInfo = propertyInfo;
+            ClassType = JsonClassInfo.GetClassType(propertyType);
+            if (elementType != null)
+            {
+                ElementClassInfo = options.GetOrAddClass(elementType);
+            }
+
+            IsNullableType = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>);
+            CanBeNull = IsNullableType || !propertyType.IsValueType;
+        }
+
+        internal JsonEnumerableConverter EnumerableConverter { get; private set; }
+
+        public PropertyInfo PropertyInfo { get; private set; }
+
+        internal Type PropertyType { get; private set; }
+
+        internal Type ParentClassType { get; private set; }
+
+        internal JsonClassInfo ElementClassInfo { get; private set; }
+
+        internal bool IsNullableType { get; private set; }
+
+        internal bool CanBeNull { get; private set; }
+
+        internal bool IgnoreNullPropertyValueOnRead(JsonSerializerOptions options)
+        {
+            return options.IgnoreNullPropertyValueOnRead;
+        }
+
+        internal bool IgnoreNullPropertyValueOnWrite(JsonSerializerOptions options)
+        {
+            return options.IgnoreNullPropertyValueOnWrite;
+        }
+
+        internal abstract object GetValueAsObject(object obj, JsonSerializerOptions options);
+        internal abstract void SetValueAsObject(object obj, object value, JsonSerializerOptions options);
+
+        internal abstract void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader);
+
+        internal abstract void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader);
+
+        internal abstract void Write(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer);
+
+        internal abstract void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer);
+
+        internal virtual void GetPolicies(JsonSerializerOptions options)
+        {
+            if (PropertyType.IsArray)
+            {
+                EnumerableConverter = s_jsonEnumerableConverter;
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfTClassTProperty.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfTClassTProperty.cs
new file mode 100644 (file)
index 0000000..b9cce4e
--- /dev/null
@@ -0,0 +1,194 @@
+// 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.
+
+using System.Diagnostics;
+using System.Reflection;
+
+namespace System.Text.Json.Serialization
+{
+    /// <summary>
+    /// Represents a strongly-typed property to prevent boxing and to create a direct delegate to the getter\setter.
+    /// </summary>
+    internal class JsonPropertyInfo<TClass, TProperty> : JsonPropertyInfo<TProperty>
+    {
+        private bool _isPropertyPolicy;
+        internal Func<TClass, TProperty> Get { get; private set; }
+        internal Action<TClass, TProperty> Set { get; private set; }
+
+        // Constructor used for internal identifiers
+        internal JsonPropertyInfo() { }
+
+        internal JsonPropertyInfo(Type classType, Type propertyType, PropertyInfo propertyInfo, Type elementType, JsonSerializerOptions options) :
+            base(classType, propertyType, propertyInfo, elementType, options)
+        {
+            if (propertyInfo != null)
+            {
+                if (propertyInfo.GetMethod?.IsPublic == true)
+                {
+                    HasGetter = true;
+                    Get = (Func<TClass, TProperty>)Delegate.CreateDelegate(typeof(Func<TClass, TProperty>), propertyInfo.GetGetMethod());
+                }
+
+                if (propertyInfo.SetMethod?.IsPublic == true)
+                {
+                    HasSetter = true;
+                    Set = (Action<TClass, TProperty>)Delegate.CreateDelegate(typeof(Action<TClass, TProperty>), propertyInfo.GetSetMethod());
+                }
+            }
+            else
+            {
+                _isPropertyPolicy = true;
+                HasGetter = true;
+                HasSetter = true;
+            }
+
+            GetPolicies(options);
+        }
+
+        internal override object GetValueAsObject(object obj, JsonSerializerOptions options)
+        {
+            if (_isPropertyPolicy)
+            {
+                return obj;
+            }
+
+            Debug.Assert(Get != null);
+            return Get((TClass)obj);
+        }
+
+        internal override void SetValueAsObject(object obj, object value, JsonSerializerOptions options)
+        {
+            Debug.Assert(Set != null);
+            TProperty typedValue = (TProperty)value;
+
+            if (typedValue != null || !IgnoreNullPropertyValueOnWrite(options))
+            {
+                Set((TClass)obj, (TProperty)value);
+            }
+        }
+
+        internal override void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
+        {
+            if (ElementClassInfo != null)
+            {
+                // Forward the setter to the value-based JsonPropertyInfo.
+                JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
+                propertyInfo.ReadEnumerable(tokenType, options, ref state, ref reader);
+            }
+            else if (HasSetter)
+            {
+                if (ValueConverter != null)
+                {
+                    Type propertyType = PropertyType;
+                    if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
+                    {
+                        propertyType = Nullable.GetUnderlyingType(propertyType);
+                    }
+
+                    if (ValueConverter.TryRead(propertyType, ref reader, out TProperty value))
+                    {
+                        if (state.Current.ReturnValue == null)
+                        {
+                            state.Current.ReturnValue = value;
+                        }
+                        else
+                        {
+                            if (value != null || !IgnoreNullPropertyValueOnRead(options))
+                            {
+                                Set((TClass)state.Current.ReturnValue, value);
+                            }
+                        }
+
+                        return;
+                    }
+                }
+
+                ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(PropertyType, reader, state);
+            }
+        }
+
+        internal override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
+        {
+            if (ValueConverter != null)
+            {
+                Type propertyType = PropertyType;
+                if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
+                {
+                    propertyType = Nullable.GetUnderlyingType(propertyType);
+                }
+
+                if (ValueConverter.TryRead(propertyType, ref reader, out TProperty value))
+                {
+                    ReadStackFrame.SetReturnValue(value, options, ref state.Current);
+                    return;
+                }
+            }
+
+            ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(PropertyType, reader, state);
+        }
+
+        // todo: have the caller check if current.Enumerator != null and call WriteEnumerable of the underlying property directly to avoid an extra virtual call.
+        internal override void Write(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer)
+        {
+            if (current.Enumerator != null)
+            {
+                // Forward the setter to the value-based JsonPropertyInfo.
+                JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
+                propertyInfo.WriteEnumerable(options, ref current, ref writer);
+            }
+            else if (HasGetter)
+            {
+                TProperty value;
+                if (_isPropertyPolicy)
+                {
+                    value = (TProperty)current.CurrentValue;
+                }
+                else
+                {
+                    value = Get((TClass)current.CurrentValue);
+                }
+
+                if (value == null)
+                {
+                    if (_escapedName == null)
+                    {
+                        writer.WriteNullValue();
+                    }
+                    else if (!IgnoreNullPropertyValueOnWrite(options))
+                    {
+                        writer.WriteNull(_escapedName);
+                    }
+                }
+                else if (ValueConverter != null)
+                {
+                    if (_escapedName != null)
+                    {
+                        ValueConverter.Write(_escapedName, value, ref writer);
+                    }
+                    else
+                    {
+                        ValueConverter.Write(value, ref writer);
+                    }
+                }
+            }
+        }
+
+        internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer)
+        {
+            if (ValueConverter != null)
+            {
+                Debug.Assert(current.Enumerator != null);
+                TProperty value = (TProperty)current.Enumerator.Current;
+                if (value == null)
+                {
+                    writer.WriteNullValue();
+                }
+                else
+                {
+                    ValueConverter.Write(value, ref writer);
+                }
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfTProperty.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfTProperty.cs
new file mode 100644 (file)
index 0000000..fd030ea
--- /dev/null
@@ -0,0 +1,47 @@
+// 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.
+
+using System.Reflection;
+using System.Text.Json.Serialization.Converters;
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization
+{
+    internal abstract class JsonPropertyInfo<TProperty> : JsonPropertyInfo
+    {
+        // For now, just a global converter.
+        private static readonly JsonValueConverter<TProperty> s_enumConverter = new DefaultEnumConverter<TProperty>(false);
+
+        // Constructor used for internal identifiers
+        internal JsonPropertyInfo() { }
+
+        internal JsonPropertyInfo(Type classType, Type propertyType, PropertyInfo propertyInfo, Type elementType, JsonSerializerOptions options) :
+            base(classType, propertyType, propertyInfo, elementType, options)
+        { }
+
+        public JsonValueConverter<TProperty> ValueConverter { get; internal set; }
+
+        internal override void GetPolicies(JsonSerializerOptions options)
+        {
+            Type propertyType = PropertyType;
+            bool isNullable = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>);
+            if (isNullable)
+            {
+                propertyType = Nullable.GetUnderlyingType(propertyType);
+            }
+
+            // For Enums, support both the type Enum plus strongly-typed Enums.
+            if (propertyType.IsEnum || propertyType == typeof(Enum))
+            {
+                ValueConverter = s_enumConverter;
+            }
+            else
+            {
+                ValueConverter = (JsonValueConverter<TProperty>)DefaultConverters.GetDefaultPropertyValueConverter(propertyType, isNullable);
+            }
+
+            base.GetPolicies(options);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs
new file mode 100644 (file)
index 0000000..74b6c6c
--- /dev/null
@@ -0,0 +1,141 @@
+// 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.
+
+using System.Collections;
+using System.Diagnostics;
+using System.Text.Json.Serialization.Policies;
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        private static void HandleStartArray(
+            JsonSerializerOptions options,
+            ref Utf8JsonReader reader,
+            ref ReadStack state)
+        {
+            if (state.Current.Skip())
+            {
+                // The array is not being applied to the object.
+                state.Push();
+                state.Current.Drain = true;
+                return;
+            }
+
+            Type arrayType = state.Current.JsonPropertyInfo.PropertyType;
+            if (!typeof(IEnumerable).IsAssignableFrom(arrayType) || (arrayType.IsArray && arrayType.GetArrayRank() > 1))
+            {
+                ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(arrayType, reader, state);
+            }
+
+            Debug.Assert(state.Current.IsPropertyEnumerable());
+            if (state.Current.IsPropertyEnumerable())
+            {
+                if (state.Current.EnumerableCreated)
+                {
+                    // A nested json array so push a new stack frame.
+                    Type elementType = state.Current.JsonClassInfo.ElementClassInfo.GetPolicyProperty().PropertyType;
+
+                    state.Push();
+
+                    state.Current.JsonClassInfo = options.GetOrAddClass(elementType);
+                    state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetPolicyProperty();
+                    state.Current.PopStackOnEndArray = true;
+                }
+                else
+                {
+                    state.Current.EnumerableCreated = true;
+                }
+
+                // If current property is already set (from a constructor, for example) leave as-is
+                if (state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue, options) == null)
+                {
+                    // Create the enumerable.
+                    object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state, options);
+                    if (value != null)
+                    {
+                        if (state.Current.ReturnValue != null)
+                        {
+                            state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value, options);
+                        }
+                        else
+                        {
+                            // Primitive arrays being returned without object
+                            state.Current.SetReturnValue(value, options);
+                        }
+                    }
+                }
+            }
+        }
+
+        private static bool HandleEndArray(
+            JsonSerializerOptions options,
+            ref ReadStack state)
+        {
+            bool lastFrame = state.IsLastFrame;
+
+            if (state.Current.Drain)
+            {
+                // The array is not being applied to the object.
+                state.Pop();
+                return lastFrame;
+            }
+
+            IEnumerable value = ReadStackFrame.GetEnumerableValue(state.Current);
+            if (value == null)
+            {
+                // We added the items to the list property already.
+                state.Current.ResetProperty();
+                return false;
+            }
+
+            bool setPropertyDirectly;
+            if (state.Current.TempEnumerableValues != null)
+            {
+                JsonEnumerableConverter converter = state.Current.JsonPropertyInfo.EnumerableConverter;
+                Debug.Assert(converter != null);
+
+                Type elementType = state.Current.GetElementType();
+                value = converter.CreateFromList(elementType, (IList)value);
+                setPropertyDirectly = true;
+            }
+            else
+            {
+                setPropertyDirectly = false;
+            }
+
+            bool valueReturning = state.Current.PopStackOnEndArray;
+            if (state.Current.PopStackOnEndArray)
+            {
+                state.Pop();
+            }
+
+            if (lastFrame)
+            {
+                if (state.Current.ReturnValue == null)
+                {
+                    // Returning a converted list or object.
+                    state.Current.Reset();
+                    state.Current.ReturnValue = value;
+                    return true;
+                }
+                else if (state.Current.IsEnumerable())
+                {
+                    // Returning a non-converted list.
+                    return true;
+                }
+                // else there must be an outer object, so we'll return false here.
+            }
+
+            ReadStackFrame.SetReturnValue(value, options, ref state.Current, setPropertyDirectly: setPropertyDirectly);
+
+            if (!valueReturning)
+            {
+                state.Current.ResetProperty();
+            }
+
+            return false;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs
new file mode 100644 (file)
index 0000000..7f968b9
--- /dev/null
@@ -0,0 +1,41 @@
+// 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.
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options)
+        {
+            if (state.Current.Skip())
+            {
+                return false;
+            }
+
+            JsonPropertyInfo propertyInfo = state.Current.JsonPropertyInfo;
+            if (!propertyInfo.CanBeNull)
+            {
+                ThrowHelper.ThrowJsonReaderException_DeserializeCannotBeNull(reader, state);
+            }
+
+            if (state.Current.IsEnumerable() || state.Current.IsPropertyEnumerable())
+            {
+                ReadStackFrame.SetReturnValue(null, options, ref state.Current);
+                return false;
+            }
+
+            if (state.Current.ReturnValue == null)
+            {
+                return true;
+            }
+
+            if (!propertyInfo.IgnoreNullPropertyValueOnRead(options))
+            {
+                state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null, options);
+            }
+
+            return false;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs
new file mode 100644 (file)
index 0000000..3ca57dd
--- /dev/null
@@ -0,0 +1,60 @@
+// 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.
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        private static void HandleStartObject(JsonSerializerOptions options, ref ReadStack state)
+        {
+            if (state.Current.Skip())
+            {
+                state.Push();
+                state.Current.Drain = true;
+                return;
+            }
+
+            if (state.Current.IsEnumerable() || state.Current.IsPropertyEnumerable())
+            {
+                // An array of objects either on the current property or on a list
+                Type objType = state.Current.GetElementType();
+                state.Push();
+                state.Current.JsonClassInfo = options.GetOrAddClass(objType);
+            }
+            else if (state.Current.JsonPropertyInfo != null)
+            {
+                // Nested object
+                Type objType = state.Current.JsonPropertyInfo.PropertyType;
+                state.Push();
+                state.Current.JsonClassInfo = options.GetOrAddClass(objType);
+            }
+
+            JsonClassInfo classInfo = state.Current.JsonClassInfo;
+            state.Current.ReturnValue = classInfo.CreateObject();
+        }
+
+        private static bool HandleEndObject(JsonSerializerOptions options, ref ReadStack state)
+        {
+            bool isLastFrame = state.IsLastFrame;
+            if (state.Current.Drain)
+            {
+                state.Pop();
+                return isLastFrame;
+            }
+
+            object value = state.Current.ReturnValue;
+
+            if (isLastFrame)
+            {
+                state.Current.Reset();
+                state.Current.ReturnValue = value;
+                return true;
+            }
+
+            state.Pop();
+            ReadStackFrame.SetReturnValue(value, options, ref state.Current);
+            return false;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs
new file mode 100644 (file)
index 0000000..c0f778a
--- /dev/null
@@ -0,0 +1,21 @@
+// 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.
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        private static bool HandleValue(JsonTokenType tokenType, JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state)
+        {
+            if (state.Current.Skip())
+            {
+                return false;
+            }
+
+            bool lastCall = (!state.Current.IsEnumerable() && !state.Current.IsPropertyEnumerable() && state.Current.ReturnValue == null);
+            state.Current.JsonPropertyInfo.Read(tokenType, options, ref state, ref reader);
+            return lastCall;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs
new file mode 100644 (file)
index 0000000..0be9000
--- /dev/null
@@ -0,0 +1,44 @@
+// 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.
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        public static TValue Parse<TValue>(ReadOnlySpan<byte> utf8Json, JsonSerializerOptions options = null)
+        {
+            if (utf8Json == null)
+                throw new ArgumentNullException(nameof(utf8Json));
+
+            return (TValue)ParseCore(utf8Json, typeof(TValue), options);
+        }
+
+        public static object Parse(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerOptions options = null)
+        {
+            if (utf8Json == null)
+                throw new ArgumentNullException(nameof(utf8Json));
+
+            return ParseCore(utf8Json, returnType, options);
+        }
+
+        private static object ParseCore(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerOptions options)
+        {
+            if (options == null)
+                options = s_defaultSettings;
+
+            var readerState = new JsonReaderState(options: options.ReaderOptions);
+            var reader = new Utf8JsonReader(utf8Json, isFinalBlock: true, readerState);
+            object result = ReadCore(returnType, options, ref reader);
+
+            readerState = reader.CurrentState;
+            if (readerState.BytesConsumed != utf8Json.Length)
+            {
+                throw new JsonReaderException(SR.Format(SR.DeserializeDataRemaining,
+                    utf8Json.Length, utf8Json.Length - readerState.BytesConsumed), readerState);
+            }
+
+            return result;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs
new file mode 100644 (file)
index 0000000..0b724c4
--- /dev/null
@@ -0,0 +1,141 @@
+// 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.
+
+using System.Buffers;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        private const int HalfMaxValue = int.MaxValue / 2;
+
+        public static ValueTask<TValue> ReadAsync<TValue>(Stream utf8Json, JsonSerializerOptions options = null, CancellationToken cancellationToken = default)
+        {
+            if (utf8Json == null)
+                throw new ArgumentNullException(nameof(utf8Json));
+
+            return ReadAsync<TValue>(utf8Json, typeof(TValue), options, cancellationToken);
+        }
+
+        public static ValueTask<object> ReadAsync(Stream utf8Json, Type returnType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default)
+        {
+            if (utf8Json == null)
+                throw new ArgumentNullException(nameof(utf8Json));
+
+            if (returnType == null)
+                throw new ArgumentNullException(nameof(returnType));
+
+            return ReadAsync<object>(utf8Json, returnType, options, cancellationToken);
+        }
+
+        private static async ValueTask<TValue> ReadAsync<TValue>(Stream utf8Json, Type returnType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default)
+        {
+            if (options == null)
+                options = s_defaultSettings;
+
+            ReadStack state = default;
+            JsonClassInfo classInfo = options.GetOrAddClass(returnType);
+            state.Current.JsonClassInfo = classInfo;
+            if (classInfo.ClassType != ClassType.Object)
+            {
+                state.Current.JsonPropertyInfo = classInfo.GetPolicyProperty();
+            }
+
+            var readerState = new JsonReaderState(options: options.ReaderOptions);
+
+            int bytesRemaining = 0;
+            int bytesRead;
+
+            // todo: switch to ArrayBuffer implementation to handle and simplify the allocs?
+            byte[] buffer = ArrayPool<byte>.Shared.Rent(options.EffectiveBufferSize);
+            int bufferSize = buffer.Length;
+            int deserializeBufferSize;
+            bool isFinalBlock;
+
+            try
+            {
+                do
+                {
+                    int bytesToRead = bufferSize - bytesRemaining;
+                    bytesRead = await utf8Json.ReadAsync(buffer, bytesRemaining, bytesToRead, cancellationToken).ConfigureAwait(false);
+
+                    deserializeBufferSize = bytesRemaining + bytesRead;
+                    isFinalBlock = (bytesRead == 0);
+
+                    ReadCore(
+                        ref readerState,
+                        isFinalBlock,
+                        buffer,
+                        deserializeBufferSize,
+                        options,
+                        ref state);
+
+                    int bytesConsumed = (int)readerState.BytesConsumed;
+                    bytesRemaining = deserializeBufferSize - bytesConsumed;
+
+                    if (isFinalBlock)
+                    {
+                        break;
+                    }
+
+                    // Check if we need to shift or expand the buffer because there wasn't enough data to complete deserialization.
+                    if (bytesConsumed <= (bufferSize / 2))
+                    {
+                        // We have less than half the buffer available, double the buffer size.
+                        bufferSize = (bufferSize < HalfMaxValue) ? bufferSize * 2 : int.MaxValue;
+
+                        byte[] dest = ArrayPool<byte>.Shared.Rent(bufferSize);
+                        bufferSize = dest.Length;
+                        if (bytesRemaining > 0)
+                        {
+                            // Copy the unprocessed data to the new buffer while shifting the processed bytes.
+                            Buffer.BlockCopy(buffer, bytesConsumed, dest, 0, bytesRemaining);
+                        }
+
+                        ArrayPool<byte>.Shared.Return(buffer, clearArray: true);
+                        buffer = dest;
+                    }
+                    else if (bytesRemaining > 0)
+                    {
+                        // Shift the processed bytes to the beginning of buffer to make more room.
+                        Buffer.BlockCopy(buffer, bytesConsumed, buffer, 0, bytesRemaining);
+                    }
+                } while (true);
+            }
+            finally
+            {
+                ArrayPool<byte>.Shared.Return(buffer, clearArray: true);
+            }
+
+            if (bytesRemaining != 0)
+            {
+                throw new JsonReaderException(SR.Format(SR.DeserializeDataRemaining,
+                    deserializeBufferSize, bytesRemaining), readerState);
+            }
+
+            return (TValue)state.Current.ReturnValue;
+        }
+
+        private static void ReadCore(
+            ref JsonReaderState readerState,
+            bool isFinalBlock,
+            byte[] buffer,
+            int bytesToRead,
+            JsonSerializerOptions options,
+            ref ReadStack state)
+        {
+            Utf8JsonReader reader = new Utf8JsonReader(buffer.AsSpan(0, bytesToRead), isFinalBlock, readerState);
+
+            ReadCore(
+                options,
+                ref reader,
+                ref state);
+
+            readerState = reader.CurrentState;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs
new file mode 100644 (file)
index 0000000..7b7c730
--- /dev/null
@@ -0,0 +1,49 @@
+// 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.
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        public static TValue Parse<TValue>(string json, JsonSerializerOptions options = null)
+        {
+            if (json == null)
+                throw new ArgumentNullException(nameof(json));
+
+            return (TValue)ParseCore(json, typeof(TValue), options);
+        }
+
+        public static object Parse(string json, Type returnType, JsonSerializerOptions options = null)
+        {
+            if (json == null)
+                throw new ArgumentNullException(nameof(json));
+
+            if (returnType == null)
+                throw new ArgumentNullException(nameof(returnType));
+
+            return ParseCore(json, returnType, options);
+        }
+
+        private static object ParseCore(string json, Type returnType, JsonSerializerOptions options = null)
+        {
+            if (options == null)
+                options = s_defaultSettings;
+
+            // todo: use an array pool here for smaller requests to avoid the alloc. Also doc the API that UTF8 is preferred for perf. 
+            byte[] jsonBytes = JsonReaderHelper.s_utf8Encoding.GetBytes(json);
+            var readerState = new JsonReaderState(options: options.ReaderOptions);
+            var reader = new Utf8JsonReader(jsonBytes, isFinalBlock: true, readerState);
+            object result = ReadCore(returnType, options, ref reader);
+
+            readerState = reader.CurrentState;
+            if (readerState.BytesConsumed != jsonBytes.Length)
+            {
+                throw new JsonReaderException(SR.Format(SR.DeserializeDataRemaining,
+                    jsonBytes.Length, jsonBytes.Length - readerState.BytesConsumed), readerState);
+            }
+
+            return result;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs
new file mode 100644 (file)
index 0000000..beed417
--- /dev/null
@@ -0,0 +1,106 @@
+// 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.
+
+using System.Buffers;
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        internal static readonly JsonPropertyInfo s_missingProperty = new JsonPropertyInfo<object, object>();
+        private static readonly JsonSerializerOptions s_defaultSettings = new JsonSerializerOptions();
+
+        private static object ReadCore(
+            Type returnType,
+            JsonSerializerOptions options,
+            ref Utf8JsonReader reader)
+        {
+            if (options == null)
+                options = s_defaultSettings;
+
+            ReadStack state = default;
+            JsonClassInfo classInfo = options.GetOrAddClass(returnType);
+            state.Current.JsonClassInfo = classInfo;
+            if (classInfo.ClassType != ClassType.Object)
+            {
+                state.Current.JsonPropertyInfo = classInfo.GetPolicyProperty();
+            }
+
+            ReadCore(options, ref reader, ref state);
+
+            return state.Current.ReturnValue;
+        }
+
+        // todo: for readability, refactor this method to split by ClassType(Enumerable, Object, or Value) like Write()
+        private static void ReadCore(
+            JsonSerializerOptions options,
+            ref Utf8JsonReader reader,
+            ref ReadStack state)
+        {
+            while (reader.Read())
+            {
+                JsonTokenType tokenType = reader.TokenType;
+
+                if (JsonHelpers.IsInRangeInclusive(tokenType, JsonTokenType.String, JsonTokenType.False))
+                {
+                    Debug.Assert(tokenType == JsonTokenType.String || tokenType == JsonTokenType.Number || tokenType == JsonTokenType.True || tokenType == JsonTokenType.False);
+
+                    if (HandleValue(tokenType, options, ref reader, ref state))
+                    {
+                        return;
+                    }
+                }
+                else if (tokenType == JsonTokenType.PropertyName)
+                {
+                    if (!state.Current.Drain)
+                    {
+                        Debug.Assert(state.Current.ReturnValue != default);
+                        Debug.Assert(state.Current.JsonClassInfo != default);
+
+                        ReadOnlySpan<byte> propertyName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
+                        state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(propertyName, state.Current.PropertyIndex);
+                        if (state.Current.JsonPropertyInfo == null)
+                        {
+                            state.Current.JsonPropertyInfo = s_missingProperty;
+                        }
+
+                        state.Current.PropertyIndex++;
+                    }
+                }
+                else if (tokenType == JsonTokenType.StartObject)
+                {
+                    HandleStartObject(options, ref state);
+                }
+                else if (tokenType == JsonTokenType.EndObject)
+                {
+                    if (HandleEndObject(options, ref state))
+                    {
+                        return;
+                    }
+                }
+                else if (tokenType == JsonTokenType.StartArray)
+                {
+                    HandleStartArray(options, ref reader, ref state);
+                }
+                else if (tokenType == JsonTokenType.EndArray)
+                {
+                    if (HandleEndArray(options, ref state))
+                    {
+                        return;
+                    }
+                }
+                else if (tokenType == JsonTokenType.Null)
+                {
+                    if (HandleNull(ref reader, ref state, options))
+                    {
+                        return;
+                    }
+                }
+            }
+
+            return;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.ByteArray.cs
new file mode 100644 (file)
index 0000000..d3a7456
--- /dev/null
@@ -0,0 +1,22 @@
+// 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.
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        // Name \ feature is pending closure on API review
+        public static byte[] ToBytes<TValue>(TValue value, JsonSerializerOptions options = null)
+        {
+            return WriteCoreBytes(value, typeof(TValue), options);
+        }
+
+        // Name \ feature is pending closure on API review
+        public static byte[] ToBytes(object value, Type type, JsonSerializerOptions options = null)
+        {
+            VerifyValueAndType(value, type);
+            return WriteCoreBytes(value, type, options);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs
new file mode 100644 (file)
index 0000000..6e8d6f4
--- /dev/null
@@ -0,0 +1,81 @@
+// 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.
+
+using System.Collections;
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        private static bool WriteEnumerable(
+            JsonSerializerOptions options,
+            ref Utf8JsonWriter writer,
+            ref WriteStack state)
+        {
+            return HandleEnumerable(state.Current.JsonClassInfo.ElementClassInfo, options, ref writer, ref state);
+        }
+
+        private static bool HandleEnumerable(
+            JsonClassInfo elementClassInfo,
+            JsonSerializerOptions options,
+            ref Utf8JsonWriter writer,
+            ref WriteStack state)
+        {
+            Debug.Assert(state.Current.JsonPropertyInfo.ClassType == ClassType.Enumerable);
+
+            JsonPropertyInfo propertyInfo = state.Current.JsonPropertyInfo;
+
+            if (state.Current.Enumerator == null)
+            {
+                if (propertyInfo._name == null)
+                {
+                    writer.WriteStartArray();
+                }
+                else
+                {
+                    writer.WriteStartArray(propertyInfo._name);
+                }
+
+                IEnumerable enumerable = (IEnumerable)propertyInfo.GetValueAsObject(state.Current.CurrentValue, options);
+
+                if (enumerable != null)
+                {
+                    state.Current.Enumerator = enumerable.GetEnumerator();
+                }
+            }
+
+            if (state.Current.Enumerator != null && state.Current.Enumerator.MoveNext())
+            {
+                if (elementClassInfo.ClassType == ClassType.Value)
+                {
+                    elementClassInfo.GetPolicyProperty().WriteEnumerable(options, ref state.Current, ref writer);
+                }
+                else
+                {
+                    // An object or another enumerator requires a new stack frame
+                    JsonClassInfo nextClassInfo = propertyInfo.ElementClassInfo;
+                    object nextValue = state.Current.Enumerator.Current;
+                    state.Push(nextClassInfo, nextValue);
+                }
+
+                return false;
+            }
+
+            // We are done enumerating.
+            writer.WriteEndArray();
+
+            if (state.Current.PopStackOnEndArray)
+            {
+                state.Pop();
+            }
+            else
+            {
+                state.Current.EndArray();
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs
new file mode 100644 (file)
index 0000000..4ef89af
--- /dev/null
@@ -0,0 +1,105 @@
+// 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.
+
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        private static bool WriteObject(
+            JsonSerializerOptions options,
+            ref Utf8JsonWriter writer,
+            ref WriteStack state)
+        {
+            JsonClassInfo classInfo = state.Current.JsonClassInfo;
+
+            // Write the start.
+            if (!state.Current.StartObjectWritten)
+            {
+                if (state.Current.JsonPropertyInfo?._escapedName == null)
+                {
+                    writer.WriteStartObject();
+                }
+                else
+                {
+                    writer.WriteStartObject(state.Current.JsonPropertyInfo._escapedName);
+                }
+                state.Current.StartObjectWritten = true;
+            }
+
+            // Determine if we are done enumerating properties.
+            if (state.Current.PropertyIndex != classInfo.PropertyCount)
+            {
+                HandleObject(options, ref writer, ref state);
+                return false;
+            }
+
+            writer.WriteEndObject();
+
+            if (state.Current.PopStackOnEndObject)
+            {
+                state.Pop();
+            }
+            else
+            {
+                state.Current.EndObject();
+            }
+
+            return true;
+        }
+
+        private static bool HandleObject(
+                JsonSerializerOptions options,
+                ref Utf8JsonWriter writer,
+                ref WriteStack state)
+        {
+            Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Object);
+
+            JsonPropertyInfo propertyInfo = state.Current.JsonClassInfo.GetProperty(state.Current.PropertyIndex);
+            state.Current.JsonPropertyInfo = propertyInfo;
+
+            ClassType propertyClassType = propertyInfo.ClassType;
+            if (propertyClassType == ClassType.Value)
+            {
+                propertyInfo.Write(options, ref state.Current, ref writer);
+                state.Current.NextProperty();
+                return true;
+            }
+
+            // A property that returns an enumerator keeps the same stack frame.
+            if (propertyClassType == ClassType.Enumerable)
+            {
+                bool endOfEnumerable = HandleEnumerable(propertyInfo.ElementClassInfo, options, ref writer, ref state);
+                if (endOfEnumerable)
+                {
+                    state.Current.NextProperty();
+                }
+
+                return endOfEnumerable;
+            }
+
+            // A property that returns an object requires a new stack frame.
+            object value = propertyInfo.GetValueAsObject(state.Current.CurrentValue, options);
+            if (value != null)
+            {
+                JsonPropertyInfo previousPropertyInfo = state.Current.JsonPropertyInfo;
+
+                state.Current.NextProperty();
+
+                JsonClassInfo nextClassInfo = options.GetOrAddClass(propertyInfo.PropertyType);
+                state.Push(nextClassInfo, value);
+
+                // Set the PropertyInfo so we can obtain the property name in order to write it.
+                state.Current.JsonPropertyInfo = previousPropertyInfo;
+            }
+            else if (!propertyInfo.IgnoreNullPropertyValueOnWrite(options))
+            {
+                writer.WriteNull(propertyInfo._escapedName);
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleValue.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleValue.cs
new file mode 100644 (file)
index 0000000..5024f26
--- /dev/null
@@ -0,0 +1,24 @@
+// 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.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static bool WriteValue(
+            JsonSerializerOptions options,
+            ref Utf8JsonWriter writer,
+            ref WriteStackFrame current)
+        {
+            Debug.Assert(current.JsonPropertyInfo.ClassType == ClassType.Value);
+
+            current.JsonPropertyInfo.Write(options, ref current, ref writer);
+            return true;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs
new file mode 100644 (file)
index 0000000..0e3d975
--- /dev/null
@@ -0,0 +1,101 @@
+// 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.
+
+using System.Buffers;
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        private static void VerifyValueAndType(object value, Type type)
+        {
+            if (type == null)
+            {
+                if (value != null)
+                {
+                    throw new ArgumentNullException(nameof(type));
+                }
+            }
+            else if (value != null)
+            {
+                if (!type.IsAssignableFrom(value.GetType()))
+                {
+                    ThrowHelper.ThrowArgumentException_DeserializeWrongType(type, value);
+                }
+            }
+        }
+
+        private static void WriteNull(
+            ref JsonWriterState writerState,
+            IBufferWriter<byte> bufferWriter)
+        {
+            Utf8JsonWriter writer = new Utf8JsonWriter(bufferWriter, writerState);
+            writer.WriteNullValue();
+            writer.Flush(true);
+        }
+
+        private static byte[] WriteCoreBytes(object value, Type type, JsonSerializerOptions options)
+        {
+            if (options == null)
+                options = s_defaultSettings;
+
+            byte[] result;
+
+            using (var output = new ArrayBufferWriter<byte>(options.EffectiveBufferSize))
+            {
+                WriteCore(output, value, type, options);
+                result = output.WrittenMemory.ToArray();
+            }
+
+            return result;
+        }
+
+        private static string WriteCoreString(object value, Type type, JsonSerializerOptions options)
+        {
+            if (options == null)
+                options = s_defaultSettings;
+
+            string result;
+
+            using (var output = new ArrayBufferWriter<byte>(options.EffectiveBufferSize))
+            {
+                WriteCore(output, value, type, options);
+                result = JsonReaderHelper.TranscodeHelper(output.WrittenMemory.Span);
+            }
+
+            return result;
+        }
+
+        private static void WriteCore(ArrayBufferWriter<byte> output, object value, Type type, JsonSerializerOptions options)
+        {
+            var writerState = new JsonWriterState(options.WriterOptions);
+            var writer = new Utf8JsonWriter(output, writerState);
+
+            if (value == null)
+            {
+                writer.WriteNullValue();
+            }
+            else
+            {
+                if (type == null)
+                {
+                    type = value.GetType();
+                }
+
+                WriteStack state = default;
+                JsonClassInfo classInfo = options.GetOrAddClass(type);
+                state.Current.JsonClassInfo = classInfo;
+                state.Current.CurrentValue = value;
+                if (classInfo.ClassType != ClassType.Object)
+                {
+                    state.Current.JsonPropertyInfo = classInfo.GetPolicyProperty();
+                }
+
+                Write(ref writer, -1, options, ref state);
+            }
+
+            writer.Flush(isFinalBlock: true);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs
new file mode 100644 (file)
index 0000000..5a652c3
--- /dev/null
@@ -0,0 +1,85 @@
+// 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.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        public static Task WriteAsync<TValue>(TValue value, Stream utf8Json, JsonSerializerOptions options = null, CancellationToken cancellationToken = default)
+        {
+            return WriteAsyncCore(value, typeof(TValue), utf8Json, options, cancellationToken);
+        }
+
+        public static Task WriteAsync(object value, Type type, Stream utf8Json, JsonSerializerOptions options = null, CancellationToken cancellationToken = default)
+        {
+            if (utf8Json == null)
+                throw new ArgumentNullException(nameof(utf8Json));
+
+            VerifyValueAndType(value, type);
+
+            return WriteAsyncCore(value, type, utf8Json, options, cancellationToken);
+        }
+
+        private static async Task WriteAsyncCore(object value, Type type, Stream utf8Json, JsonSerializerOptions options, CancellationToken cancellationToken)
+        {
+            if (options == null)
+                options = s_defaultSettings;
+
+            var writerState = new JsonWriterState(options.WriterOptions);
+
+            using (var bufferWriter = new ArrayBufferWriter<byte>(options.EffectiveBufferSize))
+            {
+                if (value == null)
+                {
+                    WriteNull(ref writerState, bufferWriter);
+#if BUILDING_INBOX_LIBRARY
+                    await utf8Json.WriteAsync(bufferWriter.WrittenMemory, cancellationToken).ConfigureAwait(false);
+#else
+                    // todo: stackalloc or pool here?
+                    await utf8Json.WriteAsync(bufferWriter.WrittenMemory.ToArray(), 0, bufferWriter.WrittenMemory.Length, cancellationToken).ConfigureAwait(false);
+#endif
+                    return;
+                }
+
+                if (type == null)
+                {
+                    type = value.GetType();
+                }
+
+                JsonClassInfo classInfo = options.GetOrAddClass(type);
+                WriteStack state = default;
+                state.Current.JsonClassInfo = classInfo;
+                state.Current.CurrentValue = value;
+                if (classInfo.ClassType != ClassType.Object)
+                {
+                    state.Current.JsonPropertyInfo = classInfo.GetPolicyProperty();
+                }
+
+                bool isFinalBlock;
+
+                int flushThreshold;
+                do
+                {
+                    flushThreshold = (int)(bufferWriter.Capacity * .9); //todo: determine best value here
+
+                    isFinalBlock = Write(ref writerState, bufferWriter, flushThreshold, options, ref state);
+#if BUILDING_INBOX_LIBRARY
+                    await utf8Json.WriteAsync(bufferWriter.WrittenMemory, cancellationToken).ConfigureAwait(false);
+#else
+                    // todo: use pool here to avod extra alloc?
+                    await utf8Json.WriteAsync(bufferWriter.WrittenMemory.ToArray(), 0, bufferWriter.WrittenMemory.Length, cancellationToken).ConfigureAwait(false);
+#endif
+                    bufferWriter.Clear();
+                } while (!isFinalBlock);
+            }
+            
+            // todo: verify that we do want to call FlushAsync here (or above). It seems like leaving it to the caller would be best.
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.String.cs
new file mode 100644 (file)
index 0000000..9dc1b1a
--- /dev/null
@@ -0,0 +1,26 @@
+// 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.
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        public static string ToString<TValue>(TValue value, JsonSerializerOptions options = null)
+        {
+            return ToStringInternal(value, typeof(TValue), options);
+        }
+
+        public static string ToString(object value, Type type, JsonSerializerOptions options = null)
+        {
+            VerifyValueAndType(value, type);
+
+            return ToStringInternal(value, type, options);
+        }
+
+        private static string ToStringInternal(object value, Type type, JsonSerializerOptions options)
+        {
+            return WriteCoreString(value, type, options);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs
new file mode 100644 (file)
index 0000000..d830840
--- /dev/null
@@ -0,0 +1,69 @@
+// 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.
+
+using System.Buffers;
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        private static bool Write(
+            ref JsonWriterState writerState,
+            IBufferWriter<byte> bufferWriter,
+            int flushThreshold,
+            JsonSerializerOptions options,
+            ref WriteStack state)
+        {
+            Utf8JsonWriter writer = new Utf8JsonWriter(bufferWriter, writerState);
+
+            bool isFinalBlock = Write(
+                ref writer,
+                flushThreshold,
+                options,
+                ref state);
+
+            writer.Flush(isFinalBlock: isFinalBlock);
+            writerState = writer.GetCurrentState();
+
+            return isFinalBlock;
+        }
+
+        private static bool Write(
+            ref Utf8JsonWriter writer,
+            int flushThreshold,
+            JsonSerializerOptions options,
+            ref WriteStack state)
+        {
+            bool continueWriting = true;
+            bool finishedSerializing;
+            do
+            {
+                switch (state.Current.JsonClassInfo.ClassType)
+                {
+                    case ClassType.Enumerable:
+                        finishedSerializing = WriteEnumerable(options, ref writer, ref state);
+                        break;
+                    case ClassType.Object:
+                        finishedSerializing = WriteObject(options, ref writer, ref state);
+                        break;
+                    default:
+                        finishedSerializing = WriteValue(options, ref writer, ref state.Current);
+                        break;
+                }
+
+                if (flushThreshold >= 0 && writer.BytesWritten > flushThreshold)
+                {
+                    return false;
+                }
+
+                if (finishedSerializing && writer.CurrentDepth == 0)
+                {
+                    continueWriting = false;
+                }
+            } while (continueWriting);
+
+            return true;
+        }        
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
new file mode 100644 (file)
index 0000000..df52795
--- /dev/null
@@ -0,0 +1,86 @@
+// 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.
+
+using System.Collections.Concurrent;
+
+namespace System.Text.Json.Serialization
+{
+    public sealed class JsonSerializerOptions
+    {
+        internal const int BufferSizeUnspecified = -1;
+        internal const int BufferSizeDefault = 16 * 1024;
+
+        private ClassMaterializer _classMaterializerStrategy;
+        private int _defaultBufferSize = BufferSizeUnspecified;
+
+        private static readonly ConcurrentDictionary<Type, JsonClassInfo> s_classes = new ConcurrentDictionary<Type, JsonClassInfo>();
+
+        public JsonSerializerOptions() { }
+
+        internal JsonClassInfo GetOrAddClass(Type classType)
+        {
+            JsonClassInfo result;
+
+            if (!s_classes.TryGetValue(classType, out result))
+            {
+                result = s_classes.GetOrAdd(classType, new JsonClassInfo(classType, this));
+            }
+
+            return result;
+        }
+
+        public JsonReaderOptions ReaderOptions { get; set; }
+        public JsonWriterOptions WriterOptions { get; set; }
+
+        public int DefaultBufferSize
+        {
+            get
+            {
+                return _defaultBufferSize;
+            }
+            set
+            {
+                if (value == 0 || value < BufferSizeUnspecified)
+                {
+                    throw new ArgumentException(SR.SerializationInvalidBufferSize);
+                }
+
+                _defaultBufferSize = value;
+
+                if (_defaultBufferSize == BufferSizeUnspecified)
+                {
+                    EffectiveBufferSize = BufferSizeDefault;
+                }
+                else
+                {
+                    EffectiveBufferSize = _defaultBufferSize;
+                }
+            }
+        }
+
+        public bool IgnoreNullPropertyValueOnWrite { get; set; }
+        public bool IgnoreNullPropertyValueOnRead { get; set; }
+
+        // Used internally for performance to avoid checking BufferSizeUnspecified.
+        internal int EffectiveBufferSize { get; private set; } = BufferSizeDefault;
+
+        internal ClassMaterializer ClassMaterializerStrategy
+        {
+            get
+            {
+                if (_classMaterializerStrategy == null)
+                {
+#if BUILDING_INBOX_LIBRARY
+                    _classMaterializerStrategy = new ReflectionEmitMaterializer();
+#else
+                    // todo: should we attempt to detect here, or at least have a #define like #SUPPORTS_IL_EMIT
+                    _classMaterializerStrategy = new ReflectionMaterializer();
+#endif
+                }
+
+                return _classMaterializerStrategy;
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Policies/JsonValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Policies/JsonValueConverter.cs
new file mode 100644 (file)
index 0000000..35d9586
--- /dev/null
@@ -0,0 +1,13 @@
+// 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.
+
+namespace System.Text.Json.Serialization.Policies
+{
+    internal abstract class JsonValueConverter<TValue>
+    {
+        public abstract bool TryRead(Type valueType, ref Utf8JsonReader reader, out TValue value);
+        public abstract void Write(TValue value, ref Utf8JsonWriter writer);
+        public abstract void Write(Span<byte> escapedPropertyName, TValue value, ref Utf8JsonWriter writer);
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/PropertyRef.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/PropertyRef.cs
new file mode 100644 (file)
index 0000000..98d9d8f
--- /dev/null
@@ -0,0 +1,18 @@
+// 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.
+
+namespace System.Text.Json.Serialization
+{
+    internal readonly struct PropertyRef
+    {
+        public PropertyRef(ulong key, JsonPropertyInfo info)
+        {
+            Key = key;
+            Info = info;
+        }
+
+        public readonly ulong Key;
+        public readonly JsonPropertyInfo Info;
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs
new file mode 100644 (file)
index 0000000..aee1c55
--- /dev/null
@@ -0,0 +1,87 @@
+// 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.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization
+{
+    internal struct ReadStack
+    {
+        // A fields is used instead of a property to avoid value semantics.
+        public ReadStackFrame Current;
+
+        private List<ReadStackFrame> _previous;
+        public int _index;
+
+        public void Push()
+        {
+            if (_previous == null)
+            {
+                _previous = new List<ReadStackFrame>();
+            }
+
+            if (_index == _previous.Count)
+            {
+                // Need to allocate a new array element.
+                _previous.Add(Current);
+            }
+            else
+            {
+                Debug.Assert(_index < _previous.Count);
+
+                // Use a previously allocated slot.
+                Current = _previous[_index];
+            }
+
+            Current.Reset();
+            _index++;
+        }
+
+        public void Pop()
+        {
+            Debug.Assert(_index > 0);
+            Current = _previous[--_index];
+        }
+
+        public bool IsLastFrame => _index == 0;
+
+        // Return a property path in the form of: [FullNameOfType].FirstProperty.SecondProperty.LastProperty
+        public string PropertyPath
+        {
+            get
+            {
+                StringBuilder path = new StringBuilder();
+
+                if (_previous == null || _index == 0)
+                {
+                    path.Append($"[{Current.JsonClassInfo.Type.FullName}]");
+                }
+                else
+                {
+                    path.Append($"[{_previous[0].JsonClassInfo.Type.FullName}]");
+
+                    for (int i = 0; i < _index; i++)
+                    {
+                        path.Append(GetPropertyName(_previous[i]));
+                    }
+                }
+
+                path.Append(GetPropertyName(Current));
+
+                return path.ToString();
+            }
+        }
+
+        private string GetPropertyName(in ReadStackFrame frame)
+        {
+            if (frame.JsonPropertyInfo != null && frame.JsonClassInfo.ClassType == ClassType.Object)
+            {
+                return $".{frame.JsonPropertyInfo.PropertyInfo.Name}";
+            }
+
+            return string.Empty;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs
new file mode 100644 (file)
index 0000000..55922de
--- /dev/null
@@ -0,0 +1,158 @@
+// 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.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization
+{
+    internal struct ReadStackFrame
+    {
+        // The object (POCO or IEnumerable) that is being populated
+        internal object ReturnValue;
+        internal JsonClassInfo JsonClassInfo;
+
+        // Current property values
+        internal JsonPropertyInfo JsonPropertyInfo;
+        internal bool PopStackOnEndArray;
+        internal bool EnumerableCreated;
+
+        // Support System.Array and other types that don't implement IList
+        internal List<object> TempEnumerableValues;
+
+        // For performance, we order the properties by the first usage and this index helps find the right slot quicker.
+        internal int PropertyIndex;
+        internal bool Drain;
+
+        internal void Reset()
+        {
+            ReturnValue = null;
+            JsonClassInfo = null;
+            PropertyIndex = 0;
+            Drain = false;
+            ResetProperty();
+        }
+
+        internal void ResetProperty()
+        {
+            JsonPropertyInfo = null;
+            PopStackOnEndArray = false;
+            EnumerableCreated = false;
+            TempEnumerableValues = null;
+        }
+
+        internal bool IsEnumerable()
+        {
+            return JsonClassInfo.ClassType == ClassType.Enumerable;
+        }
+
+        internal bool Skip()
+        {
+            return Drain || ReferenceEquals(JsonPropertyInfo, JsonSerializer.s_missingProperty);
+        }
+
+        internal bool IsPropertyEnumerable()
+        {
+            if (JsonPropertyInfo != null)
+            {
+                return JsonPropertyInfo.ClassType == ClassType.Enumerable;
+            }
+
+            return false;
+        }
+
+        public Type GetElementType()
+        {
+            if (IsPropertyEnumerable())
+            {
+                return JsonPropertyInfo.ElementClassInfo.Type;
+            }
+
+            if (IsEnumerable())
+            {
+                return JsonClassInfo.ElementClassInfo.Type;
+            }
+
+            return JsonPropertyInfo.PropertyType;
+        }
+
+        internal static object CreateEnumerableValue(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options)
+        {
+            // If the property has an EnumerableConverter, then we use tempEnumerableValues.
+            if (state.Current.JsonPropertyInfo.EnumerableConverter != null)
+            {
+                state.Current.TempEnumerableValues = new List<object>();
+                return null;
+            }
+
+            Type propType = state.Current.JsonPropertyInfo.PropertyType;
+            if (typeof(IList).IsAssignableFrom(propType))
+            {
+                // If IList, add the members as we create them.
+                JsonClassInfo collectionClassInfo = options.GetOrAddClass(propType);
+                IList collection = (IList)collectionClassInfo.CreateObject();
+                return collection;
+            }
+            else
+            {
+                ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(propType, reader, state);
+                return null;
+            }
+        }
+
+        internal static IEnumerable GetEnumerableValue(in ReadStackFrame current)
+        {
+            if (current.IsEnumerable())
+            {
+                if (current.ReturnValue != null)
+                {
+                    return (IEnumerable)current.ReturnValue;
+                }
+            }
+
+            // IEnumerable properties are finished (values added inline) unless they are using tempEnumerableValues.
+            return current.TempEnumerableValues;
+        }
+
+        internal void SetReturnValue(object value, JsonSerializerOptions options)
+        {
+            Debug.Assert(ReturnValue == null);
+            ReturnValue = value;
+        }
+
+        internal static void SetReturnValue(object value, JsonSerializerOptions options, ref ReadStackFrame current, bool setPropertyDirectly = false)
+        {
+            if (current.IsEnumerable())
+            {
+                if (current.TempEnumerableValues != null)
+                {
+                    current.TempEnumerableValues.Add(value);
+                }
+                else
+                {
+                    ((IList)current.ReturnValue).Add(value);
+                }
+            }
+            else if (!setPropertyDirectly && current.IsPropertyEnumerable())
+            {
+                Debug.Assert(current.JsonPropertyInfo != null);
+                Debug.Assert(current.ReturnValue != null);
+                if (current.TempEnumerableValues != null)
+                {
+                    current.TempEnumerableValues.Add(value);
+                }
+                else
+                {
+                    ((IList)current.JsonPropertyInfo.GetValueAsObject(current.ReturnValue, options)).Add(value);
+                }
+            }
+            else
+            {
+                Debug.Assert(current.JsonPropertyInfo != null);
+                current.JsonPropertyInfo.SetValueAsObject(current.ReturnValue, value, options);
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMaterializer.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMaterializer.cs
new file mode 100644 (file)
index 0000000..d6e0efe
--- /dev/null
@@ -0,0 +1,39 @@
+// 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.
+
+#if BUILDING_INBOX_LIBRARY
+using System.Diagnostics;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace System.Text.Json.Serialization
+{
+    internal sealed class ReflectionEmitMaterializer : ClassMaterializer
+    {
+        public override JsonClassInfo.ConstructorDelegate CreateConstructor(Type type)
+        {
+            Debug.Assert(type != null);
+
+            ConstructorInfo realMethod = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, binder: null, Type.EmptyTypes, modifiers: null);
+            if (realMethod == null)
+            {
+                return null;
+            }
+
+            var dynamicMethod = new DynamicMethod(
+                realMethod.Name,
+                type,
+                Type.EmptyTypes,
+                typeof(ReflectionEmitMaterializer).Module,
+                skipVisibility: true);
+
+            ILGenerator generator = dynamicMethod.GetILGenerator();
+            generator.Emit(OpCodes.Newobj, realMethod);
+            generator.Emit(OpCodes.Ret);
+
+            return (JsonClassInfo.ConstructorDelegate)dynamicMethod.CreateDelegate(typeof(JsonClassInfo.ConstructorDelegate));
+        }
+    }
+}
+#endif
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMaterializer.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMaterializer.cs
new file mode 100644 (file)
index 0000000..284affc
--- /dev/null
@@ -0,0 +1,14 @@
+// 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.
+
+namespace System.Text.Json.Serialization
+{
+    internal sealed class ReflectionMaterializer : ClassMaterializer
+    {
+        public override JsonClassInfo.ConstructorDelegate CreateConstructor(Type type)
+        {
+            return () => Activator.CreateInstance(type);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs
new file mode 100644 (file)
index 0000000..9dfa63a
--- /dev/null
@@ -0,0 +1,65 @@
+// 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.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization
+{
+    internal struct WriteStack
+    {
+        // Fields are used instead of properties to avoid value semantics.
+        public WriteStackFrame Current;
+        private List<WriteStackFrame> _previous;
+        private int _index;
+
+        public void Push()
+        {
+            if (_previous == null)
+            {
+                _previous = new List<WriteStackFrame>();
+            }
+
+            if (_index == _previous.Count)
+            {
+                // Need to allocate a new array element.
+                _previous.Add(Current);
+            }
+            else
+            {
+                Debug.Assert(_index < _previous.Count);
+
+                // Use a previously allocated slot.
+                Current = _previous[_index];
+            }
+
+            Current.Reset();
+            _index++;
+        }
+
+        public void Push(JsonClassInfo nextClassInfo, object nextValue)
+        {
+            Push();
+            Current.JsonClassInfo = nextClassInfo;
+            Current.CurrentValue = nextValue;
+
+            if (nextClassInfo.ClassType == ClassType.Enumerable)
+            {
+                Current.PopStackOnEndArray = true;
+                Current.JsonPropertyInfo = Current.JsonClassInfo.GetPolicyProperty();
+            }
+            else
+            {
+                Debug.Assert(nextClassInfo.ClassType == ClassType.Object);
+                Current.PopStackOnEndObject = true;
+            }
+        }
+
+        public void Pop()
+        {
+            Debug.Assert(_index > 0);
+            Current = _previous[--_index];
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs
new file mode 100644 (file)
index 0000000..10f780a
--- /dev/null
@@ -0,0 +1,63 @@
+// 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.
+
+using System.Collections;
+
+namespace System.Text.Json.Serialization
+{
+    internal struct WriteStackFrame
+    {
+        // The object (POCO or IEnumerable) that is being populated
+        internal object CurrentValue;
+        internal JsonClassInfo JsonClassInfo;
+
+        internal IEnumerator Enumerator;
+
+        // Current property values
+        internal JsonPropertyInfo JsonPropertyInfo;
+
+        // The current property.
+        internal int PropertyIndex;
+
+        // Has the Start tag been written
+        internal bool StartObjectWritten;
+
+        internal bool PopStackOnEndArray;
+        internal bool PopStackOnEndObject;
+
+        internal void Reset()
+        {
+            CurrentValue = null;
+            JsonClassInfo = null;
+            StartObjectWritten = false;
+            EndObject();
+            EndArray();
+        }
+
+        internal void EndObject()
+        {
+            PropertyIndex = 0;
+            PopStackOnEndObject = false;
+            EndProperty();
+        }
+
+        internal void EndArray()
+        {
+            Enumerator = null;
+            PopStackOnEndArray = false;
+            EndProperty();
+        }
+
+        internal void EndProperty()
+        {
+            JsonPropertyInfo = null;
+        }
+
+        internal void NextProperty()
+        {
+            JsonPropertyInfo = null;
+            PropertyIndex++;
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
new file mode 100644 (file)
index 0000000..47b21f4
--- /dev/null
@@ -0,0 +1,36 @@
+// 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.
+
+using System.Runtime.CompilerServices;
+using System.Text.Json.Serialization;
+
+namespace System.Text.Json
+{
+    internal static partial class ThrowHelper
+    {
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static void ThrowArgumentException_DeserializeWrongType(Type type, object value)
+        {
+            throw new ArgumentException(SR.Format(SR.DeserializeWrongType, type.FullName, value.GetType().FullName));
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static void ThrowJsonReaderException_DeserializeUnableToConvertValue(Type propertyType, in Utf8JsonReader reader, in ReadStack state)
+        {
+            throw new JsonReaderException(SR.Format(SR.DeserializeUnableToConvertValue, state.PropertyPath, propertyType), reader.CurrentState);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static void ThrowJsonReaderException_DeserializeCannotBeNull(in Utf8JsonReader reader, in ReadStack state)
+        {
+            throw new JsonReaderException(SR.Format(SR.DeserializeCannotBeNull, state.PropertyPath), reader.CurrentState);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static void ThrowObjectDisposedException(string name)
+        {
+            throw new ObjectDisposedException(name);
+        }
+    }
+}
index 2989fbc..74fdf90 100644 (file)
@@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
 
 namespace System.Text.Json
 {
-    internal static class ThrowHelper
+    internal static partial class ThrowHelper
     {
         public static ArgumentException GetArgumentException_MaxDepthMustBePositive()
         {
diff --git a/src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs
new file mode 100644 (file)
index 0000000..d4db11d
--- /dev/null
@@ -0,0 +1,39 @@
+// 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.
+
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class ArrayTests
+    {
+        [Fact]
+        public static void ReadClassWithStringArray()
+        {
+            TestClassWithStringArray obj = JsonSerializer.Parse<TestClassWithStringArray>(TestClassWithStringArray.s_data);
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void ReadClassWithObjectList()
+        {
+            TestClassWithObjectList obj = JsonSerializer.Parse<TestClassWithObjectList>(TestClassWithObjectList.s_data);
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void ReadClassWithObjectArray()
+        {
+            TestClassWithObjectArray obj = JsonSerializer.Parse<TestClassWithObjectArray>(TestClassWithObjectArray.s_data);
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void ReadClassWithGenericList()
+        {
+            TestClassWithGenericList obj = JsonSerializer.Parse<TestClassWithGenericList>(TestClassWithGenericList.s_data);
+            obj.Verify();
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/Array.WriteTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Array.WriteTests.cs
new file mode 100644 (file)
index 0000000..5c0197f
--- /dev/null
@@ -0,0 +1,80 @@
+// 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.
+
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class ArrayTests
+    {
+        [Fact]
+        public static void WriteClassWithStringArray()
+        {
+            string json;
+
+            {
+                TestClassWithStringArray obj = new TestClassWithStringArray();
+                obj.Initialize();
+                obj.Verify();
+                json = JsonSerializer.ToString(obj);
+            }
+
+            {
+                TestClassWithStringArray obj = JsonSerializer.Parse<TestClassWithStringArray>(json);
+                obj.Verify();
+            }
+
+            {
+                TestClassWithStringArray obj = JsonSerializer.Parse<TestClassWithStringArray>(TestClassWithStringArray.s_data);
+                obj.Verify();
+            }
+        }
+
+        [Fact]
+        public static void WriteClassWithObjectArray()
+        {
+            string json;
+
+            {
+                TestClassWithGenericList obj = new TestClassWithGenericList();
+                obj.Initialize();
+                obj.Verify();
+                json = JsonSerializer.ToString(obj);
+            }
+
+            {
+                TestClassWithGenericList obj = JsonSerializer.Parse<TestClassWithGenericList>(json);
+                obj.Verify();
+            }
+
+            {
+                TestClassWithGenericList obj = JsonSerializer.Parse<TestClassWithGenericList>(TestClassWithGenericList.s_data);
+                obj.Verify();
+            }
+        }
+
+        [Fact]
+        public static void WriteClassWithGenericList()
+        {
+            string json;
+
+            {
+                TestClassWithObjectList obj = new TestClassWithObjectList();
+                obj.Initialize();
+                obj.Verify();
+                json = JsonSerializer.ToString(obj);
+            }
+
+            {
+                TestClassWithObjectList obj = JsonSerializer.Parse<TestClassWithObjectList>(json);
+                obj.Verify();
+            }
+
+            {
+                TestClassWithObjectList obj = JsonSerializer.Parse<TestClassWithObjectList>(TestClassWithObjectList.s_data);
+                obj.Verify();
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/CyclicTests.cs b/src/libraries/System.Text.Json/tests/Serialization/CyclicTests.cs
new file mode 100644 (file)
index 0000000..58eb152
--- /dev/null
@@ -0,0 +1,21 @@
+// 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.
+
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static class CyclicTests
+    {
+        [Fact]
+        public static void WriteCyclicFail()
+        {
+            TestClassWithCycle obj = new TestClassWithCycle();
+            obj.Initialize();
+
+            // We don't allow cycles; we throw InvalidOperation instead of an unrecoverable StackOverflow.
+            Assert.Throws<InvalidOperationException>(() => JsonSerializer.ToString(obj));
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/EnumTests.cs b/src/libraries/System.Text.Json/tests/Serialization/EnumTests.cs
new file mode 100644 (file)
index 0000000..d8b6ea0
--- /dev/null
@@ -0,0 +1,34 @@
+// 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.
+
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static class EnumTests
+    {
+        private static readonly string s_jsonStringEnum =
+                @"{" +
+                @"""MyEnum"" : ""Two""" +
+                @"}";
+
+        private static readonly string s_jsonIntEnum =
+                @"{" +
+                @"""MyEnum"" : 2" +
+                @"}";
+
+        [Fact]
+        public static void EnumAsStringFail()
+        {
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<SimpleTestClass>(s_jsonStringEnum));
+        }
+
+        [Fact]
+        public static void EnumAsInt()
+        {
+            SimpleTestClass obj = JsonSerializer.Parse<SimpleTestClass>(s_jsonIntEnum);
+            Assert.Equal(SampleEnum.Two, obj.MyEnum);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/Null.ReadTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Null.ReadTests.cs
new file mode 100644 (file)
index 0000000..99a594a
--- /dev/null
@@ -0,0 +1,37 @@
+// 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.
+
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class NullTests
+    {
+        [Fact]
+        public static void ClassWithNull()
+        {
+            TestClassWithNull obj = JsonSerializer.Parse<TestClassWithNull>(TestClassWithNull.s_json);
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void DefaultReadValue()
+        {
+            TestClassWithNullButInitialized obj = JsonSerializer.Parse<TestClassWithNullButInitialized>(TestClassWithNullButInitialized.s_json);
+            Assert.Equal(null, obj.MyString);
+            Assert.Equal(null, obj.MyInt);
+        }
+
+        [Fact]
+        public static void OverrideReadOnOption()
+        {
+            var options = new JsonSerializerOptions();
+            options.IgnoreNullPropertyValueOnRead = true;
+
+            TestClassWithNullButInitialized obj = JsonSerializer.Parse<TestClassWithNullButInitialized>(TestClassWithNullButInitialized.s_json, options);
+            Assert.Equal("Hello", obj.MyString);
+            Assert.Equal(1, obj.MyInt);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/Null.WriteTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Null.WriteTests.cs
new file mode 100644 (file)
index 0000000..2d350fd
--- /dev/null
@@ -0,0 +1,30 @@
+// 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.
+
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class NullTests
+    {
+        [Fact]
+        public static void DefaultWriteOptions()
+        {
+            var input = new TestClassWithNull();
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal(@"{""MyString"":null}", json);
+        }
+
+        [Fact]
+        public static void OverrideWriteOnOption()
+        {
+            JsonSerializerOptions options = new JsonSerializerOptions();
+            options.IgnoreNullPropertyValueOnWrite = true;
+
+            var input = new TestClassWithNull();
+            string json = JsonSerializer.ToString(input, options);
+            Assert.Equal(@"{}", json);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs
new file mode 100644 (file)
index 0000000..e5d166a
--- /dev/null
@@ -0,0 +1,84 @@
+// 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.
+
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static class PolymorphicTests
+    {
+        [Fact]
+        public static void Standard()
+        {
+            Customer customer = new Customer();
+            customer.Initialize();
+            customer.Verify();
+
+            string json = JsonSerializer.ToString(customer);
+            Customer deserializedCustomer = JsonSerializer.Parse<Customer>(json);
+            deserializedCustomer.Verify();
+        }
+
+        [Fact]
+        public static void StaticAnalysis()
+        {
+            Customer customer = new Customer();
+            customer.Initialize();
+            customer.Verify();
+
+            Person person = customer;
+
+            // Generic inference used <TValue> = <Person>
+            string json = JsonSerializer.ToString(person);
+
+            Customer deserializedCustomer = JsonSerializer.Parse<Customer>(json);
+
+            // We only serialized the Person base class, so the Customer fields should be default.
+            Assert.Equal(typeof(Customer), deserializedCustomer.GetType());
+            Assert.Equal(0, deserializedCustomer.CreditLimit);
+            ((Person)deserializedCustomer).VerifyNonVirtual();
+        }
+
+        [Fact]
+        public static void WriteStringWithRuntimeType()
+        {
+            Customer customer = new Customer();
+            customer.Initialize();
+            customer.Verify();
+
+            Person person = customer;
+
+            string json = JsonSerializer.ToString(person, person.GetType());
+
+            Customer deserializedCustomer = JsonSerializer.Parse<Customer>(json);
+
+            // We serialized the Customer
+            Assert.Equal(typeof(Customer), deserializedCustomer.GetType());
+            deserializedCustomer.Verify();
+        }
+
+        [Fact]
+        public static void StaticAnalysisWithRelationship()
+        {
+            UsaCustomer usaCustomer = new UsaCustomer();
+            usaCustomer.Initialize();
+            usaCustomer.Verify();
+
+            // Note: this could be typeof(UsaAddress) if we preserve objects created in the ctor. Currently we only preserve IEnumerables.
+            Assert.Equal(typeof(Address), usaCustomer.Address.GetType());
+
+            Customer customer = usaCustomer;
+
+            // Generic inference used <TValue> = <Customer>
+            string json = JsonSerializer.ToString(customer);
+
+            UsaCustomer deserializedCustomer = JsonSerializer.Parse<UsaCustomer>(json);
+
+            // We only serialized the Customer base class
+            Assert.Equal(typeof(UsaCustomer), deserializedCustomer.GetType());
+            Assert.Equal(typeof(Address), deserializedCustomer.Address.GetType());
+            ((Customer)deserializedCustomer).VerifyNonVirtual();
+       }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs
new file mode 100644 (file)
index 0000000..97ba2e6
--- /dev/null
@@ -0,0 +1,99 @@
+// 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.
+
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static class PropertyVisibilityTests
+    {
+        [Fact]
+        public static void NoSetter()
+        {
+            var obj = new ClassWithNoSetter();
+
+            string json = JsonSerializer.ToString(obj);
+            Assert.Equal(@"{""MyString"":""DefaultValue""}", json);
+
+            ClassWithNoSetter objCopy = JsonSerializer.Parse<ClassWithNoSetter>(json);
+            Assert.Equal("DefaultValue", objCopy.MyString);
+        }
+
+        [Fact]
+        public static void NoGetter()
+        {
+            var objNoSetter = new ClassWithNoSetter();
+
+            string json = JsonSerializer.ToString(objNoSetter);
+            Assert.Equal(@"{""MyString"":""DefaultValue""}", json);
+
+            ClassWithNoGetter objNoGetter = JsonSerializer.Parse<ClassWithNoGetter>(json);
+            Assert.Equal("DefaultValue", objNoGetter.GetMyString());
+        }
+
+        [Fact]
+        public static void PrivateGetter()
+        {
+            var obj = new ClassWithPrivateSetterAndGetter();
+            obj.SetMyString("Hello");
+
+            string json = JsonSerializer.ToString(obj);
+            Assert.Equal(@"{}", json);
+        }
+
+        [Fact]
+        public static void PrivateSetter()
+        {
+            string json = @"{""MyString"":""Hello""}";
+
+            ClassWithPrivateSetterAndGetter objCopy = JsonSerializer.Parse<ClassWithPrivateSetterAndGetter>(json);
+            Assert.Null(objCopy.GetMyString());
+        }
+
+        // Todo: add tests with missing object property and missing collection property.
+
+        public class ClassWithNoSetter
+        {
+            public ClassWithNoSetter()
+            {
+                MyString = "DefaultValue";
+            }
+
+            public string MyString { get; }
+        }
+
+        public class ClassWithNoGetter
+        {
+            string _myString = "";
+
+            public string MyString
+            {
+                set
+                {
+                    _myString = value;
+                }
+            }
+
+            public string GetMyString()
+            {
+                return _myString;
+            }
+        }
+
+        public class ClassWithPrivateSetterAndGetter
+        {
+            private string MyString { get; set; }
+
+            public string GetMyString()
+            {
+                return MyString;
+            }
+
+            public void SetMyString(string value)
+            {
+                MyString = value;
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/SpanTests.cs b/src/libraries/System.Text.Json/tests/Serialization/SpanTests.cs
new file mode 100644 (file)
index 0000000..bd458fb
--- /dev/null
@@ -0,0 +1,70 @@
+// 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.
+
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static class SpanTests
+    {
+        [Fact]
+        public static void NullObjectInputFail()
+        {
+            Assert.Throws<ArgumentNullException>(() => JsonSerializer.Parse<string>((ReadOnlySpan<byte>)null));
+        }
+
+        [Theory]
+        [MemberData(nameof(ReadSuccessCases))]
+        public static void Read(Type classType, byte[] data)
+        {
+            object obj = JsonSerializer.Parse(data, classType);
+            Assert.IsAssignableFrom(typeof(ITestClass), obj);
+            ((ITestClass)obj).Verify();
+        }
+
+        [Fact]
+        public static void ReadGenericApi()
+        {
+            SimpleTestClass obj = JsonSerializer.Parse<SimpleTestClass>(SimpleTestClass.s_data);
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void VerifyValueFail()
+        {
+            Assert.Throws<ArgumentNullException>(() => JsonSerializer.ToString(new object(), (Type)null));
+        }
+
+        [Fact]
+        public static void VerifyTypeFail()
+        {
+            Assert.Throws<ArgumentException>(() => JsonSerializer.ToString(1, typeof(string)));
+        }
+
+        [Fact]
+        public static void NullObjectOutput()
+        {
+            byte[] encodedNull = Encoding.UTF8.GetBytes(@"null");
+
+            {
+                byte[] output = JsonSerializer.ToBytes(null, null);
+                Assert.Equal(encodedNull, output);
+            }
+
+            {
+                byte[] output = JsonSerializer.ToBytes(null, typeof(NullTests));
+                Assert.Equal(encodedNull, output);
+            }
+        }
+
+        public static IEnumerable<object[]> ReadSuccessCases
+        {
+            get
+            {
+                return TestData.ReadSuccessCases;
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/Stream.ReadTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Stream.ReadTests.cs
new file mode 100644 (file)
index 0000000..6997450
--- /dev/null
@@ -0,0 +1,49 @@
+// 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.
+
+using System.IO;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class StreamTests
+    {
+        [Fact]
+        public static async void NullObjectInputFalse()
+        {
+            await Assert.ThrowsAsync<ArgumentNullException>(async () => await JsonSerializer.ReadAsync<string>((Stream)null));
+        }
+
+        [Fact]
+        public static async Task ReadSimpleObjectAsync()
+        {
+            using (MemoryStream stream = new MemoryStream(SimpleTestClass.s_data))
+            {
+                JsonSerializerOptions options = new JsonSerializerOptions
+                {
+                    DefaultBufferSize = 1
+                };
+
+                SimpleTestClass obj = await JsonSerializer.ReadAsync<SimpleTestClass>(stream, options);
+                obj.Verify();
+            }
+        }
+
+        [Fact]
+        public static async Task ReadPrimitivesAsync()
+        {
+            using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(@"1")))
+            {
+                JsonSerializerOptions options = new JsonSerializerOptions
+                {
+                    DefaultBufferSize = 1
+                };
+
+                int i = await JsonSerializer.ReadAsync<int>(stream, options);
+                Assert.Equal(1, i);
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/Stream.WriteTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Stream.WriteTests.cs
new file mode 100644 (file)
index 0000000..36c3674
--- /dev/null
@@ -0,0 +1,168 @@
+// 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.
+
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class StreamTests
+    {
+        [Fact]
+        public async static Task VerifyValueFail()
+        {
+            MemoryStream stream = new MemoryStream();
+            await Assert.ThrowsAsync<ArgumentNullException>(async () => await JsonSerializer.WriteAsync("", null, stream));
+        }
+
+        [Fact]
+        public async static Task VerifyTypeFail()
+        {
+            MemoryStream stream = new MemoryStream();
+            await Assert.ThrowsAsync<ArgumentException>(async () => await JsonSerializer.WriteAsync(1, typeof(string), stream));
+        }
+
+        [Fact]
+        public static async Task NullObjectValue()
+        {
+            MemoryStream stream = new MemoryStream();
+            await JsonSerializer.WriteAsync((object)null, stream);
+
+            stream.Seek(0, SeekOrigin.Begin);
+
+            byte[] readBuffer = new byte[4];
+            int bytesRead = stream.Read(readBuffer, 0, 4);
+
+            Assert.Equal(4, bytesRead);
+            string value = Encoding.UTF8.GetString(readBuffer);
+            Assert.Equal("null", value);
+        }
+
+        [Fact]
+        public static async Task RoundTripAsync()
+        {
+            byte[] buffer;
+
+            using (TestStream stream = new TestStream(1))
+            {
+                await WriteAsync(stream);
+
+                // Make a copy
+                buffer = stream.ToArray();
+            }
+
+            using (TestStream stream = new TestStream(buffer))
+            {
+                await ReadAsync(stream);
+            }
+        }
+
+        private static async Task WriteAsync(TestStream stream)
+        {
+            JsonSerializerOptions options = new JsonSerializerOptions
+            {
+                // Will likely default to 4K due to buffer pooling.
+                DefaultBufferSize = 1
+            };
+
+            {
+                LargeDataTestClass obj = new LargeDataTestClass();
+                obj.Initialize();
+                obj.Verify();
+
+                await JsonSerializer.WriteAsync(obj, stream, options: options);
+            }
+
+            // Must be changed if the test classes change:
+            Assert.Equal(551_368, stream.TestWriteBytesCount);
+
+            // We should have more than one write called due to the large byte count.
+            Assert.True(stream.TestWriteCount > 0);
+
+            // We don't auto-flush.
+            Assert.True(stream.TestFlushCount == 0);
+        }
+
+        private static async Task ReadAsync(TestStream stream)
+        {
+            JsonSerializerOptions options = new JsonSerializerOptions
+            {
+                // Will likely default to 4K due to buffer pooling.
+                DefaultBufferSize = 1
+            };
+
+            LargeDataTestClass obj = await JsonSerializer.ReadAsync<LargeDataTestClass>(stream, options);
+            // Must be changed if the test classes change; may be > since last read may not have filled buffer.
+            Assert.True(stream.TestRequestedReadBytesCount >= 551368);
+
+            // We should have more than one read called due to the large byte count.
+            Assert.True(stream.TestReadCount > 0);
+
+            // We don't auto-flush.
+            Assert.True(stream.TestFlushCount == 0);
+
+            obj.Verify();
+        }
+
+        [Fact]
+        public static async Task WritePrimitivesAsync()
+        {
+            MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(@"1"));
+            JsonSerializerOptions options = new JsonSerializerOptions
+            {
+                DefaultBufferSize = 1
+            };
+
+            int i = await JsonSerializer.ReadAsync<int>(stream, options);
+            Assert.Equal(1, i);
+        }
+    }
+
+    public class TestStream : MemoryStream
+    {
+        public int TestFlushCount { get; private set; }
+
+        public int TestWriteCount { get; private set; }
+        public int TestWriteBytesCount { get; private set; }
+
+        public int TestReadCount { get; private set; }
+        public int TestRequestedReadBytesCount { get; private set; }
+
+        public TestStream(int capacity) : base(capacity) { }
+
+        public TestStream(byte[] buffer) : base(buffer) { }
+
+        public override Task FlushAsync(CancellationToken cancellationToken)
+        {
+            TestFlushCount++;
+            return base.FlushAsync(cancellationToken);
+        }
+
+#if BUILDING_INBOX_LIBRARY
+        public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken)
+        {
+            TestWriteCount++;
+            TestWriteBytesCount += source.Length;
+            return base.WriteAsync(source, cancellationToken);
+        }
+#else
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            TestWriteCount++;
+            TestWriteBytesCount += (count - offset);
+            return base.WriteAsync(buffer, offset, count, cancellationToken);
+        }
+#endif
+
+        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            TestReadCount++;
+            TestRequestedReadBytesCount += count;
+
+            return base.ReadAsync(buffer, offset, count, cancellationToken);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/String.ReadTests.cs b/src/libraries/System.Text.Json/tests/Serialization/String.ReadTests.cs
new file mode 100644 (file)
index 0000000..fa23e06
--- /dev/null
@@ -0,0 +1,53 @@
+// 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.
+
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class StringTests
+    {
+        [Fact]
+        public static void NullObjectInputFail()
+        {
+
+            Assert.Throws<ArgumentNullException>(() => JsonSerializer.Parse<string>((string)null));
+        }
+
+        [Fact]
+        public static void NullLiteralObjectInput()
+        {
+            {
+                string obj = JsonSerializer.Parse<string>("null");
+                Assert.Null(obj);
+            }
+
+            {
+                string obj = JsonSerializer.Parse<string>(@"""null""");
+                Assert.Equal("null", obj);
+            }
+        }
+
+        [Fact]
+        public static void EmptyStringInput()
+        {
+            string obj = JsonSerializer.Parse<string>(@"""""");
+            Assert.Equal(string.Empty, obj);
+        }
+
+        [Fact]
+        public static void ReadSimpleClass()
+        {
+            SimpleTestClass obj = JsonSerializer.Parse<SimpleTestClass>(SimpleTestClass.s_json);
+            obj.Verify();
+        }
+
+        [Fact]
+        public static void EmptyClassWithRandomData()
+        {
+            JsonSerializer.Parse<EmptyClass>(SimpleTestClass.s_json);
+            JsonSerializer.Parse<EmptyClass>(SimpleTestClassWithNulls.s_json);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/String.WriteTests.cs b/src/libraries/System.Text.Json/tests/Serialization/String.WriteTests.cs
new file mode 100644 (file)
index 0000000..57b8a7a
--- /dev/null
@@ -0,0 +1,64 @@
+// 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.
+
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class StringTests
+    {
+        [Fact]
+        public static void VerifyValueFail()
+        {
+            Assert.Throws<ArgumentNullException>(() => JsonSerializer.ToString("", (Type)null));
+        }
+
+        [Fact]
+        public static void VerifyTypeFail()
+        {
+            Assert.Throws<ArgumentException>(() => JsonSerializer.ToString(1, typeof(string)));
+        }
+
+        [Fact]
+        public static void NullObjectOutput()
+        {
+            {
+                string output = JsonSerializer.ToString<string>(null);
+                Assert.Equal("null", output);
+            }
+
+            {
+                string output = JsonSerializer.ToString<string>(null, null);
+                Assert.Equal("null", output);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(WriteSuccessCases))]
+        public static void Write(ITestClass testObj)
+        {
+            string json;
+
+            {
+                testObj.Initialize();
+                testObj.Verify();
+                json = JsonSerializer.ToString(testObj, testObj.GetType());
+            }
+
+            {
+                ITestClass obj = (ITestClass)JsonSerializer.Parse(json, testObj.GetType());
+                obj.Verify();
+            }
+        }
+
+        public static IEnumerable<object[]> WriteSuccessCases
+        {
+            get
+            {
+                return TestData.WriteSuccessCases;
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs
new file mode 100644 (file)
index 0000000..b6f8856
--- /dev/null
@@ -0,0 +1,106 @@
+// 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.
+
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public class Person : ITestClass
+    {
+        public string Name { get; set; }
+        public Address Address { get; set; }
+
+        public Person()
+        {
+            Address = new Address();
+        }
+
+        public virtual void Initialize()
+        {
+            Name = "MyName";
+
+            Address = new Address();
+            Address.Initialize();
+        }
+
+        public virtual void Verify()
+        {
+            Assert.Equal("MyName", Name);
+            Address.Verify();
+        }
+
+        public void VerifyNonVirtual()
+        {
+            Assert.Equal("MyName", Name);
+            Address.VerifyNonVirtual();
+        }
+    }
+
+    public class Address : ITestClass
+    {
+        public string City { get; set; }
+
+        public virtual void Initialize()
+        {
+            City = "MyCity";
+        }
+
+        public virtual void Verify()
+        {
+            Assert.Equal("MyCity", City);
+        }
+
+        public void VerifyNonVirtual()
+        {
+            Assert.Equal("MyCity", City);
+        }
+    }
+
+    public class Customer : Person, ITestClass
+    {
+        public decimal CreditLimit { get; set; }
+
+        public override void Initialize()
+        {
+            CreditLimit = 500;
+            base.Initialize();
+        }
+
+        public override void Verify()
+        {
+            Assert.Equal(500, CreditLimit);
+            base.Verify();
+        }
+
+        new public void VerifyNonVirtual()
+        {
+            Assert.Equal(500, CreditLimit);
+        }
+    }
+
+    public class UsaCustomer : Customer, ITestClass
+    {
+        public UsaCustomer() : base()
+        {
+            Address = new UsaAddress();
+        }
+    }
+
+    public class UsaAddress : Address, ITestClass
+    {
+        public string State { get; set; }
+
+        public override void Initialize()
+        {
+            State = "MyState";
+            base.Initialize();
+        }
+
+        public override void Verify()
+        {
+            Assert.Equal("MyState", State);
+            base.Verify();
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs
new file mode 100644 (file)
index 0000000..8623491
--- /dev/null
@@ -0,0 +1,660 @@
+// 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.
+
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public interface ITestClass
+    {
+        void Initialize();
+        void Verify();
+    }
+
+    public enum SampleEnum
+    {
+        One = 1,
+        Two = 2
+    }
+
+    public class SimpleTestClass : ITestClass
+    {
+        public short MyInt16 { get; set; }
+        public int MyInt32 { get; set; }
+        public long MyInt64 { get; set; }
+        public ushort MyUInt16 { get; set; }
+        public uint MyUInt32 { get; set; }
+        public ulong MyUInt64 { get; set; }
+        public byte MyByte { get; set; }
+        public sbyte MySByte { get; set; }
+        public char MyChar { get; set; }
+        public string MyString { get; set; }
+        public decimal MyDecimal { get; set; }
+        public bool MyBooleanTrue { get; set; }
+        public bool MyBooleanFalse { get; set; }
+        public float MySingle { get; set; }
+        public double MyDouble { get; set; }
+        public DateTime MyDateTime { get; set; }
+        public SampleEnum MyEnum { get; set; }
+        public short[] MyInt16Array{ get; set; }
+        public int[] MyInt32Array{ get; set; }
+        public long[] MyInt64Array{ get; set; }
+        public ushort[] MyUInt16Array{ get; set; }
+        public uint[] MyUInt32Array{ get; set; }
+        public ulong[] MyUInt64Array{ get; set; }
+        public byte[] MyByteArray{ get; set; }
+        public sbyte[] MySByteArray{ get; set; }
+        public char[] MyCharArray{ get; set; }
+        public string[] MyStringArray{ get; set; }
+        public decimal[] MyDecimalArray{ get; set; }
+        public bool[] MyBooleanTrueArray{ get; set; }
+        public bool[] MyBooleanFalseArray{ get; set; }
+        public float[] MySingleArray{ get; set; }
+        public double[] MyDoubleArray{ get; set; }
+        public DateTime[] MyDateTimeArray{ get; set; }
+        public SampleEnum[] MyEnumArray{ get; set; }
+
+        public static readonly string s_json =
+                @"{" +
+                @"""MyInt16"" : 1," +
+                @"""MyInt32"" : 2," +
+                @"""MyInt64"" : 3," +
+                @"""MyUInt16"" : 4," +
+                @"""MyUInt32"" : 5," +
+                @"""MyUInt64"" : 6," +
+                @"""MyByte"" : 7," +
+                @"""MySByte"" : 8," +
+                @"""MyChar"" : ""a""," +
+                @"""MyString"" : ""Hello""," +
+                @"""MyBooleanTrue"" : true," +
+                @"""MyBooleanFalse"" : false," +
+                @"""MySingle"" : 1.1," +
+                @"""MyDouble"" : 2.2," +
+                @"""MyDecimal"" : 3.3," +
+                @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," +
+                @"""MyEnum"" : 2," + // int by default
+                @"""MyInt16Array"" : [1]," +
+                @"""MyInt32Array"" : [2]," +
+                @"""MyInt64Array"" : [3]," +
+                @"""MyUInt16Array"" : [4]," +
+                @"""MyUInt32Array"" : [5]," +
+                @"""MyUInt64Array"" : [6]," +
+                @"""MyByteArray"" : [7]," +
+                @"""MySByteArray"" : [8]," +
+                @"""MyCharArray"" : [""a""]," +
+                @"""MyStringArray"" : [""Hello""]," +
+                @"""MyBooleanTrueArray"" : [true]," +
+                @"""MyBooleanFalseArray"" : [false]," +
+                @"""MySingleArray"" : [1.1]," +
+                @"""MyDoubleArray"" : [2.2]," +
+                @"""MyDecimalArray"" : [3.3]," +
+                @"""MyDateTimeArray"" : [""2019-01-30T12:01:02.0000000Z""]," +
+                @"""MyEnumArray"" : [2]" + // int by default
+                @"}";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Initialize()
+        {
+            MyInt16 = 1;
+            MyInt32 = 2;
+            MyInt64 = 3;
+            MyUInt16 = 4;
+            MyUInt32 = 5;
+            MyUInt64 = 6;
+            MyByte = 7;
+            MySByte = 8;
+            MyChar = 'a';
+            MyString = "Hello";
+            MyBooleanTrue = true;
+            MyBooleanFalse = false;
+            MySingle = 1.1f;
+            MyDouble = 2.2d;
+            MyDecimal = 3.3m;
+            MyDateTime = new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc);
+            MyEnum = SampleEnum.Two;
+
+            MyInt16Array = new short[] { 1 };
+            MyInt32Array = new int[] { 2 };
+            MyInt64Array = new long[] { 3 };
+            MyUInt16Array = new ushort[] { 4 };
+            MyUInt32Array = new uint[] { 5 };
+            MyUInt64Array = new ulong[] { 6 };
+            MyByteArray = new byte[] { 7 };
+            MySByteArray = new sbyte[] { 8 };
+            MyCharArray = new char[] { 'a' };
+            MyStringArray = new string[] { "Hello" };
+            MyBooleanTrueArray = new bool[] { true };
+            MyBooleanFalseArray = new bool[] { false };
+            MySingleArray = new float[] { 1.1f };
+            MyDoubleArray = new double[] { 2.2d };
+            MyDecimalArray = new decimal[] { 3.3m };
+            MyDateTimeArray = new DateTime[] { new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc) };
+            MyEnumArray = new SampleEnum[] { SampleEnum.Two };
+        }
+
+        public void Verify()
+        {
+            Assert.Equal((short)1, MyInt16);
+            Assert.Equal((int)2, MyInt32);
+            Assert.Equal((long)3, MyInt64);
+            Assert.Equal((ushort)4, MyUInt16);
+            Assert.Equal((uint)5, MyUInt32);
+            Assert.Equal((ulong)6, MyUInt64);
+            Assert.Equal((byte)7, MyByte);
+            Assert.Equal((sbyte)8, MySByte);
+            Assert.Equal('a', MyChar);
+            Assert.Equal("Hello", MyString);
+            Assert.Equal(3.3m, MyDecimal);
+            Assert.Equal(false, MyBooleanFalse);
+            Assert.Equal(true, MyBooleanTrue);
+            Assert.Equal(1.1f, MySingle);
+            Assert.Equal(2.2d, MyDouble);
+            Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTime);
+            Assert.Equal(SampleEnum.Two, MyEnum);
+
+            Assert.Equal((short)1, MyInt16Array[0]);
+            Assert.Equal((int)2, MyInt32Array[0]);
+            Assert.Equal((long)3, MyInt64Array[0]);
+            Assert.Equal((ushort)4, MyUInt16Array[0]);
+            Assert.Equal((uint)5, MyUInt32Array[0]);
+            Assert.Equal((ulong)6, MyUInt64Array[0]);
+            Assert.Equal((byte)7, MyByteArray[0]);
+            Assert.Equal((sbyte)8, MySByteArray[0]);
+            Assert.Equal('a', MyCharArray[0]);
+            Assert.Equal("Hello", MyStringArray[0]);
+            Assert.Equal(3.3m, MyDecimalArray[0]);
+            Assert.Equal(false, MyBooleanFalseArray[0]);
+            Assert.Equal(true, MyBooleanTrueArray[0]);
+            Assert.Equal(1.1f, MySingleArray[0]);
+            Assert.Equal(2.2d, MyDoubleArray[0]);
+            Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTimeArray[0]);
+            Assert.Equal(SampleEnum.Two, MyEnumArray[0]);
+        }
+    }
+
+    public abstract class SimpleBaseClassWithNullables
+    {
+        public short? MyInt16 { get; set; }
+        public int? MyInt32 { get; set; }
+        public long? MyInt64 { get; set; }
+        public ushort? MyUInt16 { get; set; }
+        public uint? MyUInt32 { get; set; }
+        public ulong? MyUInt64 { get; set; }
+        public byte? MyByte { get; set; }
+        public sbyte? MySByte { get; set; }
+        public char? MyChar { get; set; }
+        public decimal? MyDecimal { get; set; }
+        public bool? MyBooleanTrue { get; set; }
+        public bool? MyBooleanFalse { get; set; }
+        public float? MySingle { get; set; }
+        public double? MyDouble { get; set; }
+        public DateTime? MyDateTime { get; set; }
+        public SampleEnum? MyEnum { get; set; }
+    }
+
+    public class SimpleTestClassWithNulls : SimpleBaseClassWithNullables, ITestClass
+    {
+        public void Initialize()
+        {
+        }
+
+        public void Verify()
+        {
+            Assert.Null(MyInt16);
+            Assert.Null(MyInt32);
+            Assert.Null(MyInt64);
+            Assert.Null(MyUInt16);
+            Assert.Null(MyUInt32);
+            Assert.Null(MyUInt64);
+            Assert.Null(MyByte);
+            Assert.Null(MySByte);
+            Assert.Null(MyChar);
+            Assert.Null(MyDecimal);
+            Assert.Null(MyBooleanFalse);
+            Assert.Null(MyBooleanTrue);
+            Assert.Null(MySingle);
+            Assert.Null(MyDouble);
+            Assert.Null(MyDateTime);
+            Assert.Null(MyEnum);
+        }
+        public static readonly string s_json =
+                @"{" +
+                @"""MyInt16"" : null," +
+                @"""MyInt32"" : null," +
+                @"""MyInt64"" : null," +
+                @"""MyUInt16"" : null," +
+                @"""MyUInt32"" : null," +
+                @"""MyUInt64"" : null," +
+                @"""MyByte"" : null," +
+                @"""MySByte"" : null," +
+                @"""MyChar"" : null," +
+                @"""MyBooleanTrue"" : null," +
+                @"""MyBooleanFalse"" : null," +
+                @"""MySingle"" : null," +
+                @"""MyDouble"" : null," +
+                @"""MyDecimal"" : null," +
+                @"""MyDateTime"" : null," +
+                @"""MyEnum"" : null" +
+                @"}";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+    }
+
+    public class SimpleTestClassWithNullables : SimpleBaseClassWithNullables, ITestClass
+    {
+        public static readonly string s_json =
+                @"{" +
+                @"""MyInt16"" : 1," +
+                @"""MyInt32"" : 2," +
+                @"""MyInt64"" : 3," +
+                @"""MyUInt16"" : 4," +
+                @"""MyUInt32"" : 5," +
+                @"""MyUInt64"" : 6," +
+                @"""MyByte"" : 7," +
+                @"""MySByte"" : 8," +
+                @"""MyChar"" : ""a""," +
+                @"""MyBooleanTrue"" : true," +
+                @"""MyBooleanFalse"" : false," +
+                @"""MySingle"" : 1.1," +
+                @"""MyDouble"" : 2.2," +
+                @"""MyDecimal"" : 3.3," +
+                @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," +
+                @"""MyEnum"" : 2" + // int by default
+                @"}";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Initialize()
+        {
+            MyInt16 = 1;
+            MyInt32 = 2;
+            MyInt64 = 3;
+            MyUInt16 = 4;
+            MyUInt32 = 5;
+            MyUInt64 = 6;
+            MyByte = 7;
+            MySByte = 8;
+            MyChar = 'a';
+            MyBooleanTrue = true;
+            MyBooleanFalse = false;
+            MySingle = 1.1f;
+            MyDouble = 2.2d;
+            MyDecimal = 3.3m;
+            MyDateTime = new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc);
+            MyEnum = SampleEnum.Two;
+        }
+
+        public void Verify()
+        {
+            Assert.Equal(MyInt16, (short)1);
+            Assert.Equal(MyInt32, (int)2);
+            Assert.Equal(MyInt64, (long)3);
+            Assert.Equal(MyUInt16, (ushort)4);
+            Assert.Equal(MyUInt32, (uint)5);
+            Assert.Equal(MyUInt64, (ulong)6);
+            Assert.Equal(MyByte, (byte)7);
+            Assert.Equal(MySByte, (sbyte)8);
+            Assert.Equal(MyChar, 'a');
+            Assert.Equal(MyDecimal, 3.3m);
+            Assert.Equal(MyBooleanFalse, false);
+            Assert.Equal(MyBooleanTrue, true);
+            Assert.Equal(MySingle, 1.1f);
+            Assert.Equal(MyDouble, 2.2d);
+            Assert.Equal(MyDateTime, new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc));
+            Assert.Equal(MyEnum, SampleEnum.Two);
+        }
+    }
+
+    public class TestClassWithNull
+    {
+        public string MyString { get; set; }
+        public static readonly string s_json =
+                @"{" +
+                @"""MyString"" : null" +
+                @"}";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Initialize()
+        {
+            MyString = null;
+        }
+
+        public void Verify()
+        {
+            Assert.Equal(MyString, null);
+        }
+    }
+
+    public class TestClassWithNullButInitialized
+    {
+        public string MyString { get; set; } = "Hello";
+        public int? MyInt { get; set; } = 1;
+        public static readonly string s_json =
+                @"{" +
+                @"""MyString"" : null," +
+                @"""MyInt"" : null" +
+                @"}";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+    }
+
+    public class TestClassWithNestedObjectInner : ITestClass
+    {
+        public SimpleTestClass MyData { get; set; }
+
+        public static readonly string s_json =
+            @"{" +
+                @"""MyData"":" + SimpleTestClass.s_json +
+            @"}";
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+
+        public void Initialize()
+        {
+            MyData = new SimpleTestClass();
+            MyData.Initialize();
+        }
+
+        public void Verify()
+        {
+            Assert.NotNull(MyData);
+            MyData.Verify();
+        }
+    }
+
+    public class TestClassWithNestedObjectOuter : ITestClass
+    {
+        public TestClassWithNestedObjectInner MyData { get; set; }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            @"{" +
+                @"""MyData"":" + TestClassWithNestedObjectInner.s_json +
+            @"}");
+
+        public void Initialize()
+        {
+            MyData = new TestClassWithNestedObjectInner();
+            MyData.Initialize();
+        }
+
+        public void Verify()
+        {
+            Assert.NotNull(MyData);
+            MyData.Verify();
+        }
+    }
+
+    public class TestClassWithObjectList : ITestClass
+    {
+        public List<SimpleTestClass> MyData { get; set; }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            @"{" +
+                @"""MyData"":[" +
+                    SimpleTestClass.s_json + "," +
+                    SimpleTestClass.s_json +
+                @"]" +
+            @"}");
+
+        public void Initialize()
+        {
+            MyData = new List<SimpleTestClass>();
+
+            {
+                SimpleTestClass obj = new SimpleTestClass();
+                obj.Initialize();
+                MyData.Add(obj);
+            }
+
+            {
+                SimpleTestClass obj = new SimpleTestClass();
+                obj.Initialize();
+                MyData.Add(obj);
+            }
+        }
+
+        public void Verify()
+        {
+            Assert.Equal(2, MyData.Count);
+            MyData[0].Verify();
+            MyData[1].Verify();
+        }
+    }
+
+    public class TestClassWithObjectArray : ITestClass
+    {
+        public SimpleTestClass[] MyData { get; set; }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            @"{" +
+                @"""MyData"":[" +
+                    SimpleTestClass.s_json + "," +
+                    SimpleTestClass.s_json +
+                @"]" +
+            @"}");
+
+        public void Initialize()
+        {
+            SimpleTestClass obj1 = new SimpleTestClass();
+            obj1.Initialize();
+
+            SimpleTestClass obj2 = new SimpleTestClass();
+            obj2.Initialize();
+
+            MyData = new SimpleTestClass[2] { obj1, obj2 };
+        }
+
+        public void Verify()
+        {
+            MyData[0].Verify();
+            MyData[1].Verify();
+            Assert.Equal(2, MyData.Length);
+        }
+    }
+
+    public class TestClassWithStringArray : ITestClass
+    {
+        public string[] MyData { get; set; }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            @"{" +
+                @"""MyData"":[" +
+                    @"""Hello""," +
+                    @"""World""" +
+                @"]" +
+            @"}");
+
+        public void Initialize()
+        {
+            MyData = new string[] { "Hello", "World" };
+        }
+
+        public void Verify()
+        {
+            Assert.Equal("Hello", MyData[0]);
+            Assert.Equal("World", MyData[1]);
+            Assert.Equal(2, MyData.Length);
+        }
+    }
+
+    public class TestClassWithCycle
+    {
+        public TestClassWithCycle Parent { get; set; }
+
+        public void Initialize()
+        {
+            Parent = this;
+        }
+    }
+
+    public class TestClassWithGenericList : ITestClass
+    {
+        public List<string> MyData { get; set; }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            @"{" +
+                @"""MyData"":[" +
+                    @"""Hello""," +
+                    @"""World""" +
+                @"]" +
+            @"}");
+
+        public void Initialize()
+        {
+            MyData = new List<string>
+            {
+                "Hello",
+                "World"
+            };
+            Assert.Equal(2, MyData.Count);
+        }
+
+        public void Verify()
+        {
+            Assert.Equal("Hello", MyData[0]);
+            Assert.Equal("World", MyData[1]);
+            Assert.Equal(2, MyData.Count);
+        }
+    }
+
+    public class SimpleDerivedTestClass : SimpleTestClass
+    {
+    }
+
+    public class OverridePropertyNameRuntime_TestClass
+    {
+        public Int16 MyInt16 { get; set; }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            @"{" +
+            @"""blah"" : 1" +
+            @"}"
+        );
+    }
+
+    public class LargeDataTestClass : ITestClass
+    {
+        public List<LargeDataChildTestClass> Children { get; set; } = new List<LargeDataChildTestClass>();
+        public const int ChildrenCount = 10;
+
+        public string MyString { get; set; }
+        public const int MyStringLength = 1000;
+
+        public void Initialize()
+        {
+            MyString = new string('1', MyStringLength);
+
+            for (int i = 0; i < ChildrenCount; i++)
+            {
+                var child = new LargeDataChildTestClass
+                {
+                    MyString = new string('2', LargeDataChildTestClass.MyStringLength),
+                    MyStringArray = new string[LargeDataChildTestClass.MyStringArrayArrayCount]
+                };
+                for (int j = 0; j < child.MyStringArray.Length; j++)
+                {
+                    child.MyStringArray[j] = new string('3', LargeDataChildTestClass.MyStringArrayElementStringLength);
+                }
+
+                Children.Add(child);
+            }
+        }
+
+        public void Verify()
+        {
+            Assert.Equal(MyStringLength, MyString.Length);
+            Assert.Equal('1', MyString[0]);
+            Assert.Equal('1', MyString[MyStringLength - 1]);
+
+            Assert.Equal(ChildrenCount, Children.Count);
+            for (int i = 0; i < ChildrenCount; i++)
+            {
+                LargeDataChildTestClass child = Children[i];
+                Assert.Equal(LargeDataChildTestClass.MyStringLength, child.MyString.Length);
+                Assert.Equal('2', child.MyString[0]);
+                Assert.Equal('2', child.MyString[LargeDataChildTestClass.MyStringLength - 1]);
+
+                Assert.Equal(LargeDataChildTestClass.MyStringArrayArrayCount, child.MyStringArray.Length);
+                for (int j = 0; j < LargeDataChildTestClass.MyStringArrayArrayCount; j++)
+                {
+                    Assert.Equal('3', child.MyStringArray[j][0]);
+                    Assert.Equal('3', child.MyStringArray[j][LargeDataChildTestClass.MyStringArrayElementStringLength - 1]);
+                }
+            }
+        }
+    }
+
+    public class LargeDataChildTestClass
+    {
+        public const int MyStringLength = 2000;
+        public string MyString { get; set; }
+
+        public string[] MyStringArray { get; set; }
+        public const int MyStringArrayArrayCount = 1000;
+        public const int MyStringArrayElementStringLength = 50;
+    }
+
+    public class EmptyClass { }
+
+    public class BasicJson : ITestClass
+    {
+        public int age { get; set; }
+        public string first { get; set; }
+        public string last { get; set; }
+        public List<string> phoneNumbers { get; set; }
+        public BasicJsonAddress address { get; set; }
+
+        public void Initialize()
+        {
+            age = 30;
+            first = "John";
+            last = "Smith";
+            phoneNumbers = new List<string> { "425-000-0000", "425-000-0001" };
+            address = new BasicJsonAddress
+            {
+                street = "1 Microsoft Way",
+                city = "Redmond",
+                zip = 98052
+            };
+        }
+
+        public void Verify()
+        {
+            Assert.Equal(30, age);
+            Assert.Equal("John", first);
+            Assert.Equal("Smith", last);
+            Assert.Equal("425-000-0000", phoneNumbers[0]);
+            Assert.Equal("425-000-0001", phoneNumbers[1]);
+            Assert.Equal("1 Microsoft Way", address.street);
+            Assert.Equal("Redmond", address.city);
+            Assert.Equal(98052, address.zip);
+        }
+
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(
+            "{" +
+                @"""age"" : 30," +
+                @"""first"" : ""John""," +
+                @"""last"" : ""Smith""," +
+                @"""phoneNumbers"" : [" +
+                    @"""425-000-0000""," +
+                    @"""425-000-0001""" +
+                @"]," +
+                @"""address"" : {" +
+                    @"""street"" : ""1 Microsoft Way""," +
+                    @"""city"" : ""Redmond""," +
+                    @"""zip"" : 98052" +
+                "}" +
+            "}");
+    }
+
+    public class BasicJsonAddress
+    {
+        public string street { get; set; }
+        public string city { get; set; }
+        public int zip { get; set; }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestData.cs b/src/libraries/System.Text.Json/tests/Serialization/TestData.cs
new file mode 100644 (file)
index 0000000..6a60deb
--- /dev/null
@@ -0,0 +1,42 @@
+// 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.
+
+using System.Collections.Generic;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static class TestData
+    {
+        public static IEnumerable<object[]> ReadSuccessCases
+        {
+            get
+            {
+                yield return new object[] { typeof(SimpleTestClass), SimpleTestClass.s_data };
+                yield return new object[] { typeof(SimpleTestClassWithNullables), SimpleTestClassWithNullables.s_data };
+                yield return new object[] { typeof(SimpleTestClassWithNulls), SimpleTestClassWithNulls.s_data };
+                yield return new object[] { typeof(BasicJson), BasicJson.s_data };
+                yield return new object[] { typeof(TestClassWithNestedObjectInner), TestClassWithNestedObjectInner.s_data };
+                yield return new object[] { typeof(TestClassWithNestedObjectOuter), TestClassWithNestedObjectOuter.s_data };
+                yield return new object[] { typeof(TestClassWithObjectArray), TestClassWithObjectArray.s_data };
+                yield return new object[] { typeof(TestClassWithStringArray), TestClassWithStringArray.s_data };
+                yield return new object[] { typeof(TestClassWithGenericList), TestClassWithGenericList.s_data };
+            }
+        }
+        public static IEnumerable<object[]> WriteSuccessCases
+        {
+            get
+            {
+                yield return new object[] { new SimpleTestClass() };
+                yield return new object[] { new SimpleTestClassWithNullables() };
+                yield return new object[] { new SimpleTestClassWithNulls() };
+                yield return new object[] { new BasicJson() };
+                yield return new object[] { new TestClassWithNestedObjectInner() };
+                yield return new object[] { new TestClassWithNestedObjectOuter() };
+                yield return new object[] { new TestClassWithObjectArray() };
+                yield return new object[] { new TestClassWithStringArray() };
+                yield return new object[] { new TestClassWithGenericList() };
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.cs
new file mode 100644 (file)
index 0000000..1751ca5
--- /dev/null
@@ -0,0 +1,379 @@
+// 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.
+
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class ValueTests
+    {
+        [Fact]
+        public static void ReadPrimitives()
+        {
+            int i = JsonSerializer.Parse<int>(Encoding.UTF8.GetBytes(@"1"));
+            Assert.Equal(1, i);
+
+            int i2 = JsonSerializer.Parse<int>("2");
+            Assert.Equal(2, i2);
+
+            long l = JsonSerializer.Parse<long>(Encoding.UTF8.GetBytes(long.MaxValue.ToString()));
+            Assert.Equal(long.MaxValue, l);
+
+            long l2 = JsonSerializer.Parse<long>(long.MaxValue.ToString());
+            Assert.Equal(long.MaxValue, l2);
+
+            string s = JsonSerializer.Parse<string>(Encoding.UTF8.GetBytes(@"""Hello"""));
+            Assert.Equal("Hello", s);
+
+            string s2 = JsonSerializer.Parse<string>(@"""Hello""");
+            Assert.Equal("Hello", s2);
+        }
+
+        [Fact]
+        public static void ReadPrimitivesFail()
+        {
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int>(Encoding.UTF8.GetBytes(@"a")));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int[]>(Encoding.UTF8.GetBytes(@"[1,a]")));
+        }
+
+        [Fact]
+        public static void ReadPrimitiveArray()
+        {
+            int[] i = JsonSerializer.Parse<int[]>(Encoding.UTF8.GetBytes(@"[1,2]"));
+            Assert.Equal(1, i[0]);
+            Assert.Equal(2, i[1]);
+        }
+
+        [Fact]
+        public static void ReadArrayWithEnums()
+        {
+            SampleEnum[] i = JsonSerializer.Parse<SampleEnum[]>(Encoding.UTF8.GetBytes(@"[1,2]"));
+            Assert.Equal(SampleEnum.One, i[0]);
+            Assert.Equal(SampleEnum.Two, i[1]);
+        }
+
+        [Fact]
+        public static void ReadPrimitiveArrayFail()
+        {
+            // Invalid data
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int[]>(Encoding.UTF8.GetBytes(@"[1,""a""]")));
+
+            // Multidimensional arrays currently not supported
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int[,]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")));
+        }
+
+        [Fact]
+        public static void ReadPrimitiveExtraBytesFail()
+        {
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int[]>("[2] {3}"));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int[]>(Encoding.UTF8.GetBytes(@"[2] {3}")));
+        }
+
+        [Fact]
+        public static void RangeFail()
+        {
+            // These have custom code because the reader doesn't natively support:
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<byte>((byte.MinValue - 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<byte>((byte.MaxValue + 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<byte?>((byte.MinValue - 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<byte?>((byte.MaxValue + 1).ToString()));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<sbyte>((sbyte.MinValue - 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<sbyte>((sbyte.MaxValue + 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<sbyte?>((sbyte.MinValue - 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<sbyte?>((sbyte.MaxValue + 1).ToString()));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<short>((short.MinValue - 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<short>((short.MaxValue + 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<short?>((short.MinValue - 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<short?>((short.MaxValue + 1).ToString()));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ushort>((ushort.MinValue - 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ushort>((ushort.MaxValue + 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ushort?>((ushort.MinValue - 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ushort?>((ushort.MaxValue + 1).ToString()));
+
+            // To ensure range failure, just use double's MinValue and MaxValue (instead of float.MinValue\MaxValue +-1)
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<float>(double.MinValue.ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<float>(double.MaxValue.ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<float?>(double.MinValue.ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<float?>(double.MaxValue.ToString()));
+
+            // These are natively supported by the reader:
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int>(((long)int.MinValue - 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int>(((long)int.MaxValue + 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int?>(((long)int.MinValue - 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int?>(((long)int.MaxValue + 1).ToString()));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<uint>(((long)uint.MinValue - 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<uint>(((long)uint.MaxValue + 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<uint?>(((long)uint.MinValue - 1).ToString()));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<uint?>(((long)uint.MaxValue + 1).ToString()));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<long>(long.MinValue.ToString() + "0"));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<long>(long.MaxValue.ToString() + "0"));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<long?>(long.MinValue.ToString() + "0"));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<long?>(long.MaxValue.ToString() + "0"));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ulong>(ulong.MinValue.ToString() + "0"));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ulong>(ulong.MaxValue.ToString() + "0"));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ulong?>(ulong.MinValue.ToString() + "0"));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ulong?>(ulong.MaxValue.ToString() + "0"));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<decimal>(decimal.MinValue.ToString() + "0"));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<decimal>(decimal.MaxValue.ToString() + "0"));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<decimal?>(decimal.MinValue.ToString() + "0"));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<decimal?>(decimal.MaxValue.ToString() + "0"));
+
+            // todo: determine why these don't throw (issue with reader?)
+            //Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<double>(double.MinValue.ToString() + "0"));
+            //Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<double>(double.MaxValue.ToString() + "0"));
+            //Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<double?>(double.MinValue.ToString() + "0"));
+            //Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<double?>(double.MaxValue.ToString() + "0"));
+        }
+
+        [Fact]
+        public static void RangePass()
+        {
+            Assert.Equal(byte.MinValue, JsonSerializer.Parse<byte>(byte.MinValue.ToString()));
+            Assert.Equal(byte.MaxValue, JsonSerializer.Parse<byte>(byte.MaxValue.ToString()));
+            Assert.Equal(byte.MinValue, JsonSerializer.Parse<byte?>(byte.MinValue.ToString()));
+            Assert.Equal(byte.MaxValue, JsonSerializer.Parse<byte?>(byte.MaxValue.ToString()));
+
+            Assert.Equal(sbyte.MinValue, JsonSerializer.Parse<sbyte>(sbyte.MinValue.ToString()));
+            Assert.Equal(sbyte.MaxValue, JsonSerializer.Parse<sbyte>(sbyte.MaxValue.ToString()));
+            Assert.Equal(sbyte.MinValue, JsonSerializer.Parse<sbyte?>(sbyte.MinValue.ToString()));
+            Assert.Equal(sbyte.MaxValue, JsonSerializer.Parse<sbyte?>(sbyte.MaxValue.ToString()));
+
+            Assert.Equal(short.MinValue, JsonSerializer.Parse<short>(short.MinValue.ToString()));
+            Assert.Equal(short.MaxValue, JsonSerializer.Parse<short>(short.MaxValue.ToString()));
+            Assert.Equal(short.MinValue, JsonSerializer.Parse<short?>(short.MinValue.ToString()));
+            Assert.Equal(short.MaxValue, JsonSerializer.Parse<short?>(short.MaxValue.ToString()));
+
+            Assert.Equal(ushort.MinValue, JsonSerializer.Parse<ushort>(ushort.MinValue.ToString()));
+            Assert.Equal(ushort.MaxValue, JsonSerializer.Parse<ushort>(ushort.MaxValue.ToString()));
+            Assert.Equal(ushort.MinValue, JsonSerializer.Parse<ushort?>(ushort.MinValue.ToString()));
+            Assert.Equal(ushort.MaxValue, JsonSerializer.Parse<ushort?>(ushort.MaxValue.ToString()));
+
+            // todo: these fail due to double->float conversion
+            //Assert.Equal(float.MinValue, JsonSerializer.Parse<float>(float.MinValue.ToString()));
+            //Assert.Equal(float.MaxValue, JsonSerializer.Parse<float>(float.MaxValue.ToString()));
+            //Assert.Equal(float.MinValue, JsonSerializer.Parse<float?>(float.MinValue.ToString()));
+            //Assert.Equal(float.MaxValue, JsonSerializer.Parse<float?>(float.MaxValue.ToString()));
+
+            Assert.Equal(int.MinValue, JsonSerializer.Parse<int>(int.MinValue.ToString()));
+            Assert.Equal(int.MaxValue, JsonSerializer.Parse<int>(int.MaxValue.ToString()));
+            Assert.Equal(int.MinValue, JsonSerializer.Parse<int?>(int.MinValue.ToString()));
+            Assert.Equal(int.MaxValue, JsonSerializer.Parse<int?>(int.MaxValue.ToString()));
+
+            Assert.Equal(uint.MinValue, JsonSerializer.Parse<uint>(uint.MinValue.ToString()));
+            Assert.Equal(uint.MaxValue, JsonSerializer.Parse<uint>(uint.MaxValue.ToString()));
+            Assert.Equal(uint.MinValue, JsonSerializer.Parse<uint?>(uint.MinValue.ToString()));
+            Assert.Equal(uint.MaxValue, JsonSerializer.Parse<uint?>(uint.MaxValue.ToString()));
+
+            Assert.Equal(long.MinValue, JsonSerializer.Parse<long>(long.MinValue.ToString()));
+            Assert.Equal(long.MaxValue, JsonSerializer.Parse<long>(long.MaxValue.ToString()));
+            Assert.Equal(long.MinValue, JsonSerializer.Parse<long?>(long.MinValue.ToString()));
+            Assert.Equal(long.MaxValue, JsonSerializer.Parse<long?>(long.MaxValue.ToString()));
+
+            Assert.Equal(ulong.MinValue, JsonSerializer.Parse<ulong>(ulong.MinValue.ToString()));
+            Assert.Equal(ulong.MaxValue, JsonSerializer.Parse<ulong>(ulong.MaxValue.ToString()));
+            Assert.Equal(ulong.MinValue, JsonSerializer.Parse<ulong?>(ulong.MinValue.ToString()));
+            Assert.Equal(ulong.MaxValue, JsonSerializer.Parse<ulong?>(ulong.MaxValue.ToString()));
+
+            Assert.Equal(decimal.MinValue, JsonSerializer.Parse<decimal>(decimal.MinValue.ToString()));
+            Assert.Equal(decimal.MaxValue, JsonSerializer.Parse<decimal>(decimal.MaxValue.ToString()));
+            Assert.Equal(decimal.MinValue, JsonSerializer.Parse<decimal?>(decimal.MinValue.ToString()));
+            Assert.Equal(decimal.MaxValue, JsonSerializer.Parse<decimal?>(decimal.MaxValue.ToString()));
+
+            // todo: these are failing; do we need round-trip format "R"?
+            //Assert.Equal(double.MinValue, JsonSerializer.Parse<double>(double.MinValue.ToString()));
+            //Assert.Equal(double.MaxValue, JsonSerializer.Parse<double>(double.MaxValue.ToString()));
+            //Assert.Equal(double.MinValue, JsonSerializer.Parse<double?>(double.MinValue.ToString()));
+            //Assert.Equal(double.MaxValue, JsonSerializer.Parse<double?>(double.MaxValue.ToString()));
+        }
+
+        [Fact]
+        public static void ValueFail()
+        {
+            string unexpectedString = @"""unexpected string""";
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<byte>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<byte>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<byte?>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<byte?>(unexpectedString));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<sbyte>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<sbyte>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<sbyte?>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<sbyte?>(unexpectedString));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<short>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<short>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<short?>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<short?>(unexpectedString));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ushort>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ushort>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ushort?>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ushort?>(unexpectedString));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<float>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<float>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<float?>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<float?>(unexpectedString));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int?>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int?>(unexpectedString));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<uint>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<uint>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<uint?>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<uint?>(unexpectedString));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<long>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<long>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<long?>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<long?>(unexpectedString));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ulong>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ulong>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ulong?>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<ulong?>(unexpectedString));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<decimal>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<decimal>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<decimal?>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<decimal?>(unexpectedString));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<double>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<double>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<double?>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<double?>(unexpectedString));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<DateTime>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<DateTime>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<DateTime?>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<DateTime?>(unexpectedString));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<string>("1"));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<string>("1"));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<char>("1"));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<char>("1"));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<char?>("1"));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<char?>("1"));
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<Enum>(unexpectedString));
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<Enum>(unexpectedString));
+        }
+
+        [Fact]
+        public static void ReadObjectArray()
+        {
+            string data =
+                "[" +
+                SimpleTestClass.s_json +
+                "," +
+                SimpleTestClass.s_json +
+                "]";
+
+            SimpleTestClass[] i = JsonSerializer.Parse<SimpleTestClass[]>(Encoding.UTF8.GetBytes(data));
+
+            i[0].Verify();
+            i[1].Verify();
+        }
+
+        [Fact]
+        public static void ReadPrimitiveJaggedArray()
+        {
+            int[][] i = JsonSerializer.Parse<int[][]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            Assert.Equal(1, i[0][0]);
+            Assert.Equal(2, i[0][1]);
+            Assert.Equal(3, i[1][0]);
+            Assert.Equal(4, i[1][1]);
+        }
+
+        [Fact]
+        public static void ReadListOfList()
+        {
+            List<List<int>> result = JsonSerializer.Parse<List<List<int>>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+
+            Assert.Equal(1, result[0][0]);
+            Assert.Equal(2, result[0][1]);
+            Assert.Equal(3, result[1][0]);
+            Assert.Equal(4, result[1][1]);
+        }
+
+        [Fact]
+        public static void ReadListOfArray()
+        {
+            List<int[]> result = JsonSerializer.Parse<List<int[]>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+
+            Assert.Equal(1, result[0][0]);
+            Assert.Equal(2, result[0][1]);
+            Assert.Equal(3, result[1][0]);
+            Assert.Equal(4, result[1][1]);
+        }
+
+        [Fact]
+        public static void ReadArrayOfList()
+        {
+            List<int>[] result = JsonSerializer.Parse<List<int>[]> (Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+
+            Assert.Equal(1, result[0][0]);
+            Assert.Equal(2, result[0][1]);
+            Assert.Equal(3, result[1][0]);
+            Assert.Equal(4, result[1][1]);
+        }
+
+        [Fact]
+        public static void ReadPrimitiveList()
+        {
+            List<int> i = JsonSerializer.Parse<List<int>>(Encoding.UTF8.GetBytes(@"[1,2]"));
+            Assert.Equal(1, i[0]);
+            Assert.Equal(2, i[1]);
+        }
+
+        public class TestClassWithBadData
+        {
+            public TestChildClassWithBadData[] Children { get; set; }
+        }
+
+        public class TestChildClassWithBadData
+        {
+            public int MyProperty { get; set; }
+        }
+
+        [Fact]
+        public static void ReadConversionFails()
+        {
+            byte[] data = Encoding.UTF8.GetBytes(
+                @"{" +
+                    @"""Children"":[" +
+                        @"{""MyProperty"":""StringButShouldBeInt""}" +
+                    @"]" +
+                @"}");
+
+            bool exceptionThrown = false;
+
+            try
+            {
+                JsonSerializer.Parse<TestClassWithBadData>(data);
+            }
+            catch (JsonReaderException exception)
+            {
+                exceptionThrown = true;
+
+                // Exception should contain property path.
+                Assert.True(exception.ToString().Contains("[System.Text.Json.Serialization.Tests.ValueTests+TestClassWithBadData].Children.MyProperty"));
+            }
+
+            Assert.True(exceptionThrown);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.cs
new file mode 100644 (file)
index 0000000..32a1974
--- /dev/null
@@ -0,0 +1,143 @@
+// 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.
+
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public static partial class ValueTests
+    {
+        [Fact]
+        public static void WritePrimitives()
+        {
+            {
+                string json = JsonSerializer.ToString(1);
+                Assert.Equal("1", json);
+            }
+
+            {
+                Span<byte> json = JsonSerializer.ToBytes(1);
+                Assert.Equal(Encoding.UTF8.GetBytes("1"), json.ToArray());
+            }
+
+            {
+                string json = JsonSerializer.ToString(long.MaxValue);
+                Assert.Equal(long.MaxValue.ToString(), json);
+            }
+
+            {
+                Span<byte> json = JsonSerializer.ToBytes(long.MaxValue);
+                Assert.Equal(Encoding.UTF8.GetBytes(long.MaxValue.ToString()), json.ToArray());
+            }
+
+            {
+                string json = JsonSerializer.ToString("Hello");
+                Assert.Equal(@"""Hello""", json);
+            }
+
+            {
+                Span<byte> json = JsonSerializer.ToBytes("Hello");
+                Assert.Equal(Encoding.UTF8.GetBytes(@"""Hello"""), json.ToArray());
+            }
+        }
+
+        [Fact]
+        public static void WritePrimitiveArray()
+        {
+            var input = new int[] { 0, 1 };
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[0,1]", json);
+        }
+
+        [Fact]
+        public static void WriteArrayWithEnums()
+        {
+            var input = new SampleEnum[] { SampleEnum.One, SampleEnum.Two };
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[1,2]", json);
+        }
+
+        [Fact]
+        public static void WriteObjectArray()
+        {
+            string json;
+
+            {
+                SimpleTestClass[] input = new SimpleTestClass[] { new SimpleTestClass(), new SimpleTestClass() };
+                input[0].Initialize();
+                input[0].Verify();
+
+                input[1].Initialize();
+                input[1].Verify();
+
+                json = JsonSerializer.ToString(input);
+            }
+
+            {
+                SimpleTestClass[] output = JsonSerializer.Parse<SimpleTestClass[]>(json);
+                Assert.Equal(2, output.Length);
+                output[0].Verify();
+                output[1].Verify();
+            }
+        }
+
+        [Fact]
+        public static void WritePrimitiveJaggedArray()
+        {
+            var input = new int[2][];
+            input[0] = new int[] { 1, 2 };
+            input[1] = new int[] { 3, 4 };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WriteListOfList()
+        {
+            var input = new List<List<int>>
+            {
+                new List<int>() { 1, 2 },
+                new List<int>() { 3, 4 }
+            };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WriteListOfArray()
+        {
+            var input = new List<int[]>
+            {
+                new int[] { 1, 2 },
+                new int[] { 3, 4 }
+            };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WriteArrayOfList()
+        {
+            var input = new List<int>[2];
+            input[0] = new List<int>() { 1, 2 };
+            input[1] = new List<int>() { 3, 4 };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WritePrimitiveList()
+        {
+            var input = new List<int> { 1, 2 };
+
+            string json = JsonSerializer.ToString(input);
+            Assert.Equal("[1,2]", json);
+        }
+    }
+}
index 7771966..57737f0 100644 (file)
@@ -2,6 +2,10 @@
   <PropertyGroup>
     <ProjectGuid>{5F553243-042C-45C0-8E49-C739131E11C3}</ProjectGuid>
     <Configurations>netcoreapp-Debug;netcoreapp-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;netstandard-Debug;netstandard-Release</Configurations>
+    <!-- For the inbox library (that is shipping with the product), this should always be true. -->
+    <!-- BUILDING_INBOX_LIBRARY is only false when building for netstandard to validate that the sources are netstandard compatible. -->
+    <!-- This is meant to help with producing a source package and not to ship a netstandard compatible binary. -->
+    <DefineConstants Condition="'$(TargetsNETStandard)' != 'true'">$(DefineConstants);BUILDING_INBOX_LIBRARY</DefineConstants>
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="$(CommonTestPath)\System\IO\WrappedMemoryStream.cs">
     <Compile Include="JsonTestHelper.cs" />
     <Compile Include="JsonWriterStateTests.cs" />
     <Compile Include="ResizableArray.cs" />
+    <Compile Include="Serialization\Array.ReadTests.cs" />
+    <Compile Include="Serialization\Array.WriteTests.cs" />
+    <Compile Include="Serialization\CyclicTests.cs" />
+    <Compile Include="Serialization\EnumTests.cs" />
+    <Compile Include="Serialization\Null.ReadTests.cs" />
+    <Compile Include="Serialization\Null.WriteTests.cs" />
+    <Compile Include="Serialization\PolymorphicTests.cs" />
+    <Compile Include="Serialization\PropertyVisibilityTests.cs" />
+    <Compile Include="Serialization\SpanTests.cs" />
+    <Compile Include="Serialization\Stream.ReadTests.cs" />
+    <Compile Include="Serialization\Stream.WriteTests.cs" />
+    <Compile Include="Serialization\String.ReadTests.cs" />
+    <Compile Include="Serialization\String.WriteTests.cs" />
+    <Compile Include="Serialization\TestClasses.cs" />
+    <Compile Include="Serialization\TestClasses.Polymorphic.cs" />
+    <Compile Include="Serialization\TestData.cs" />
+    <Compile Include="Serialization\Value.ReadTests.cs" />
+    <Compile Include="Serialization\Value.WriteTests.cs" />
     <Compile Include="TestCaseType.cs" />
     <Compile Include="Utf8JsonReaderTests.cs" />
     <Compile Include="Utf8JsonReaderTests.MultiSegment.cs" />