JsonSerializerOptions API update and ignore property features (dotnet/corefx#36776)
authorSteve Harter <steveharter@users.noreply.github.com>
Sat, 13 Apr 2019 22:32:49 +0000 (15:32 -0700)
committerGitHub <noreply@github.com>
Sat, 13 Apr 2019 22:32:49 +0000 (15:32 -0700)
Commit migrated from https://github.com/dotnet/corefx/commit/b06250eb9ef50bca50cf7676e87753dbd8b5470a

34 files changed:
src/libraries/System.Text.Json/ref/System.Text.Json.cs
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/Reader/JsonReaderOptions.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonAttribute.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonIgnoreAttribute.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Helpers.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs
src/libraries/System.Text.Json/tests/Serialization/Array.WriteTests.cs
src/libraries/System.Text.Json/tests/Serialization/Null.ReadTests.cs
src/libraries/System.Text.Json/tests/Serialization/Null.WriteTests.cs
src/libraries/System.Text.Json/tests/Serialization/Object.ReadTests.cs
src/libraries/System.Text.Json/tests/Serialization/OptionsTests.cs
src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs
src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs
src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.cs
src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.cs

index aff6e05..b4e1103 100644 (file)
@@ -118,9 +118,9 @@ namespace System.Text.Json
     public partial struct JsonReaderOptions
     {
         private int _dummyPrimitive;
+        public bool AllowTrailingCommas { get { throw null; } set { } }
         public System.Text.Json.JsonCommentHandling CommentHandling { get { throw null; } set { } }
         public int MaxDepth { get { throw null; } set { } }
-        public bool AllowTrailingCommas { get { throw null; } set { } }
     }
     public partial struct JsonReaderState
     {
@@ -311,6 +311,15 @@ namespace System.Text.Json
 }
 namespace System.Text.Json.Serialization
 {
+    public abstract partial class JsonAttribute : System.Attribute
+    {
+        protected JsonAttribute() { }
+    }
+    [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false)]
+    public sealed partial class JsonIgnoreAttribute : System.Text.Json.Serialization.JsonAttribute
+    {
+        public JsonIgnoreAttribute() { }
+    }
     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; }
@@ -329,10 +338,12 @@ namespace System.Text.Json.Serialization
     public sealed partial class JsonSerializerOptions
     {
         public JsonSerializerOptions() { }
+        public bool AllowTrailingCommas { get { throw null; } set { } }
         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 { } }
+        public bool IgnoreNullValues { get { throw null; } set { } }
+        public bool IgnoreReadOnlyProperties { get { throw null; } set { } }
+        public int MaxDepth { get { throw null; } set { } }
+        public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } }
+        public bool WriteIndented { get { throw null; } set { } }
     }
 }
index b83207b..eb4d7ec 100644 (file)
   <data name="TrailingCommaNotAllowedBeforeObjectEnd" xml:space="preserve">
     <value>The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options.</value>
   </data>
+  <data name="SerializerOptionsImmutable" xml:space="preserve">
+    <value>Serializer options cannot be changed once serialization or deserialization has occurred.</value>
+  </data>
 </root>
\ No newline at end of file
index b22566c..9dc525a 100644 (file)
     <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt16.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt32.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt64.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonAttribute.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\JsonIgnoreAttribute.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonPropertyInfo.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonPropertyInfoCommon.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonPropertyInfoNotNullable.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonPropertyInfoNullable.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleArray.cs" />
+    <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Helpers.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" />
index 7ef94d8..e5d1b83 100644 (file)
@@ -15,7 +15,7 @@ namespace System.Text.Json
 
         /// <summary>
         /// Defines how the <see cref="Utf8JsonReader"/> should handle comments when reading through the JSON.
-        /// By default the reader will throw a <exception cref="JsonReaderException"/> if it encounters a comment.
+        /// By default <exception cref="JsonReaderException"/> is thrown if a comment is encountered.
         /// </summary>
         public JsonCommentHandling CommentHandling { get; set; }
 
@@ -37,8 +37,8 @@ namespace System.Text.Json
 
         /// <summary>
         /// Defines whether an extra comma at the end of a list of JSON values in an object or array
-        /// are allowed (and ignored) within the JSON payload being read.
-        /// By default, it's set to false, and the reader will throw a <exception cref="JsonReaderException"/> if it encounters a trailing comma.
+        /// is allowed (and ignored) within the JSON payload being read.
+        /// By default, it's set to false, and <exception cref="JsonReaderException"/> is thrown if a trailing comma is encountered.
         /// </summary>
         public bool AllowTrailingCommas { get; set; }
     }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonAttribute.cs
new file mode 100644 (file)
index 0000000..8b4ca3d
--- /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
+{
+    /// <summary>
+    /// The base class of serialization attributes.
+    /// </summary>
+    public abstract class JsonAttribute : Attribute { }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonIgnoreAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonIgnoreAttribute.cs
new file mode 100644 (file)
index 0000000..8644189
--- /dev/null
@@ -0,0 +1,15 @@
+// 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>
+    /// Prevents a property from being serialized or deserialized.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
+    public sealed class JsonIgnoreAttribute : JsonAttribute
+    {
+        public JsonIgnoreAttribute() { }
+    }
+}
index bcd3037..a43d1d1 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections;
+using System.Diagnostics;
 using System.Reflection;
 using System.Text.Json.Serialization.Converters;
 using System.Text.Json.Serialization.Policies;
@@ -11,6 +12,10 @@ namespace System.Text.Json.Serialization
 {
     internal abstract class JsonPropertyInfo
     {
+        // Cache the array and enumerable converters so they don't get created for every enumerable property.
+        private static readonly JsonEnumerableConverter s_jsonArrayConverter = new DefaultArrayConverter();
+        private static readonly JsonEnumerableConverter s_jsonEnumerableConverter = new DefaultEnumerableConverter();
+
         internal ClassType ClassType;
 
         internal byte[] _name = default;
@@ -18,8 +23,11 @@ namespace System.Text.Json.Serialization
 
         internal bool HasGetter { get; set; }
         internal bool HasSetter { get; set; }
+        internal bool ShouldSerialize { get; private set; }
+        internal bool ShouldDeserialize { get; private set; }
+
+        internal bool IgnoreNullValues { get; private set; }
 
-        public ReadOnlySpan<byte> EscapedName => _escapedName;
         public ReadOnlySpan<byte> Name => _name;
 
         // todo: to minimize hashtable lookups, cache JsonClassInfo:
@@ -43,6 +51,7 @@ namespace System.Text.Json.Serialization
             ClassType = JsonClassInfo.GetClassType(runtimePropertyType);
             if (elementType != null)
             {
+                Debug.Assert(ClassType == ClassType.Enumerable);
                 ElementClassInfo = options.GetOrAddClass(elementType);
             }
 
@@ -66,31 +75,80 @@ namespace System.Text.Json.Serialization
 
         internal virtual void GetPolicies(JsonSerializerOptions options)
         {
-            if (RuntimePropertyType.IsArray)
+            DetermineSerializationCapabilities(options);
+            IgnoreNullValues = options.IgnoreNullValues;
+        }
+
+        private void DetermineSerializationCapabilities(JsonSerializerOptions options)
+        {
+            bool hasIgnoreAttribute = (GetAttribute<JsonIgnoreAttribute>() != null);
+
+            if (hasIgnoreAttribute)
             {
-                EnumerableConverter = new DefaultArrayConverter();
+                // We don't serialize or deserialize.
+                return;
             }
-            else if (typeof(IEnumerable).IsAssignableFrom(RuntimePropertyType))
+
+            if (ClassType != ClassType.Enumerable)
             {
-                Type elementType = JsonClassInfo.GetElementType(RuntimePropertyType);
+                // We serialize if there is a getter + no [Ignore] attribute + not ignoring readonly properties.
+                ShouldSerialize = HasGetter && (HasSetter || !options.IgnoreReadOnlyProperties);
 
-                if (RuntimePropertyType.IsAssignableFrom(typeof(JsonEnumerableT<>).MakeGenericType(elementType)))
+                // We deserialize if there is a setter + no [Ignore] attribute. 
+                ShouldDeserialize = HasSetter;
+            }
+            else
+            {
+                if (HasGetter)
                 {
-                    EnumerableConverter = new DefaultEnumerableConverter();
+                    if (HasSetter)
+                    {
+                        ShouldDeserialize = true;
+                    }
+                    else if (RuntimePropertyType.IsAssignableFrom(typeof(IList)))
+                    {
+                        ShouldDeserialize = true;
+                    }
+                    //else
+                    //{
+                    //    // todo: future feature that allows non-List types (e.g. from System.Collections.Immutable) to have converters.
+                    //}
+                }
+                //else if (HasSetter)
+                //{
+                //    // todo: Special case where there is no getter but a setter (and an EnumerableConverter)
+                //}
+
+                if (ShouldDeserialize)
+                {
+                    ShouldSerialize = HasGetter;
+
+                    if (RuntimePropertyType.IsArray)
+                    {
+                        EnumerableConverter = s_jsonArrayConverter;
+                    }
+                    else if (typeof(IEnumerable).IsAssignableFrom(RuntimePropertyType))
+                    {
+                        Type elementType = JsonClassInfo.GetElementType(RuntimePropertyType);
+
+                        if (RuntimePropertyType.IsAssignableFrom(typeof(JsonEnumerableT<>).MakeGenericType(elementType)))
+                        {
+                            EnumerableConverter = s_jsonEnumerableConverter;
+                        }
+                    }
+                }
+                else
+                {
+                    ShouldSerialize = HasGetter && !options.IgnoreReadOnlyProperties;
                 }
             }
         }
 
         internal abstract object GetValueAsObject(object obj, JsonSerializerOptions options);
 
-        internal bool IgnoreNullPropertyValueOnRead(JsonSerializerOptions options)
-        {
-            return options.IgnoreNullPropertyValueOnRead;
-        }
-
-        internal bool IgnoreNullPropertyValueOnWrite(JsonSerializerOptions options)
+        internal TAttribute GetAttribute<TAttribute>() where TAttribute : Attribute
         {
-            return options.IgnoreNullPropertyValueOnWrite;
+            return (TAttribute)PropertyInfo?.GetCustomAttribute(typeof(TAttribute), inherit: false);
         }
 
         internal abstract void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader);
index a90b880..35efe45 100644 (file)
@@ -78,7 +78,7 @@ namespace System.Text.Json.Serialization
             Debug.Assert(Set != null);
             TDeclaredProperty typedValue = (TDeclaredProperty)value;
 
-            if (typedValue != null || !IgnoreNullPropertyValueOnWrite(options))
+            if (typedValue != null || !IgnoreNullValues)
             {
                 Set((TClass)obj, (TDeclaredProperty)value);
             }
index 8a17d00..c321937 100644 (file)
@@ -36,7 +36,7 @@ namespace System.Text.Json.Serialization
                 JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
                 propertyInfo.ReadEnumerable(tokenType, options, ref state, ref reader);
             }
-            else if (HasSetter)
+            else if (ShouldDeserialize)
             {
                 if (ValueConverter != null)
                 {
@@ -48,10 +48,10 @@ namespace System.Text.Json.Serialization
                         }
                         else
                         {
-                            if (value != null || !IgnoreNullPropertyValueOnRead(options))
-                            {
-                                Set((TClass)state.Current.ReturnValue, value);
-                            }
+                            // Null values were already handled.
+                            Debug.Assert(value != null);
+
+                            Set((TClass)state.Current.ReturnValue, value);
                         }
 
                         return;
@@ -85,7 +85,7 @@ namespace System.Text.Json.Serialization
                 JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
                 propertyInfo.WriteEnumerable(options, ref current, ref writer);
             }
-            else if (HasGetter)
+            else if (ShouldSerialize)
             {
                 TRuntimeProperty value;
                 if (_isPropertyPolicy)
@@ -103,7 +103,7 @@ namespace System.Text.Json.Serialization
                     {
                         writer.WriteNullValue();
                     }
-                    else if (!IgnoreNullPropertyValueOnWrite(options))
+                    else if (!IgnoreNullValues)
                     {
                         writer.WriteNull(_escapedName);
                     }
index 06c6473..d387d90 100644 (file)
@@ -36,7 +36,7 @@ namespace System.Text.Json.Serialization
                 JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
                 propertyInfo.ReadEnumerable(tokenType, options, ref state, ref reader);
             }
-            else if (HasSetter)
+            else if (ShouldDeserialize)
             {
                 if (ValueConverter != null)
                 {
@@ -82,7 +82,7 @@ namespace System.Text.Json.Serialization
                 JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
                 propertyInfo.WriteEnumerable(options, ref current, ref writer);
             }
-            else if (HasGetter)
+            else if (ShouldSerialize)
             {
                 TProperty? value;
                 if (_isPropertyPolicy)
@@ -100,7 +100,7 @@ namespace System.Text.Json.Serialization
                     {
                         writer.WriteNullValue();
                     }
-                    else if (!IgnoreNullPropertyValueOnWrite(options))
+                    else if (!IgnoreNullValues)
                     {
                         writer.WriteNull(_escapedName);
                     }
index 5035e4e..4f4c30c 100644 (file)
@@ -15,7 +15,10 @@ namespace System.Text.Json.Serialization
             ref Utf8JsonReader reader,
             ref ReadStack state)
         {
-            if (state.Current.Skip())
+            JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
+
+            bool skip = jsonPropertyInfo != null && !jsonPropertyInfo.ShouldDeserialize;
+            if (skip || state.Current.Skip())
             {
                 // The array is not being applied to the object.
                 state.Push();
@@ -23,7 +26,6 @@ namespace System.Text.Json.Serialization
                 return;
             }
 
-            JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
             if (jsonPropertyInfo == null || state.Current.JsonClassInfo.ClassType == ClassType.Unknown)
             {
                 jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options);
@@ -53,8 +55,10 @@ namespace System.Text.Json.Serialization
                     state.Current.EnumerableCreated = true;
                 }
 
+                jsonPropertyInfo = state.Current.JsonPropertyInfo;
+
                 // If current property is already set (from a constructor, for example) leave as-is
-                if (state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue, options) == null)
+                if (jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue, options) == null)
                 {
                     // Create the enumerable.
                     object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state, options);
index 16c07e5..ace0c71 100644 (file)
@@ -41,7 +41,7 @@ namespace System.Text.Json.Serialization
                 return true;
             }
 
-            if (!propertyInfo.IgnoreNullPropertyValueOnRead(options))
+            if (!propertyInfo.IgnoreNullValues)
             {
                 state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null, options);
             }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Helpers.cs
new file mode 100644 (file)
index 0000000..7250c80
--- /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.
+
+namespace System.Text.Json.Serialization
+{
+    public static partial class JsonSerializer
+    {
+        private static object ReadCore(
+            Type returnType,
+            JsonSerializerOptions options,
+            ref Utf8JsonReader reader)
+        {
+            options ??= JsonSerializerOptions.s_defaultOptions;
+
+            ReadStack state = default;
+            state.Current.Initialize(returnType, options);
+
+            ReadCore(options, ref reader, ref state);
+
+            return state.Current.ReturnValue;
+        }
+    }
+}
index c77c1cb..e6ffb2c 100644 (file)
@@ -48,10 +48,9 @@ namespace System.Text.Json.Serialization
 
         private static object ParseCore(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerOptions options)
         {
-            if (options == null)
-                options = s_defaultSettings;
+            options ??= JsonSerializerOptions.s_defaultOptions;
 
-            var readerState = new JsonReaderState(options: options.ReaderOptions);
+            var readerState = new JsonReaderState(options.GetReaderOptions());
             var reader = new Utf8JsonReader(utf8Json, isFinalBlock: true, readerState);
             object result = ReadCore(returnType, options, ref reader);
 
index d44faac..402bfd1 100644 (file)
@@ -78,12 +78,12 @@ namespace System.Text.Json.Serialization
             JsonSerializerOptions options = null,
             CancellationToken cancellationToken = default)
         {
-            options ??= s_defaultSettings;
+            options ??= JsonSerializerOptions.s_defaultOptions;
 
             ReadStack state = default;
             state.Current.Initialize(returnType, options);
 
-            var readerState = new JsonReaderState(options.ReaderOptions);
+            var readerState = new JsonReaderState(options.GetReaderOptions());
 
             // todo: switch to ArrayBuffer implementation to handle and simplify the allocs?
             byte[] buffer = ArrayPool<byte>.Shared.Rent(options.DefaultBufferSize);
index 095a394..ef4d869 100644 (file)
@@ -62,12 +62,11 @@ namespace System.Text.Json.Serialization
 
         private static object ParseCore(string json, Type returnType, JsonSerializerOptions options = null)
         {
-            if (options == null)
-                options = s_defaultSettings;
+            options ??= JsonSerializerOptions.s_defaultOptions;
 
             // todo: use an array pool here for smaller requests to avoid the alloc?
             byte[] jsonBytes = JsonReaderHelper.s_utf8Encoding.GetBytes(json);
-            var readerState = new JsonReaderState(options: options.ReaderOptions);
+            var readerState = new JsonReaderState(options.GetReaderOptions());
             var reader = new Utf8JsonReader(jsonBytes, isFinalBlock: true, readerState);
             object result = ReadCore(returnType, options, ref reader);
 
index 295861e..0bad2a0 100644 (file)
@@ -14,23 +14,6 @@ namespace System.Text.Json.Serialization
     public static partial class JsonSerializer
     {
         internal static readonly JsonPropertyInfo s_missingProperty = new JsonPropertyInfoNotNullable<object, 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;
-            state.Current.Initialize(returnType, options);
-
-            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(
index ef65d72..ea02e71 100644 (file)
@@ -26,6 +26,11 @@ namespace System.Text.Json.Serialization
             Debug.Assert(state.Current.JsonPropertyInfo.ClassType == ClassType.Enumerable);
 
             JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
+            if (!jsonPropertyInfo.ShouldSerialize)
+            {
+                // Ignore writing this property.
+                return true;
+            }
 
             if (state.Current.Enumerator == null)
             {
index beec923..ee4b796 100644 (file)
@@ -114,7 +114,7 @@ namespace System.Text.Json.Serialization
             }
             else
             {
-                if (!jsonPropertyInfo.IgnoreNullPropertyValueOnWrite(options))
+                if (!jsonPropertyInfo.IgnoreNullValues)
                 {
                     writer.WriteNull(jsonPropertyInfo._escapedName);
                 }
index d0c9b74..f0b62d7 100644 (file)
@@ -66,8 +66,7 @@ namespace System.Text.Json.Serialization
 
         private static byte[] WriteCoreBytes(object value, Type type, JsonSerializerOptions options)
         {
-            if (options == null)
-                options = s_defaultSettings;
+            options ??= JsonSerializerOptions.s_defaultOptions;
 
             byte[] result;
 
@@ -82,9 +81,7 @@ namespace System.Text.Json.Serialization
 
         private static string WriteCoreString(object value, Type type, JsonSerializerOptions options)
         {
-            if (options == null)
-                options = s_defaultSettings;
-
+            options ??= JsonSerializerOptions.s_defaultOptions;
             string result;
 
             using (var output = new ArrayBufferWriter<byte>(options.DefaultBufferSize))
@@ -100,7 +97,7 @@ namespace System.Text.Json.Serialization
         {
             Debug.Assert(type != null || value == null);
 
-            var writerState = new JsonWriterState(options.WriterOptions);
+            var writerState = new JsonWriterState(options.GetWriterOptions());
             var writer = new Utf8JsonWriter(output, writerState);
 
             if (value == null)
index 2990306..2889d3f 100644 (file)
@@ -45,10 +45,9 @@ namespace System.Text.Json.Serialization
 
         private static async Task WriteAsyncCore(object value, Type type, Stream utf8Json, JsonSerializerOptions options, CancellationToken cancellationToken)
         {
-            if (options == null)
-                options = s_defaultSettings;
+            options ??= JsonSerializerOptions.s_defaultOptions;
 
-            var writerState = new JsonWriterState(options.WriterOptions);
+            var writerState = new JsonWriterState(options.GetWriterOptions());
 
             using (var bufferWriter = new ArrayBufferWriter<byte>(options.DefaultBufferSize))
             {
index e8230a8..f10a0db 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Concurrent;
+using System.Diagnostics;
 
 namespace System.Text.Json.Serialization
 {
@@ -13,43 +14,53 @@ namespace System.Text.Json.Serialization
     {
         internal const int BufferSizeDefault = 16 * 1024;
 
+        internal static readonly JsonSerializerOptions s_defaultOptions = new JsonSerializerOptions();
+
+        private readonly ConcurrentDictionary<Type, JsonClassInfo> _classes = new ConcurrentDictionary<Type, JsonClassInfo>();
         private ClassMaterializer _classMaterializerStrategy;
+        private JsonCommentHandling _readCommentHandling;
         private int _defaultBufferSize = BufferSizeDefault;
-
-        private static readonly ConcurrentDictionary<Type, JsonClassInfo> s_classes = new ConcurrentDictionary<Type, JsonClassInfo>();
+        private int _maxDepth;
+        private bool _allowTrailingCommas;
+        private bool _haveTypesBeenCreated;
+        private bool _ignoreNullValues;
+        private bool _ignoreReadOnlyProperties;
+        private bool _writeIndented;
 
         /// <summary>
         /// Constructs a new <see cref="JsonSerializerOptions"/> instance.
         /// </summary>
         public JsonSerializerOptions() { }
 
-        internal JsonClassInfo GetOrAddClass(Type classType)
+        /// <summary>
+        /// Defines whether an extra comma at the end of a list of JSON values in an object or array
+        /// is allowed (and ignored) within the JSON payload being read.
+        /// By default, it's set to false, and <exception cref="JsonReaderException"/> is thrown if a trailing comma is encountered.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this property is set after serialization or deserialization has occurred.
+        /// </exception>
+        public bool AllowTrailingCommas
         {
-            JsonClassInfo result;
-
-            if (!s_classes.TryGetValue(classType, out result))
+            get
             {
-                result = s_classes.GetOrAdd(classType, new JsonClassInfo(classType, this));
+                return _allowTrailingCommas;
+            }
+            set
+            {
+                VerifyMutable();
+                _allowTrailingCommas = value;
             }
-
-            return result;
         }
 
         /// <summary>
-        /// Options to control the <see cref="Utf8JsonReader"/>.
-        /// </summary>
-        public JsonReaderOptions ReaderOptions { get; set; }
-
-        /// <summary>
-        /// Options to control the <see cref="Utf8JsonWriter"/>.
-        /// </summary>
-        public JsonWriterOptions WriterOptions { get; set; }
-
-        /// <summary>
         /// The default buffer size in bytes used when creating temporary buffers.
         /// </summary>
         /// <remarks>The default size is 16K.</remarks>
         /// <exception cref="System.ArgumentException">Thrown when the buffer size is less than 1.</exception>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this property is set after serialization or deserialization has occurred.
+        /// </exception>
         public int DefaultBufferSize
         {
             get
@@ -58,6 +69,8 @@ namespace System.Text.Json.Serialization
             }
             set
             {
+                VerifyMutable();
+
                 if (value < 1)
                 {
                     throw new ArgumentException(SR.SerializationInvalidBufferSize);
@@ -68,14 +81,106 @@ namespace System.Text.Json.Serialization
         }
 
         /// <summary>
-        /// Determines whether null values of properties are ignored or whether they are written to the JSON.
+        /// Determines whether null values are ignored during serialization and deserialization.
+        /// The default value is false.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this property is set after serialization or deserialization has occurred.
+        /// </exception>
+        public bool IgnoreNullValues
+        {
+            get
+            {
+                return _ignoreNullValues;
+            }
+            set
+            {
+                VerifyMutable();
+                _ignoreNullValues = value;
+            }
+        }
+
+        /// <summary>
+        /// Determines whether read-only properties are ignored during serialization and deserialization.
+        /// A property is read-only if it contains a public getter but not a public setter.
+        /// The default value is false.
         /// </summary>
-        public bool IgnoreNullPropertyValueOnWrite { get; set; }
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this property is set after serialization or deserialization has occurred.
+        /// </exception>
+        public bool IgnoreReadOnlyProperties
+        {
+            get
+            {
+                return _ignoreReadOnlyProperties;
+            }
+            set
+            {
+                VerifyMutable();
+                _ignoreReadOnlyProperties = value;
+            }
+        }
 
         /// <summary>
-        /// Determines whether null values in the JSON are ignored or whether they are set on properties.
+        /// Gets or sets the maximum depth allowed when reading or writing JSON, with the default (i.e. 0) indicating a max depth of 64.
+        /// Reading past this depth will throw a <exception cref="JsonReaderException"/>.
         /// </summary>
-        public bool IgnoreNullPropertyValueOnRead { get; set; }
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this property is set after serialization or deserialization has occurred.
+        /// </exception>
+        public int MaxDepth
+        {
+            get
+            {
+                return _maxDepth;
+            }
+            set
+            {
+                VerifyMutable();
+                _maxDepth = value;
+            }
+        }
+
+        /// <summary>
+        /// Defines how the comments are handled during deserialization.
+        /// By default <exception cref="JsonReaderException"/> is thrown if a comment is encountered.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this property is set after serialization or deserialization has occurred.
+        /// </exception>
+        public JsonCommentHandling ReadCommentHandling
+        {
+            get
+            {
+                return _readCommentHandling;
+            }
+            set
+            {
+                VerifyMutable();
+                _readCommentHandling = value;
+            }
+        }
+
+        /// <summary>
+        /// Defines whether JSON should pretty print which includes:
+        /// indenting nested JSON tokens, adding new lines, and adding white space between property names and values.
+        /// By default, the JSON is written without any extra white space.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// Thrown if this property is set after serialization or deserialization has occurred.
+        /// </exception>
+        public bool WriteIndented
+        {
+            get
+            {
+                return _writeIndented;
+            }
+            set
+            {
+                VerifyMutable();
+                _writeIndented = value;
+            }
+        }
 
         internal ClassMaterializer ClassMaterializerStrategy
         {
@@ -94,5 +199,47 @@ namespace System.Text.Json.Serialization
                 return _classMaterializerStrategy;
             }
         }
+
+        internal JsonClassInfo GetOrAddClass(Type classType)
+        {
+            _haveTypesBeenCreated = true;
+
+            // todo: for performance, consider obtaining the type from s_defaultOptions and then cloning.
+            if (!_classes.TryGetValue(classType, out JsonClassInfo result))
+            {
+                result = _classes.GetOrAdd(classType, new JsonClassInfo(classType, this));
+            }
+
+            return result;
+        }
+
+        internal JsonReaderOptions GetReaderOptions()
+        {
+            return new JsonReaderOptions
+            {
+                AllowTrailingCommas = AllowTrailingCommas,
+                CommentHandling = ReadCommentHandling,
+                MaxDepth = MaxDepth
+            };
+        }
+
+        internal JsonWriterOptions GetWriterOptions()
+        {
+            return new JsonWriterOptions
+            {
+                Indented = WriteIndented
+            };
+        }
+
+        private void VerifyMutable()
+        {
+            // The default options are hidden and thus should be immutable.
+            Debug.Assert(this != s_defaultOptions);
+
+            if (_haveTypesBeenCreated)
+            {
+                ThrowHelper.ThrowInvalidOperationException_SerializerOptionsImmutable();
+            }
+        }
     }
 }
index 6bd5fc1..7c9fbab 100644 (file)
@@ -22,12 +22,6 @@ namespace System.Text.Json
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowJsonReaderException_DeserializeUnableToConvertValue(Type propertyType, in ReadStack state)
-        {
-            throw new JsonReaderException(SR.Format(SR.DeserializeUnableToConvertValue, state.PropertyPath, propertyType), 0 , 0);
-        }
-
-        [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);
@@ -38,5 +32,11 @@ namespace System.Text.Json
         {
             throw new ObjectDisposedException(name);
         }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static void ThrowInvalidOperationException_SerializerOptionsImmutable()
+        {
+            throw new InvalidOperationException(SR.SerializerOptionsImmutable);
+        }
     }
 }
index a07b4d0..b606976 100644 (file)
@@ -2,6 +2,7 @@
 // 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
@@ -9,6 +10,16 @@ namespace System.Text.Json.Serialization.Tests
     public static partial class ArrayTests
     {
         [Fact]
+        public static void ReadEmpty()
+        {
+            SimpleTestClass[] arr = JsonSerializer.Parse<SimpleTestClass[]>("[]");
+            Assert.Equal(0, arr.Length);
+
+            List<SimpleTestClass> list = JsonSerializer.Parse<List<SimpleTestClass>>("[]");
+            Assert.Equal(0, list.Count);
+        }
+
+        [Fact]
         public static void ReadClassWithStringArray()
         {
             TestClassWithStringArray obj = JsonSerializer.Parse<TestClassWithStringArray>(TestClassWithStringArray.s_data);
index 1a63ef8..d6e0715 100644 (file)
@@ -2,6 +2,7 @@
 // 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
@@ -9,6 +10,16 @@ namespace System.Text.Json.Serialization.Tests
     public static partial class ArrayTests
     {
         [Fact]
+        public static void WriteEmpty()
+        {
+            string json = JsonSerializer.ToString(new SimpleTestClass[] { });
+            Assert.Equal("[]", json);
+
+            json = JsonSerializer.ToString(new List<SimpleTestClass>());
+            Assert.Equal("[]", json);
+        }
+
+        [Fact]
         public static void WriteClassWithStringArray()
         {
             string json;
index b36ad30..13bb1d5 100644 (file)
@@ -30,20 +30,20 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
-        public static void DefaultReadValue()
+        public static void DefaultIgnoreNullValuesOnRead()
         {
-            TestClassWithNullButInitialized obj = JsonSerializer.Parse<TestClassWithNullButInitialized>(TestClassWithNullButInitialized.s_json);
+            TestClassWithInitializedProperties obj = JsonSerializer.Parse<TestClassWithInitializedProperties>(TestClassWithInitializedProperties.s_null_json);
             Assert.Equal(null, obj.MyString);
             Assert.Equal(null, obj.MyInt);
         }
 
         [Fact]
-        public static void OverrideReadOnOption()
+        public static void EnableIgnoreNullValuesOnRead()
         {
             var options = new JsonSerializerOptions();
-            options.IgnoreNullPropertyValueOnRead = true;
+            options.IgnoreNullValues = true;
 
-            TestClassWithNullButInitialized obj = JsonSerializer.Parse<TestClassWithNullButInitialized>(TestClassWithNullButInitialized.s_json, options);
+            TestClassWithInitializedProperties obj = JsonSerializer.Parse<TestClassWithInitializedProperties>(TestClassWithInitializedProperties.s_null_json, options);
             Assert.Equal("Hello", obj.MyString);
             Assert.Equal(1, obj.MyInt);
         }
index 2a53ce0..8e72f1e 100644 (file)
@@ -9,21 +9,28 @@ namespace System.Text.Json.Serialization.Tests
     public static partial class NullTests
     {
         [Fact]
-        public static void DefaultWriteOptions()
+        public static void DefaultIgnoreNullValuesOnWrite()
         {
-            var input = new TestClassWithNull();
-            string json = JsonSerializer.ToString(input);
-            Assert.Equal(@"{""MyString"":null}", json);
+            var obj = new TestClassWithInitializedProperties();
+            obj.MyString = null;
+            obj.MyInt = null;
+
+            string json = JsonSerializer.ToString(obj);
+            Assert.Contains(@"""MyString"":null", json);
+            Assert.Contains(@"""MyInt"":null", json);
         }
 
         [Fact]
-        public static void OverrideWriteOnOption()
+        public static void EnableIgnoreNullValuesOnWrite()
         {
             JsonSerializerOptions options = new JsonSerializerOptions();
-            options.IgnoreNullPropertyValueOnWrite = true;
+            options.IgnoreNullValues = true;
+
+            var obj = new TestClassWithInitializedProperties();
+            obj.MyString = null;
+            obj.MyInt = null;
 
-            var input = new TestClassWithNull();
-            string json = JsonSerializer.ToString(input, options);
+            string json = JsonSerializer.ToString(obj, options);
             Assert.Equal(@"{}", json);
         }
 
index 88637d8..ae498e5 100644 (file)
@@ -16,6 +16,13 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
+        public static void ReadEmpty()
+        {
+            SimpleTestClass obj = JsonSerializer.Parse<SimpleTestClass>("{}");
+            Assert.NotNull(obj);
+        }
+
+        [Fact]
         public static void EmptyClassWithRandomData()
         {
             JsonSerializer.Parse<EmptyClass>(SimpleTestClass.s_json);
index 16031f6..35c7d25 100644 (file)
@@ -9,6 +9,30 @@ namespace System.Text.Json.Serialization.Tests
     public static partial class OptionsTests
     {
         [Fact]
+        public static void SetOptionsFail()
+        {
+            var options = new JsonSerializerOptions();
+
+            JsonSerializer.Parse<int>("1", options);
+
+            // Verify defaults and ensure getters do not throw.
+            Assert.False(options.AllowTrailingCommas);
+            Assert.Equal(16 * 1024, options.DefaultBufferSize);
+            Assert.False(options.IgnoreNullValues);
+            Assert.Equal(0, options.MaxDepth);
+            Assert.Equal(JsonCommentHandling.Disallow, options.ReadCommentHandling);
+            Assert.False(options.WriteIndented);
+
+            // Setters should throw
+            Assert.Throws<InvalidOperationException>(() => options.AllowTrailingCommas = options.AllowTrailingCommas);
+            Assert.Throws<InvalidOperationException>(() => options.DefaultBufferSize = options.DefaultBufferSize);
+            Assert.Throws<InvalidOperationException>(() => options.IgnoreNullValues = options.IgnoreNullValues);
+            Assert.Throws<InvalidOperationException>(() => options.MaxDepth = options.MaxDepth);
+            Assert.Throws<InvalidOperationException>(() => options.ReadCommentHandling = options.ReadCommentHandling);
+            Assert.Throws<InvalidOperationException>(() => options.WriteIndented = options.WriteIndented);
+        }
+
+        [Fact]
         public static void DefaultBufferSizeFail()
         {
             Assert.Throws<ArgumentException>(() => new JsonSerializerOptions().DefaultBufferSize = 0);
@@ -25,5 +49,69 @@ namespace System.Text.Json.Serialization.Tests
             options.DefaultBufferSize = 1;
             Assert.Equal(1, options.DefaultBufferSize);
         }
+
+        [Fact]
+        public static void AllowTrailingCommas()
+        {
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int[]>("[1,]"));
+
+            var options = new JsonSerializerOptions();
+            options.AllowTrailingCommas = true;
+
+            int[] value = JsonSerializer.Parse<int[]>("[1,]", options);
+            Assert.Equal(1, value[0]);
+        }
+
+        [Fact]
+        public static void WriteIndented()
+        {
+            var obj = new BasicCompany();
+            obj.Initialize();
+
+            // Verify default value.
+            string json = JsonSerializer.ToString(obj);
+            Assert.DoesNotContain(Environment.NewLine, json);
+
+            // Verify default value on options.
+            var options = new JsonSerializerOptions();
+            json = JsonSerializer.ToString(obj, options);
+            Assert.DoesNotContain(Environment.NewLine, json);
+
+            // Change the value on options.
+            options = new JsonSerializerOptions();
+            options.WriteIndented = true;
+            json = JsonSerializer.ToString(obj, options);
+            Assert.Contains(Environment.NewLine, json);
+        }
+
+        [Fact]
+        public static void ReadCommentHandling()
+        {
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<object>("/* commment */"));
+
+            var options = new JsonSerializerOptions();
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<object>("/* commment */", options));
+
+            options = new JsonSerializerOptions();
+            options.ReadCommentHandling = JsonCommentHandling.Allow;
+
+            JsonSerializer.Parse<object>("/* commment */", options);
+        }
+
+        [Fact]
+        public static void MaxDepthRead()
+        {
+            JsonSerializer.Parse<BasicCompany>(BasicCompany.s_data);
+
+            var options = new JsonSerializerOptions();
+
+            JsonSerializer.Parse<BasicCompany>(BasicCompany.s_data, options);
+
+            options = new JsonSerializerOptions();
+            options.MaxDepth = 1;
+
+            Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<BasicCompany>(BasicCompany.s_data, options));
+        }
     }
 }
index da88e2a..3c9ac76 100644 (file)
@@ -181,7 +181,7 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Contains(@"""NullableInt"":null", json);
 
             JsonSerializerOptions options = new JsonSerializerOptions();
-            options.IgnoreNullPropertyValueOnWrite = true;
+            options.IgnoreNullValues = true;
             json = JsonSerializer.ToString(obj, options);
             Assert.DoesNotContain(@"""NullableInt"":null", json);
         }
index 97ba2e6..dd4f8ec 100644 (file)
@@ -2,6 +2,7 @@
 // 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
@@ -14,22 +15,37 @@ namespace System.Text.Json.Serialization.Tests
             var obj = new ClassWithNoSetter();
 
             string json = JsonSerializer.ToString(obj);
-            Assert.Equal(@"{""MyString"":""DefaultValue""}", json);
+            Assert.Contains(@"""MyString"":""DefaultValue""", json);
+            Assert.Contains(@"""MyInts"":[1,2]", json);
 
-            ClassWithNoSetter objCopy = JsonSerializer.Parse<ClassWithNoSetter>(json);
-            Assert.Equal("DefaultValue", objCopy.MyString);
+            obj = JsonSerializer.Parse<ClassWithNoSetter>(@"{""MyString"":""IgnoreMe"",""MyInts"":[0]}");
+            Assert.Equal("DefaultValue", obj.MyString);
+            Assert.Equal(2, obj.MyInts.Length);
+        }
+
+        [Fact]
+        public static void IgnoreReadOnlyProperties()
+        {
+            var options = new JsonSerializerOptions();
+            options.IgnoreReadOnlyProperties = true;
+
+            var obj = new ClassWithNoSetter();
+
+            string json = JsonSerializer.ToString(obj, options);
+            Assert.Equal(@"{}", json);
         }
 
         [Fact]
         public static void NoGetter()
         {
-            var objNoSetter = new ClassWithNoSetter();
+            ClassWithNoGetter objWithNoGetter = JsonSerializer.Parse<ClassWithNoGetter>(
+                @"{""MyString"":""Hello"",""MyIntArray"":[0],""MyIntList"":[0]}");
 
-            string json = JsonSerializer.ToString(objNoSetter);
-            Assert.Equal(@"{""MyString"":""DefaultValue""}", json);
+            Assert.Equal("Hello", objWithNoGetter.GetMyString());
 
-            ClassWithNoGetter objNoGetter = JsonSerializer.Parse<ClassWithNoGetter>(json);
-            Assert.Equal("DefaultValue", objNoGetter.GetMyString());
+            // Currently we don't support setters without getters.
+            Assert.Equal(0, objWithNoGetter.GetMyIntArray().Length);
+            Assert.Equal(0, objWithNoGetter.GetMyIntList().Count);
         }
 
         [Fact]
@@ -51,21 +67,69 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Null(objCopy.GetMyString());
         }
 
+        [Fact]
+        public static void JsonIgnoreAttribute()
+        {
+            // Verify default state.
+            var obj = new ClassWithIgnoreAttributeProperty();
+            Assert.Equal(@"MyString", obj.MyString);
+            Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore);
+            Assert.Equal(2, obj.MyStringsWithIgnore.Length);
+
+            // Verify serialize.
+            string json = JsonSerializer.ToString(obj);
+            Assert.Contains(@"""MyString""", json);
+            Assert.DoesNotContain(@"MyStringWithIgnore", json);
+            Assert.DoesNotContain(@"MyStringsWithIgnore", json);
+
+            // Verify deserialize default.
+            obj = JsonSerializer.Parse<ClassWithIgnoreAttributeProperty>(@"{}");
+            Assert.Equal(@"MyString", obj.MyString);
+            Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore);
+            Assert.Equal(2, obj.MyStringsWithIgnore.Length);
+
+            // Verify deserialize ignores the json for MyStringWithIgnore and MyStringsWithIgnore.
+            obj = JsonSerializer.Parse<ClassWithIgnoreAttributeProperty>(
+                @"{""MyString"":""Hello"", ""MyStringWithIgnore"":""IgnoreMe"", ""MyStringsWithIgnore"":[""IgnoreMe""]}");
+            Assert.Contains(@"Hello", obj.MyString);
+            Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore);
+            Assert.Equal(2, obj.MyStringsWithIgnore.Length);
+        }
+
         // Todo: add tests with missing object property and missing collection property.
 
+        public class ClassWithPrivateSetterAndGetter
+        {
+            private string MyString { get; set; }
+
+            public string GetMyString()
+            {
+                return MyString;
+            }
+
+            public void SetMyString(string value)
+            {
+                MyString = value;
+            }
+        }
+
         public class ClassWithNoSetter
         {
             public ClassWithNoSetter()
             {
                 MyString = "DefaultValue";
+                MyInts = new int[] { 1, 2 };
             }
 
             public string MyString { get; }
+            public int[] MyInts { get; }
         }
 
         public class ClassWithNoGetter
         {
             string _myString = "";
+            int[] _myIntArray = new int[] { };
+            List<int> _myIntList = new List<int> { };
 
             public string MyString
             {
@@ -75,25 +139,54 @@ namespace System.Text.Json.Serialization.Tests
                 }
             }
 
+            public int[] MyIntArray
+            {
+                set
+                {
+                    _myIntArray = value;
+                }
+            }
+
+            public List<int> MyList
+            {
+                set
+                {
+                    _myIntList = value;
+                }
+            }
+
             public string GetMyString()
             {
                 return _myString;
             }
-        }
 
-        public class ClassWithPrivateSetterAndGetter
-        {
-            private string MyString { get; set; }
+            public int[] GetMyIntArray()
+            {
+                return _myIntArray;
+            }
 
-            public string GetMyString()
+            public List<int> GetMyIntList()
             {
-                return MyString;
+                return _myIntList;
             }
+        }
 
-            public void SetMyString(string value)
+        public class ClassWithIgnoreAttributeProperty
+        {
+            public ClassWithIgnoreAttributeProperty()
             {
-                MyString = value;
+                MyString = "MyString";
+                MyStringWithIgnore = "MyStringWithIgnore";
+                MyStringsWithIgnore = new string[] { "1", "2" };
             }
+
+            [JsonIgnore]
+            public string MyStringWithIgnore { get; set; }
+
+            public string MyString { get; set; }
+
+            [JsonIgnore]
+            public string[] MyStringsWithIgnore { get; set; }
         }
     }
 }
index c00cff0..4701d52 100644 (file)
@@ -555,17 +555,17 @@ namespace System.Text.Json.Serialization.Tests
         }
     }
 
-    public class TestClassWithNullButInitialized
+    public class TestClassWithInitializedProperties
     {
         public string MyString { get; set; } = "Hello";
         public int? MyInt { get; set; } = 1;
-        public static readonly string s_json =
+        public static readonly string s_null_json =
                 @"{" +
                 @"""MyString"" : null," +
                 @"""MyInt"" : null" +
                 @"}";
 
-        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+        public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_null_json);
     }
 
     public class TestClassWithNestedObjectInner : ITestClass
index 271c840..cb5ee2e 100644 (file)
@@ -254,6 +254,14 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
+        public static void ReadEmptyObjectArray()
+        {
+            SimpleTestClass[] data = JsonSerializer.Parse<SimpleTestClass[]>("[{}]");
+            Assert.Equal(1, data.Length);
+            Assert.NotNull(data[0]);
+        }
+
+        [Fact]
         public static void ReadPrimitiveJaggedArray()
         {
             int[][] i = JsonSerializer.Parse<int[][]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
index bd53182..0ff8dbe 100644 (file)
@@ -84,6 +84,15 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
+        public static void WriteEmptyObjectArray()
+        {
+            object[] arr = new object[]{new object()};
+
+            string json = JsonSerializer.ToString(arr);
+            Assert.Equal("[{}]", json);
+        }
+
+        [Fact]
         public static void WritePrimitiveJaggedArray()
         {
             var input = new int[2][];