From: Steve Harter Date: Sat, 13 Apr 2019 22:32:49 +0000 (-0700) Subject: JsonSerializerOptions API update and ignore property features (dotnet/corefx#36776) X-Git-Tag: submit/tizen/20210909.063632~11031^2~1887 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=d5e7e4004e3b3fd8af7bf6e74df5ecc073cbf5c1;p=platform%2Fupstream%2Fdotnet%2Fruntime.git JsonSerializerOptions API update and ignore property features (dotnet/corefx#36776) Commit migrated from https://github.com/dotnet/corefx/commit/b06250eb9ef50bca50cf7676e87753dbd8b5470a --- diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index aff6e05..b4e1103 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -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 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 { } } } } diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index b83207b..eb4d7ec 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -321,4 +321,7 @@ The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. + + Serializer options cannot be changed once serialization or deserialization has occurred. + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index b22566c..9dc525a 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -65,14 +65,17 @@ + + + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderOptions.cs index 7ef94d8..e5d1b83 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderOptions.cs @@ -15,7 +15,7 @@ namespace System.Text.Json /// /// Defines how the should handle comments when reading through the JSON. - /// By default the reader will throw a if it encounters a comment. + /// By default is thrown if a comment is encountered. /// public JsonCommentHandling CommentHandling { get; set; } @@ -37,8 +37,8 @@ namespace System.Text.Json /// /// 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 if it encounters a trailing comma. + /// is allowed (and ignored) within the JSON payload being read. + /// By default, it's set to false, and is thrown if a trailing comma is encountered. /// 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 index 0000000..8b4ca3d --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonAttribute.cs @@ -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 +{ + /// + /// The base class of serialization attributes. + /// + 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 index 0000000..8644189 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonIgnoreAttribute.cs @@ -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 +{ + /// + /// Prevents a property from being serialized or deserialized. + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] + public sealed class JsonIgnoreAttribute : JsonAttribute + { + public JsonIgnoreAttribute() { } + } +} 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 index bcd3037..a43d1d1 100644 --- 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 @@ -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 EscapedName => _escapedName; public ReadOnlySpan 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() != 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() 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); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs index a90b880..35efe45 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs @@ -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); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs index 8a17d00..c321937 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs @@ -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); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs index 06c6473..d387d90 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs @@ -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); } 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 index 5035e4e..4f4c30c 100644 --- 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 @@ -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); 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 index 16c07e5..ace0c71 100644 --- 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 @@ -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 index 0000000..7250c80 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Helpers.cs @@ -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; + } + } +} 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 index c77c1cb..e6ffb2c 100644 --- 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 @@ -48,10 +48,9 @@ namespace System.Text.Json.Serialization private static object ParseCore(ReadOnlySpan 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); 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 index d44faac..402bfd1 100644 --- 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 @@ -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.Shared.Rent(options.DefaultBufferSize); 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 index 095a394..ef4d869 100644 --- 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 @@ -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); 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 index 295861e..0bad2a0 100644 --- 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 @@ -14,23 +14,6 @@ namespace System.Text.Json.Serialization public static partial class JsonSerializer { internal static readonly JsonPropertyInfo s_missingProperty = new JsonPropertyInfoNotNullable(); - 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( 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 index ef65d72..ea02e71 100644 --- 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 @@ -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) { 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 index beec923..ee4b796 100644 --- 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 @@ -114,7 +114,7 @@ namespace System.Text.Json.Serialization } else { - if (!jsonPropertyInfo.IgnoreNullPropertyValueOnWrite(options)) + if (!jsonPropertyInfo.IgnoreNullValues) { writer.WriteNull(jsonPropertyInfo._escapedName); } 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 index d0c9b74..f0b62d7 100644 --- 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 @@ -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(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) 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 index 2990306..2889d3f 100644 --- 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 @@ -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(options.DefaultBufferSize)) { 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 index e8230a8..f10a0db 100644 --- 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 @@ -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 _classes = new ConcurrentDictionary(); private ClassMaterializer _classMaterializerStrategy; + private JsonCommentHandling _readCommentHandling; private int _defaultBufferSize = BufferSizeDefault; - - private static readonly ConcurrentDictionary s_classes = new ConcurrentDictionary(); + private int _maxDepth; + private bool _allowTrailingCommas; + private bool _haveTypesBeenCreated; + private bool _ignoreNullValues; + private bool _ignoreReadOnlyProperties; + private bool _writeIndented; /// /// Constructs a new instance. /// public JsonSerializerOptions() { } - internal JsonClassInfo GetOrAddClass(Type classType) + /// + /// 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 is thrown if a trailing comma is encountered. + /// + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + 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; } /// - /// Options to control the . - /// - public JsonReaderOptions ReaderOptions { get; set; } - - /// - /// Options to control the . - /// - public JsonWriterOptions WriterOptions { get; set; } - - /// /// The default buffer size in bytes used when creating temporary buffers. /// /// The default size is 16K. /// Thrown when the buffer size is less than 1. + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// 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 } /// - /// 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. + /// + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + public bool IgnoreNullValues + { + get + { + return _ignoreNullValues; + } + set + { + VerifyMutable(); + _ignoreNullValues = value; + } + } + + /// + /// 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. /// - public bool IgnoreNullPropertyValueOnWrite { get; set; } + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + public bool IgnoreReadOnlyProperties + { + get + { + return _ignoreReadOnlyProperties; + } + set + { + VerifyMutable(); + _ignoreReadOnlyProperties = value; + } + } /// - /// 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 . /// - public bool IgnoreNullPropertyValueOnRead { get; set; } + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + public int MaxDepth + { + get + { + return _maxDepth; + } + set + { + VerifyMutable(); + _maxDepth = value; + } + } + + /// + /// Defines how the comments are handled during deserialization. + /// By default is thrown if a comment is encountered. + /// + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + public JsonCommentHandling ReadCommentHandling + { + get + { + return _readCommentHandling; + } + set + { + VerifyMutable(); + _readCommentHandling = value; + } + } + + /// + /// 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. + /// + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + 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(); + } + } } } 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 index 6bd5fc1..7c9fbab 100644 --- 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 @@ -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); + } } } diff --git a/src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs index a07b4d0..b606976 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs @@ -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("[]"); + Assert.Equal(0, arr.Length); + + List list = JsonSerializer.Parse>("[]"); + Assert.Equal(0, list.Count); + } + + [Fact] public static void ReadClassWithStringArray() { TestClassWithStringArray obj = JsonSerializer.Parse(TestClassWithStringArray.s_data); diff --git a/src/libraries/System.Text.Json/tests/Serialization/Array.WriteTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Array.WriteTests.cs index 1a63ef8..d6e0715 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Array.WriteTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Array.WriteTests.cs @@ -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()); + Assert.Equal("[]", json); + } + + [Fact] public static void WriteClassWithStringArray() { string json; diff --git a/src/libraries/System.Text.Json/tests/Serialization/Null.ReadTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Null.ReadTests.cs index b36ad30..13bb1d5 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Null.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Null.ReadTests.cs @@ -30,20 +30,20 @@ namespace System.Text.Json.Serialization.Tests } [Fact] - public static void DefaultReadValue() + public static void DefaultIgnoreNullValuesOnRead() { - TestClassWithNullButInitialized obj = JsonSerializer.Parse(TestClassWithNullButInitialized.s_json); + TestClassWithInitializedProperties obj = JsonSerializer.Parse(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.s_json, options); + TestClassWithInitializedProperties obj = JsonSerializer.Parse(TestClassWithInitializedProperties.s_null_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 index 2a53ce0..8e72f1e 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Null.WriteTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Null.WriteTests.cs @@ -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); } diff --git a/src/libraries/System.Text.Json/tests/Serialization/Object.ReadTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Object.ReadTests.cs index 88637d8..ae498e5 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Object.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Object.ReadTests.cs @@ -16,6 +16,13 @@ namespace System.Text.Json.Serialization.Tests } [Fact] + public static void ReadEmpty() + { + SimpleTestClass obj = JsonSerializer.Parse("{}"); + Assert.NotNull(obj); + } + + [Fact] public static void EmptyClassWithRandomData() { JsonSerializer.Parse(SimpleTestClass.s_json); diff --git a/src/libraries/System.Text.Json/tests/Serialization/OptionsTests.cs b/src/libraries/System.Text.Json/tests/Serialization/OptionsTests.cs index 16031f6..35c7d25 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/OptionsTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/OptionsTests.cs @@ -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("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(() => options.AllowTrailingCommas = options.AllowTrailingCommas); + Assert.Throws(() => options.DefaultBufferSize = options.DefaultBufferSize); + Assert.Throws(() => options.IgnoreNullValues = options.IgnoreNullValues); + Assert.Throws(() => options.MaxDepth = options.MaxDepth); + Assert.Throws(() => options.ReadCommentHandling = options.ReadCommentHandling); + Assert.Throws(() => options.WriteIndented = options.WriteIndented); + } + + [Fact] public static void DefaultBufferSizeFail() { Assert.Throws(() => 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(() => JsonSerializer.Parse("[1,]")); + + var options = new JsonSerializerOptions(); + options.AllowTrailingCommas = true; + + int[] value = JsonSerializer.Parse("[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(() => JsonSerializer.Parse("/* commment */")); + + var options = new JsonSerializerOptions(); + + Assert.Throws(() => JsonSerializer.Parse("/* commment */", options)); + + options = new JsonSerializerOptions(); + options.ReadCommentHandling = JsonCommentHandling.Allow; + + JsonSerializer.Parse("/* commment */", options); + } + + [Fact] + public static void MaxDepthRead() + { + JsonSerializer.Parse(BasicCompany.s_data); + + var options = new JsonSerializerOptions(); + + JsonSerializer.Parse(BasicCompany.s_data, options); + + options = new JsonSerializerOptions(); + options.MaxDepth = 1; + + Assert.Throws(() => JsonSerializer.Parse(BasicCompany.s_data, options)); + } } } diff --git a/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs index da88e2a..3c9ac76 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs @@ -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); } diff --git a/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs index 97ba2e6..dd4f8ec 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs @@ -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(json); - Assert.Equal("DefaultValue", objCopy.MyString); + obj = JsonSerializer.Parse(@"{""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( + @"{""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(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(@"{}"); + 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( + @"{""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 _myIntList = new List { }; public string MyString { @@ -75,25 +139,54 @@ namespace System.Text.Json.Serialization.Tests } } + public int[] MyIntArray + { + set + { + _myIntArray = value; + } + } + + public List 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 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; } } } } diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs index c00cff0..4701d52 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs @@ -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 diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.cs index 271c840..cb5ee2e 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.cs @@ -254,6 +254,14 @@ namespace System.Text.Json.Serialization.Tests } [Fact] + public static void ReadEmptyObjectArray() + { + SimpleTestClass[] data = JsonSerializer.Parse("[{}]"); + Assert.Equal(1, data.Length); + Assert.NotNull(data[0]); + } + + [Fact] public static void ReadPrimitiveJaggedArray() { int[][] i = JsonSerializer.Parse(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.cs index bd53182..0ff8dbe 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.cs @@ -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][];