From: Steve Harter Date: Fri, 26 Apr 2019 18:59:28 +0000 (-0700) Subject: Add serializer support for IDictionary non-primitive types (dotnet/corefx#37186) X-Git-Tag: submit/tizen/20210909.063632~11031^2~1767 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=3bce23e607f3e4309bd3b1364b7d0e9ab85533c2;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Add serializer support for IDictionary non-primitive types (dotnet/corefx#37186) Commit migrated from https://github.com/dotnet/corefx/commit/c5bb6abec986627f90b2e7e648dc4e733cdbf63b --- 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 5070433..28d6bbc 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -17,7 +17,6 @@ - @@ -90,6 +89,7 @@ + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs index a9c5c5a..df4f992 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs @@ -9,6 +9,18 @@ namespace System.Text.Json.Serialization { internal partial class JsonClassInfo { + private JsonPropertyInfo AddPolicyProperty(Type propertyType, JsonSerializerOptions options) + { + // A policy property is not a real property on a type; instead it leverages the existing converter + // logic and generic support to avoid boxing. It is used with values types and elements from collections and + // dictionaries. Typically it would represent a CLR type such as System.String. + return AddProperty( + propertyType, + propertyInfo : null, // Not a real property so this is null. + classType : typeof(object), // A dummy type (not used). + options : options); + + } private JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options) { JsonPropertyInfo jsonInfo = CreateProperty(propertyType, propertyType, propertyInfo, classType, options); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs index d26b4ec..a4f8122 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs @@ -114,7 +114,7 @@ namespace System.Text.Json.Serialization else if (ClassType == ClassType.Enumerable || ClassType == ClassType.Dictionary) { // Add a single property that maps to the class type so we can have policies applied. - JsonPropertyInfo jsonPropertyInfo = AddProperty(type, propertyInfo: null, type, options); + JsonPropertyInfo jsonPropertyInfo = AddPolicyProperty(type, options); // Use the type from the property policy to get any late-bound concrete types (from an interface like IDictionary). CreateObject = options.ClassMaterializerStrategy.CreateConstructor(jsonPropertyInfo.RuntimePropertyType); @@ -127,7 +127,7 @@ namespace System.Text.Json.Serialization else if (ClassType == ClassType.Value) { // Add a single property that maps to the class type so we can have policies applied. - AddProperty(type, propertyInfo: null, type, options); + AddPolicyProperty(type, options); } else { @@ -319,19 +319,22 @@ namespace System.Text.Json.Serialization if (propertyType.IsGenericType) { - if (GetClassType(propertyType) == ClassType.Dictionary) + if (GetClassType(propertyType) == ClassType.Dictionary && + args.Length >= 2) // It is >= 2 in case there is a Dictionary. { + elementType = args[1]; } - else + else if (args.Length >= 1) // It is >= 1 in case there is an IEnumerable. { + Debug.Assert(GetClassType(propertyType) == ClassType.Enumerable); elementType = args[0]; } } else { // Unable to determine collection type; attempt to use object which will be used to create loosely-typed collection. - return typeof(object); + elementType = typeof(object); } } } 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 1fb7bc1..f46a5c9 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 @@ -251,7 +251,7 @@ namespace System.Text.Json.Serialization other._escapedName = _escapedName; } - public abstract object GetValueAsObject(object obj, JsonSerializerOptions options); + public abstract object GetValueAsObject(object obj); public TAttribute GetAttribute() where TAttribute : Attribute { @@ -266,7 +266,7 @@ namespace System.Text.Json.Serialization public abstract void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader); public abstract void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader); - public abstract void SetValueAsObject(object obj, object value, JsonSerializerOptions options); + public abstract void SetValueAsObject(object obj, object value); public abstract void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer); 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 b1e5e12..12d1e3a 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 @@ -53,11 +53,7 @@ namespace System.Text.Json.Serialization _isPropertyPolicy = true; HasGetter = true; HasSetter = true; - - if (ClassType == ClassType.Dictionary) - { - ValueConverter = DefaultConverters.s_converter; - } + ValueConverter = DefaultConverters.s_converter; } GetPolicies(options); @@ -69,7 +65,7 @@ namespace System.Text.Json.Serialization base.GetPolicies(options); } - public override object GetValueAsObject(object obj, JsonSerializerOptions options) + public override object GetValueAsObject(object obj) { if (_isPropertyPolicy) { @@ -80,7 +76,7 @@ namespace System.Text.Json.Serialization return Get((TClass)obj); } - public override void SetValueAsObject(object obj, object value, JsonSerializerOptions options) + public override void SetValueAsObject(object obj, object value) { Debug.Assert(Set != null); TDeclaredProperty typedValue = (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 b07fe02..14c445c 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 @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; @@ -79,7 +78,7 @@ namespace System.Text.Json.Serialization public override void ApplyNullValue(JsonSerializerOptions options, ref ReadStack state) { Debug.Assert(state.Current.JsonPropertyInfo != null); - state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null, options); + state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value : null); } // todo: have the caller check if current.Enumerator != null and call WriteEnumerable of the underlying property directly to avoid an extra virtual call. 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 d2d0699..afdfcdc 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 @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; 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 ab2efb3..5d12c8f 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 @@ -40,42 +40,40 @@ namespace System.Text.Json.Serialization ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(arrayType, reader, state); } - Debug.Assert(state.Current.IsPropertyEnumerable()); - if (state.Current.IsPropertyEnumerable()) + Debug.Assert(state.Current.IsPropertyEnumerable || state.Current.IsDictionary); + + if (state.Current.EnumerableCreated) { - if (state.Current.EnumerableCreated) - { - // A nested json array so push a new stack frame. - Type elementType = state.Current.JsonClassInfo.ElementClassInfo.GetPolicyProperty().RuntimePropertyType; + // A nested json array so push a new stack frame. + Type elementType = state.Current.JsonClassInfo.ElementClassInfo.GetPolicyProperty().RuntimePropertyType; - state.Push(); + state.Push(); - state.Current.Initialize(elementType, options); - state.Current.PopStackOnEndArray = true; - } - else - { - state.Current.EnumerableCreated = true; - } + state.Current.Initialize(elementType, options); + state.Current.PopStackOnEnd = true; + } + else + { + state.Current.EnumerableCreated = true; + } - jsonPropertyInfo = state.Current.JsonPropertyInfo; + jsonPropertyInfo = state.Current.JsonPropertyInfo; - // If current property is already set (from a constructor, for example) leave as-is - if (jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue, options) == null) + // If current property is already set (from a constructor, for example) leave as-is. + if (jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue) == null) + { + // Create the enumerable. + object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state, options); + if (value != null) { - // Create the enumerable. - object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state, options); - if (value != null) + if (state.Current.ReturnValue != null) { - if (state.Current.ReturnValue != null) - { - state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value, options); - } - else - { - // Primitive arrays being returned without object - state.Current.SetReturnValue(value, options); - } + state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); + } + else + { + // Primitive arrays being returned without object + state.Current.SetReturnValue(value); } } } @@ -117,8 +115,8 @@ namespace System.Text.Json.Serialization setPropertyDirectly = false; } - bool valueReturning = state.Current.PopStackOnEndArray; - if (state.Current.PopStackOnEndArray) + bool valueReturning = state.Current.PopStackOnEnd; + if (state.Current.PopStackOnEnd) { state.Pop(); } @@ -132,7 +130,7 @@ namespace System.Text.Json.Serialization state.Current.ReturnValue = value; return true; } - else if (state.Current.IsEnumerable()) + else if (state.Current.IsEnumerable || state.Current.IsDictionary) { // Returning a non-converted list. return true; @@ -153,7 +151,7 @@ namespace System.Text.Json.Serialization // If this method is changed, also change ApplyValueToEnumerable. internal static void ApplyObjectToEnumerable(object value, JsonSerializerOptions options, ref ReadStackFrame frame, bool setPropertyDirectly = false) { - if (frame.IsEnumerable()) + if (frame.IsEnumerable) { if (frame.TempEnumerableValues != null) { @@ -164,7 +162,7 @@ namespace System.Text.Json.Serialization ((IList)frame.ReturnValue).Add(value); } } - else if (!setPropertyDirectly && frame.IsPropertyEnumerable()) + else if (!setPropertyDirectly && frame.IsPropertyEnumerable) { Debug.Assert(frame.JsonPropertyInfo != null); Debug.Assert(frame.ReturnValue != null); @@ -174,23 +172,18 @@ namespace System.Text.Json.Serialization } else { - ((IList)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue, options)).Add(value); + ((IList)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue)).Add(value); } } - else if (frame.IsDictionary()) - { - ((IDictionary)frame.ReturnValue).Add(frame.KeyName, value); - } - else if (frame.IsPropertyADictionary()) + else if (frame.IsDictionary) { - Debug.Assert(frame.JsonPropertyInfo != null); Debug.Assert(frame.ReturnValue != null); - frame.JsonPropertyInfo.SetValueAsObject(frame.ReturnValue, value, options); + ((IDictionary)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue)).Add(frame.KeyName, value); } else { Debug.Assert(frame.JsonPropertyInfo != null); - frame.JsonPropertyInfo.SetValueAsObject(frame.ReturnValue, value, options); + frame.JsonPropertyInfo.SetValueAsObject(frame.ReturnValue, value); } } @@ -200,7 +193,7 @@ namespace System.Text.Json.Serialization JsonSerializerOptions options, ref ReadStackFrame frame) { - if (frame.IsEnumerable()) + if (frame.IsEnumerable) { if (frame.TempEnumerableValues != null) { @@ -211,7 +204,7 @@ namespace System.Text.Json.Serialization ((IList)frame.ReturnValue).Add(value); } } - else if (frame.IsPropertyEnumerable()) + else if (frame.IsPropertyEnumerable) { Debug.Assert(frame.JsonPropertyInfo != null); Debug.Assert(frame.ReturnValue != null); @@ -221,24 +214,18 @@ namespace System.Text.Json.Serialization } else { - ((IList)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue, options)).Add(value); + ((IList)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue)).Add(value); } } - else if (frame.IsDictionary()) + else if (frame.IsDictionary) { - // todo: use TryAdd and throw JsonReaderException - ((IDictionary)frame.ReturnValue).Add(frame.KeyName, value); - } - else if (frame.IsPropertyADictionary()) - { - Debug.Assert(frame.JsonPropertyInfo != null); Debug.Assert(frame.ReturnValue != null); - ((IDictionary)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue, options)).Add(frame.KeyName, value); + ((IDictionary)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue)).Add(frame.KeyName, value); } else { Debug.Assert(frame.JsonPropertyInfo != null); - frame.JsonPropertyInfo.SetValueAsObject(frame.ReturnValue, value, options); + frame.JsonPropertyInfo.SetValueAsObject(frame.ReturnValue, value); } } } 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 c4ea004..ad97372 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 @@ -30,13 +30,13 @@ namespace System.Text.Json.Serialization ThrowHelper.ThrowJsonReaderException_DeserializeCannotBeNull(reader, state); } - if (state.Current.IsEnumerable() || state.Current.IsDictionary()) + if (state.Current.IsEnumerable || state.Current.IsDictionary) { ApplyObjectToEnumerable(null, options, ref state.Current); return false; } - if (state.Current.IsPropertyEnumerable() || state.Current.IsPropertyADictionary()) + if (state.Current.IsPropertyEnumerable) { state.Current.JsonPropertyInfo.ApplyNullValue(options, ref state); return false; @@ -50,7 +50,7 @@ namespace System.Text.Json.Serialization if (!propertyInfo.IgnoreNullValues) { - state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null, options); + state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value : null); } return false; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs index 1289b49..ae17128 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; + namespace System.Text.Json.Serialization { public static partial class JsonSerializer { - private static void HandleStartObject(JsonSerializerOptions options, ref ReadStack state) + private static void HandleStartObject(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state) { if (state.Current.Skip()) { @@ -15,23 +17,49 @@ namespace System.Text.Json.Serialization return; } - if (state.Current.IsDictionary()) - { - // Fall through and treat as a return value. - } - else if (state.Current.IsEnumerable() || state.Current.IsPropertyEnumerable() || state.Current.IsPropertyADictionary()) + if (state.Current.IsProcessingEnumerable) { - // An array of objects either on the current property or on a list Type objType = state.Current.GetElementType(); state.Push(); - state.Current.JsonClassInfo = options.GetOrAddClass(objType); + state.Current.Initialize(objType, options); } else if (state.Current.JsonPropertyInfo != null) { - // Nested object - Type objType = state.Current.JsonPropertyInfo.RuntimePropertyType; - state.Push(); - state.Current.JsonClassInfo = options.GetOrAddClass(objType); + if (state.Current.IsDictionary) + { + // Verify that the Dictionary can be deserialized by having as first generic argument. + Debug.Assert(state.Current.JsonClassInfo.Type.GetGenericArguments().Length >= 1); + if (state.Current.JsonClassInfo.Type.GetGenericArguments()[0].UnderlyingSystemType != typeof(string)) + { + ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(state.Current.JsonClassInfo.Type, reader, state); + } + + ClassType classType = state.Current.JsonClassInfo.ElementClassInfo.ClassType; + + if (state.Current.ReturnValue == null) + { + // The Dictionary created below will be returned to corresponding Parse() etc method. + // Ensure any nested array creates a new frame. + state.Current.EnumerableCreated = true; + } + else + { + Debug.Assert(classType == ClassType.Object || classType == ClassType.Dictionary); + + // A nested object or dictionary. + JsonClassInfo classInfoTemp = state.Current.JsonClassInfo; + state.Push(); + state.Current.JsonClassInfo = classInfoTemp.ElementClassInfo; + state.Current.InitializeJsonPropertyInfo(); + } + } + else + { + // Nested object. + Type objType = state.Current.JsonPropertyInfo.RuntimePropertyType; + state.Push(); + state.Current.Initialize(objType, options); + } } JsonClassInfo classInfo = state.Current.JsonClassInfo; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs index 51b51aa..4f3c737 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleValue.cs @@ -19,7 +19,7 @@ namespace System.Text.Json.Serialization jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options); } - bool lastCall = (!state.Current.IsProcessingEnumerableOrDictionary() && state.Current.ReturnValue == null); + bool lastCall = (!state.Current.IsProcessingEnumerableOrDictionary && state.Current.ReturnValue == null); jsonPropertyInfo.Read(tokenType, options, ref state, ref reader); return lastCall; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs index 08b8d0b..94b89a4 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 @@ -42,7 +42,7 @@ namespace System.Text.Json.Serialization Debug.Assert(state.Current.ReturnValue != default); Debug.Assert(state.Current.JsonClassInfo != default); - if (state.Current.IsDictionary()) + if (state.Current.IsDictionary) { string keyName = reader.GetString(); if (options.DictionaryKeyPolicy != null) @@ -75,7 +75,7 @@ namespace System.Text.Json.Serialization } else if (tokenType == JsonTokenType.StartObject) { - HandleStartObject(options, ref state); + HandleStartObject(options, ref reader, ref state); } else if (tokenType == JsonTokenType.EndObject) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs index bcacc6f..bbba769 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs @@ -18,8 +18,6 @@ namespace System.Text.Json.Serialization Utf8JsonWriter writer, ref WriteStack state) { - Debug.Assert(state.Current.JsonPropertyInfo.ClassType == ClassType.Dictionary); - JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; if (!jsonPropertyInfo.ShouldSerialize) { @@ -29,25 +27,17 @@ namespace System.Text.Json.Serialization if (state.Current.Enumerator == null) { - IEnumerable enumerable = (IEnumerable)jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue, options); + IEnumerable enumerable = (IEnumerable)jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue); if (enumerable == null) { // Write a null object or enumerable. - writer.WriteNull(jsonPropertyInfo.Name); + state.Current.WriteObjectOrArrayStart(ClassType.Dictionary, writer, writeNull : true); return true; } state.Current.Enumerator = enumerable.GetEnumerator(); - - if (jsonPropertyInfo.Name == null) - { - writer.WriteStartObject(); - } - else - { - writer.WriteStartObject(jsonPropertyInfo.Name); - } + state.Current.WriteObjectOrArrayStart(ClassType.Dictionary, writer); } if (state.Current.Enumerator.MoveNext()) @@ -55,8 +45,7 @@ namespace System.Text.Json.Serialization // Check for polymorphism. if (elementClassInfo.ClassType == ClassType.Unknown) { - //todo:test - object currentValue = ((IDictionaryEnumerator)(state.Current.Enumerator)).Entry; + object currentValue = ((IDictionaryEnumerator)state.Current.Enumerator).Entry; GetRuntimeClassInfo(currentValue, ref elementClassInfo, options); } @@ -71,8 +60,10 @@ namespace System.Text.Json.Serialization else { // An object or another enumerator requires a new stack frame. - object nextValue = state.Current.Enumerator.Current; - state.Push(elementClassInfo, nextValue); + var enumerator = (IDictionaryEnumerator)state.Current.Enumerator; + object value = enumerator.Value; + state.Push(elementClassInfo, value); + state.Current.KeyName = (string)enumerator.Key; } return false; @@ -81,7 +72,14 @@ namespace System.Text.Json.Serialization // We are done enumerating. writer.WriteEndObject(); - state.Current.EndDictionary(); + if (state.Current.PopStackOnEnd) + { + state.Pop(); + } + else + { + state.Current.EndDictionary(); + } return true; } @@ -119,28 +117,35 @@ namespace System.Text.Json.Serialization } else { + byte[] utf8Key = Encoding.UTF8.GetBytes(key); #if true // temporary behavior until the writer can accept escaped string. - byte[] utf8Key = Encoding.UTF8.GetBytes(key); converter.Write(utf8Key, value, writer); #else - byte[] pooledKey = null; - byte[] utf8Key = Encoding.UTF8.GetBytes(key); - int length = JsonWriterHelper.GetMaxEscapedLength(utf8Key.Length, 0); + int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Key); + if (valueIdx == -1) + { + converter.Write(utf8Key, value, writer); + } + else + { + byte[] pooledKey = null; + int length = JsonWriterHelper.GetMaxEscapedLength(utf8Key.Length, valueIdx); - Span escapedKey = length <= JsonConstants.StackallocThreshold ? - stackalloc byte[length] : - (pooledKey = ArrayPool.Shared.Rent(length)); + Span escapedKey = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (pooledKey = ArrayPool.Shared.Rent(length)); - JsonWriterHelper.EscapeString(utf8Key, escapedKey, 0, out int written); + JsonWriterHelper.EscapeString(utf8Key, escapedKey, valueIdx, out int written); - converter.Write(escapedKey.Slice(0, written), value, writer); + converter.Write(escapedKey.Slice(0, written), value, writer); - if (pooledKey != null) - { - // We clear the array because it is "user data" (although a property name). - new Span(pooledKey, 0, written).Clear(); - ArrayPool.Shared.Return(pooledKey); + if (pooledKey != null) + { + // We clear the array because it is "user data" (although a property name). + new Span(pooledKey, 0, written).Clear(); + ArrayPool.Shared.Return(pooledKey); + } } #endif } 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 da494ef..74d0ca5 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,25 +26,18 @@ namespace System.Text.Json.Serialization if (state.Current.Enumerator == null) { - IEnumerable enumerable = (IEnumerable)jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue, options); + IEnumerable enumerable = (IEnumerable)jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue); if (enumerable == null) { // Write a null object or enumerable. - writer.WriteNull(jsonPropertyInfo.Name); + state.Current.WriteObjectOrArrayStart(ClassType.Enumerable, writer, writeNull: true); return true; } state.Current.Enumerator = enumerable.GetEnumerator(); - if (jsonPropertyInfo.Name == null) - { - writer.WriteStartArray(); - } - else - { - writer.WriteStartArray(jsonPropertyInfo.Name); - } + state.Current.WriteObjectOrArrayStart(ClassType.Enumerable, writer); } if (state.Current.Enumerator.MoveNext()) @@ -78,7 +71,7 @@ namespace System.Text.Json.Serialization // We are done enumerating. writer.WriteEndArray(); - if (state.Current.PopStackOnEndArray) + if (state.Current.PopStackOnEnd) { state.Pop(); } 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 8cddfd7..4e8585b 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 @@ -18,15 +18,7 @@ namespace System.Text.Json.Serialization // Write the start. if (!state.Current.StartObjectWritten) { - if (state.Current.JsonPropertyInfo?._escapedName == null) - { - writer.WriteStartObject(); - } - else - { - writer.WriteStartObject(state.Current.JsonPropertyInfo._escapedName); - } - state.Current.StartObjectWritten = true; + state.Current.WriteObjectOrArrayStart(ClassType.Object, writer); } // Determine if we are done enumerating properties. @@ -67,7 +59,7 @@ namespace System.Text.Json.Serialization // Check for polymorphism. if (jsonPropertyInfo.ClassType == ClassType.Unknown) { - currentValue = jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue, options); + currentValue = jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue); obtainedValue = true; GetRuntimePropertyInfo(currentValue, state.Current.JsonClassInfo, ref jsonPropertyInfo, options); } @@ -108,7 +100,7 @@ namespace System.Text.Json.Serialization // A property that returns an object. if (!obtainedValue) { - currentValue = jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue, options); + currentValue = jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue); } if (currentValue != null) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs index 946815b..54e11c6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs @@ -11,116 +11,74 @@ namespace System.Text.Json.Serialization internal struct ReadStackFrame { // The object (POCO or IEnumerable) that is being populated - internal object ReturnValue; - internal JsonClassInfo JsonClassInfo; + public object ReturnValue; + public JsonClassInfo JsonClassInfo; - // Support Dictionary - internal string KeyName; + // Support Dictionary keys. + public string KeyName; - // Current property values - internal JsonPropertyInfo JsonPropertyInfo; - internal bool PopStackOnEndArray; - internal bool EnumerableCreated; + // Current property values. + public JsonPropertyInfo JsonPropertyInfo; - // Support System.Array and other types that don't implement IList - internal IList TempEnumerableValues; + // Pop the stack when the current array or dictionary is done. + public bool PopStackOnEnd; + + // Support System.Array and other types that don't implement IList. + public IList TempEnumerableValues; + public bool EnumerableCreated; // For performance, we order the properties by the first deserialize and PropertyIndex helps find the right slot quicker. - internal int PropertyIndex; - internal List PropertyRefCache; + public int PropertyIndex; + public List PropertyRefCache; + + // The current JSON data for a property does not match a given POCO, so ignore the property (recursively). + public bool Drain; - // The current JSON data for a property does not match a given POCO, so ignore the property (recursively for enumerables or object). - internal bool Drain; + public bool IsDictionary => JsonClassInfo.ClassType == ClassType.Dictionary; + public bool IsEnumerable => JsonClassInfo.ClassType == ClassType.Enumerable; + public bool IsProcessingEnumerableOrDictionary => IsProcessingEnumerable || IsDictionary; + public bool IsProcessingEnumerable => IsEnumerable || IsPropertyEnumerable; + public bool IsPropertyEnumerable => JsonPropertyInfo != null ? JsonPropertyInfo.ClassType == ClassType.Enumerable : false; - internal void Initialize(Type type, JsonSerializerOptions options) + public void Initialize(Type type, JsonSerializerOptions options) { JsonClassInfo = options.GetOrAddClass(type); + InitializeJsonPropertyInfo(); + } + + public void InitializeJsonPropertyInfo() + { if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable || JsonClassInfo.ClassType == ClassType.Dictionary) { JsonPropertyInfo = JsonClassInfo.GetPolicyProperty(); } } - internal void Reset() + public void Reset() { - ReturnValue = null; + Drain = false; JsonClassInfo = null; + KeyName = null; PropertyRefCache = null; - PropertyIndex = 0; - Drain = false; - ResetProperty(); + ReturnValue = null; + EndObject(); } - internal void ResetProperty() + public void ResetProperty() { - JsonPropertyInfo = null; - PopStackOnEndArray = false; EnumerableCreated = false; + JsonPropertyInfo = null; + PopStackOnEnd = false; TempEnumerableValues = null; - KeyName = null; } - internal bool IsProcessingEnumerableOrDictionary() + public void EndObject() { - return IsEnumerable() ||IsPropertyEnumerable() || IsDictionary() || IsPropertyADictionary(); - } - - internal bool IsEnumerable() - { - return JsonClassInfo.ClassType == ClassType.Enumerable; - } - - internal bool IsDictionary() - { - return JsonClassInfo.ClassType == ClassType.Dictionary; - } - - internal bool Skip() - { - return Drain || ReferenceEquals(JsonPropertyInfo, JsonSerializer.s_missingProperty); - } - - internal bool IsPropertyEnumerable() - { - if (JsonPropertyInfo != null) - { - return JsonPropertyInfo.ClassType == ClassType.Enumerable; - } - - return false; - } - - internal bool IsPropertyADictionary() - { - if (JsonPropertyInfo != null) - { - return JsonPropertyInfo.ClassType == ClassType.Dictionary; - } - - return false; - } - - public Type GetElementType() - { - if (IsPropertyEnumerable()) - { - return JsonPropertyInfo.ElementClassInfo.Type; - } - - if (IsEnumerable()) - { - return JsonClassInfo.ElementClassInfo.Type; - } - - if (IsDictionary()) - { - return JsonClassInfo.ElementClassInfo.Type; - } - - return JsonPropertyInfo.RuntimePropertyType; + PropertyIndex = 0; + ResetProperty(); } - internal static object CreateEnumerableValue(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options) + public static object CreateEnumerableValue(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options) { JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; @@ -157,9 +115,29 @@ namespace System.Text.Json.Serialization } } - internal static IEnumerable GetEnumerableValue(in ReadStackFrame current) + public Type GetElementType() + { + if (IsPropertyEnumerable) + { + return JsonPropertyInfo.ElementClassInfo.Type; + } + + if (IsEnumerable) + { + return JsonClassInfo.ElementClassInfo.Type; + } + + if (IsDictionary) + { + return JsonClassInfo.ElementClassInfo.Type; + } + + return JsonPropertyInfo.RuntimePropertyType; + } + + public static IEnumerable GetEnumerableValue(in ReadStackFrame current) { - if (current.IsEnumerable()) + if (current.IsEnumerable) { if (current.ReturnValue != null) { @@ -171,10 +149,15 @@ namespace System.Text.Json.Serialization return current.TempEnumerableValues; } - internal void SetReturnValue(object value, JsonSerializerOptions options) + public void SetReturnValue(object value) { Debug.Assert(ReturnValue == null); ReturnValue = value; } + + public bool Skip() + { + return Drain || ReferenceEquals(JsonPropertyInfo, JsonSerializer.s_missingProperty); + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs index 4b614a9..5cc3453 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs @@ -44,14 +44,16 @@ namespace System.Text.Json.Serialization Current.JsonClassInfo = nextClassInfo; Current.CurrentValue = nextValue; - if (nextClassInfo.ClassType == ClassType.Enumerable) + ClassType classType = nextClassInfo.ClassType; + + if (classType == ClassType.Enumerable || nextClassInfo.ClassType == ClassType.Dictionary) { - Current.PopStackOnEndArray = true; + Current.PopStackOnEnd = true; Current.JsonPropertyInfo = Current.JsonClassInfo.GetPolicyProperty(); } else { - Debug.Assert(nextClassInfo.ClassType == ClassType.Object || nextClassInfo.ClassType == ClassType.Dictionary || nextClassInfo.ClassType == ClassType.Unknown); + Debug.Assert(nextClassInfo.ClassType == ClassType.Object || nextClassInfo.ClassType == ClassType.Unknown); Current.PopStackOnEndObject = true; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs index 72e3167..82f823b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs @@ -2,31 +2,40 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Buffers; using System.Collections; +using System.Diagnostics; namespace System.Text.Json.Serialization { internal struct WriteStackFrame { - // The object (POCO or IEnumerable) that is being populated - internal object CurrentValue; - internal JsonClassInfo JsonClassInfo; + // The object (POCO or IEnumerable) that is being populated. + public object CurrentValue; + public JsonClassInfo JsonClassInfo; - internal IEnumerator Enumerator; + // Support Dictionary keys. + public string KeyName; - // Current property values - internal JsonPropertyInfo JsonPropertyInfo; + // The current enumerator for the IEnumerable or IDictionary. + public IEnumerator Enumerator; + + // Current property values. + public JsonPropertyInfo JsonPropertyInfo; // The current property. - internal int PropertyIndex; + public int PropertyIndex; + + // Has the Start tag been written. + public bool StartObjectWritten; - // Has the Start tag been written - internal bool StartObjectWritten; + // Pop the stack when the current array or dictionary is done. + public bool PopStackOnEnd; - internal bool PopStackOnEndArray; - internal bool PopStackOnEndObject; + // Pop the stack when the current object is done. + public bool PopStackOnEndObject; - internal void Initialize(Type type, JsonSerializerOptions options) + public void Initialize(Type type, JsonSerializerOptions options) { JsonClassInfo = options.GetOrAddClass(type); if (JsonClassInfo.ClassType == ClassType.Value || JsonClassInfo.ClassType == ClassType.Enumerable || JsonClassInfo.ClassType == ClassType.Dictionary) @@ -35,41 +44,102 @@ namespace System.Text.Json.Serialization } } - internal void Reset() + public void WriteObjectOrArrayStart(ClassType classType, Utf8JsonWriter writer, bool writeNull = false) + { + if (JsonPropertyInfo?._escapedName != null) + { + WriteObjectOrArrayStart(classType, JsonPropertyInfo?._escapedName, writer, writeNull); + } + else if (KeyName != null) + { + byte[] pooledKey = null; + byte[] utf8Key = Encoding.UTF8.GetBytes(KeyName); + int length = JsonWriterHelper.GetMaxEscapedLength(utf8Key.Length, 0); + + Span escapedKey = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (pooledKey = ArrayPool.Shared.Rent(length)); + + JsonWriterHelper.EscapeString(utf8Key, escapedKey, 0, out int written); + Span propertyName = escapedKey.Slice(0, written); + + WriteObjectOrArrayStart(classType, propertyName, writer, writeNull); + + if (pooledKey != null) + { + ArrayPool.Shared.Return(pooledKey); + } + } + else + { + Debug.Assert(writeNull == false); + + // Write start without a property name. + if (classType == ClassType.Object || classType == ClassType.Dictionary) + { + writer.WriteStartObject(); + StartObjectWritten = true; + } + else + { + Debug.Assert(classType == ClassType.Enumerable); + writer.WriteStartArray(); + } + } + } + + private void WriteObjectOrArrayStart(ClassType classType, ReadOnlySpan propertyName, Utf8JsonWriter writer, bool writeNull) + { + if (writeNull) + { + writer.WriteNull(propertyName); + } + else if (classType == ClassType.Object || classType == ClassType.Dictionary) + { + writer.WriteStartObject(propertyName); + StartObjectWritten = true; + } + else + { + Debug.Assert(classType == ClassType.Enumerable); + writer.WriteStartArray(propertyName); + } + } + + public void Reset() { CurrentValue = null; + Enumerator = null; + KeyName = null; JsonClassInfo = null; + JsonPropertyInfo = null; + PropertyIndex = 0; + PopStackOnEndObject = false; + PopStackOnEnd = false; StartObjectWritten = false; - EndObject(); - EndArray(); } - internal void EndObject() + public void EndObject() { PropertyIndex = 0; PopStackOnEndObject = false; - EndProperty(); + JsonPropertyInfo = null; } - internal void EndDictionary() + public void EndDictionary() { Enumerator = null; - EndProperty(); + PopStackOnEnd = false; } - internal void EndArray() + public void EndArray() { Enumerator = null; - PopStackOnEndArray = false; - EndProperty(); - } - - internal void EndProperty() - { + PopStackOnEnd = false; JsonPropertyInfo = null; } - internal void NextProperty() + public void NextProperty() { JsonPropertyInfo = null; PropertyIndex++; diff --git a/src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs b/src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs index 13c7a09..92fdeaa 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs @@ -10,50 +10,221 @@ namespace System.Text.Json.Serialization.Tests public static partial class DictionaryTests { [Fact] - public static void DirectReturn() + public static void DictionaryOfString() { + const string JsonString = @"{""Hello"":""World"",""Hello2"":""World2""}"; + { - Dictionary obj = JsonSerializer.Parse>(@"{""Hello"":""World"", ""Hello2"":""World2""}"); + Dictionary obj = JsonSerializer.Parse>(JsonString); Assert.Equal("World", obj["Hello"]); Assert.Equal("World2", obj["Hello2"]); string json = JsonSerializer.ToString(obj); - Assert.Equal(@"{""Hello"":""World"",""Hello2"":""World2""}", json); + Assert.Equal(JsonString, json); - // Round-trip the json - obj = JsonSerializer.Parse>(json); - Assert.Equal("World", obj["Hello"]); - Assert.Equal("World2", obj["Hello2"]); + json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); } { - IDictionary obj = JsonSerializer.Parse>(@"{""Hello"":""World""}"); + IDictionary obj = JsonSerializer.Parse>(JsonString); Assert.Equal("World", obj["Hello"]); + Assert.Equal("World2", obj["Hello2"]); string json = JsonSerializer.ToString(obj); - Assert.Equal(@"{""Hello"":""World""}", json); + Assert.Equal(JsonString, json); - obj = JsonSerializer.Parse>(json); - Assert.Equal("World", obj["Hello"]); + json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); } { - IReadOnlyDictionary obj = JsonSerializer.Parse>(@"{""Hello"":""World""}"); + IReadOnlyDictionary obj = JsonSerializer.Parse>(JsonString); Assert.Equal("World", obj["Hello"]); + Assert.Equal("World2", obj["Hello2"]); string json = JsonSerializer.ToString(obj); - Assert.Equal(@"{""Hello"":""World""}", json); + Assert.Equal(JsonString, json); - obj = JsonSerializer.Parse>(json); - Assert.Equal("World", obj["Hello"]); + json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); } } [Fact] - public static void ThrowsOnDuplicateKeys() + public static void DuplicateKeysFail() { // todo: this should throw a JsonReaderException - Assert.Throws(() => JsonSerializer.Parse>(@"{""Hello"":""World"", ""Hello"":""World""}")); + Assert.Throws(() => JsonSerializer.Parse>( + @"{""Hello"":""World"", ""Hello"":""World""}")); + } + + [Fact] + public static void DictionaryOfObjectFail() + { + Assert.Throws(() => JsonSerializer.Parse>(@"{""Key1"":1")); + } + + [Fact] + public static void FirstGenericArgNotStringFail() + { + Assert.Throws(() => JsonSerializer.Parse>(@"{""Key1"":1}")); + } + + [Fact] + public static void DictionaryOfList() + { + const string JsonString = @"{""Key1"":[1,2],""Key2"":[3,4]}"; + + IDictionary> obj = JsonSerializer.Parse>>(JsonString); + + Assert.Equal(2, obj.Count); + Assert.Equal(2, obj["Key1"].Count); + Assert.Equal(1, obj["Key1"][0]); + Assert.Equal(2, obj["Key1"][1]); + Assert.Equal(2, obj["Key2"].Count); + Assert.Equal(3, obj["Key2"][0]); + Assert.Equal(4, obj["Key2"][1]); + + + string json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + } + + [Fact] + public static void DictionaryOfArray() + { + const string JsonString = @"{""Key1"":[1,2],""Key2"":[3,4]}"; + Dictionary obj = JsonSerializer.Parse>(JsonString); + + Assert.Equal(2, obj.Count); + Assert.Equal(2, obj["Key1"].Length); + Assert.Equal(1, obj["Key1"][0]); + Assert.Equal(2, obj["Key1"][1]); + Assert.Equal(2, obj["Key2"].Length); + Assert.Equal(3, obj["Key2"][0]); + Assert.Equal(4, obj["Key2"][1]); + + string json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + } + + [Fact] + public static void ListOfDictionary() + { + const string JsonString = @"[{""Key1"":1,""Key2"":2},{""Key1"":3,""Key2"":4}]"; + List> obj = JsonSerializer.Parse>>(JsonString); + + Assert.Equal(2, obj.Count); + Assert.Equal(2, obj[0].Count); + Assert.Equal(1, obj[0]["Key1"]); + Assert.Equal(2, obj[0]["Key2"]); + Assert.Equal(2, obj[1].Count); + Assert.Equal(3, obj[1]["Key1"]); + Assert.Equal(4, obj[1]["Key2"]); + + string json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + + json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + } + + [Fact] + public static void ArrayOfDictionary() + { + const string JsonString = @"[{""Key1"":1,""Key2"":2},{""Key1"":3,""Key2"":4}]"; + Dictionary[] obj = JsonSerializer.Parse[]>(JsonString); + + Assert.Equal(2, obj.Length); + Assert.Equal(2, obj[0].Count); + Assert.Equal(1, obj[0]["Key1"]); + Assert.Equal(2, obj[0]["Key2"]); + Assert.Equal(2, obj[1].Count); + Assert.Equal(3, obj[1]["Key1"]); + Assert.Equal(4, obj[1]["Key2"]); + + string json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + + json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + } + + [Fact] + public static void DictionaryOfDictionary() + { + const string JsonString = @"{""Key1"":{""Key1a"":1,""Key1b"":2},""Key2"":{""Key2a"":3,""Key2b"":4}}"; + Dictionary> obj = JsonSerializer.Parse>>(JsonString); + + Assert.Equal(2, obj.Count); + Assert.Equal(2, obj["Key1"].Count); + Assert.Equal(1, obj["Key1"]["Key1a"]); + Assert.Equal(2, obj["Key1"]["Key1b"]); + Assert.Equal(2, obj["Key2"].Count); + Assert.Equal(3, obj["Key2"]["Key2a"]); + Assert.Equal(4, obj["Key2"]["Key2b"]); + + string json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + + json = JsonSerializer.ToString(obj); + Assert.Equal(JsonString, json); + } + + [Fact] + public static void DictionaryOfClasses() + { + Dictionary obj; + + { + string json = @"{""Key1"":" + SimpleTestClass.s_json + @",""Key2"":" + SimpleTestClass.s_json + "}"; + obj = JsonSerializer.Parse>(json); + Assert.Equal(2, obj.Count); + obj["Key1"].Verify(); + obj["Key2"].Verify(); + } + + { + // We can't compare against the json string above because property ordering is not deterministic (based on reflection order) + // so just round-trip the json and compare. + string json = JsonSerializer.ToString(obj); + obj = JsonSerializer.Parse>(json); + Assert.Equal(2, obj.Count); + obj["Key1"].Verify(); + obj["Key2"].Verify(); + } + + { + string json = JsonSerializer.ToString(obj); + obj = JsonSerializer.Parse>(json); + Assert.Equal(2, obj.Count); + obj["Key1"].Verify(); + obj["Key2"].Verify(); + } + } + + [Fact] + public static void CamelCaseOption() + { + var options = new JsonSerializerOptions(); + options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; + + const string JsonString = @"[{""Key1"":1,""Key2"":2},{""Key1"":3,""Key2"":4}]"; + Dictionary[] obj = JsonSerializer.Parse[]>(JsonString, options); + + Assert.Equal(2, obj.Length); + Assert.Equal(1, obj[0]["key1"]); + Assert.Equal(2, obj[0]["key2"]); + Assert.Equal(3, obj[1]["key1"]); + Assert.Equal(4, obj[1]["key2"]); + + const string JsonCamel = @"[{""key1"":1,""key2"":2},{""key1"":3,""key2"":4}]"; + string jsonCamel = JsonSerializer.ToString(obj); + Assert.Equal(JsonCamel, jsonCamel); + + jsonCamel = JsonSerializer.ToString(obj, options); + Assert.Equal(JsonCamel, jsonCamel); } [Fact] diff --git a/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs index 3c9ac76..e6021dd 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs @@ -32,7 +32,7 @@ namespace System.Text.Json.Serialization.Tests json = JsonSerializer.ToString(null, typeof(object)); Assert.Equal(@"null", json); - Decimal pi = 3.1415926535897932384626433833m; + decimal pi = 3.1415926535897932384626433833m; json = JsonSerializer.ToString(pi); Assert.Equal(@"3.1415926535897932384626433833", json); json = JsonSerializer.ToString(pi, typeof(object)); @@ -59,13 +59,18 @@ namespace System.Text.Json.Serialization.Tests { const string ExpectedJson = @"[1,true,{""City"":""MyCity""},null,""foo""]"; - Address address = new Address(); + var address = new Address(); address.Initialize(); - object[] array = new object[] { 1, true, address, null, "foo" }; + var array = new object[] { 1, true, address, null, "foo" }; string json = JsonSerializer.ToString(array); Assert.Equal(ExpectedJson, json); + var dictionary = new Dictionary { { "City", "MyCity" } }; + var arrayWithDictionary = new object[] { 1, true, dictionary, null, "foo" }; + json = JsonSerializer.ToString(arrayWithDictionary); + Assert.Equal(ExpectedJson, json); + json = JsonSerializer.ToString(array); Assert.Equal(ExpectedJson, json); diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithNullables.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithNullables.cs index ac6953d..698c0e6 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithNullables.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithNullables.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 @@ -42,6 +43,7 @@ namespace System.Text.Json.Serialization.Tests public DateTime?[] MyDateTimeArray { get; set; } public DateTimeOffset?[] MyDateTimeOffsetArray { get; set; } public SampleEnum?[] MyEnumArray { get; set; } + public Dictionary MyStringToStringDict { get; set; } } public class SimpleTestClassWithNulls : SimpleBaseClassWithNullables, ITestClass @@ -87,6 +89,7 @@ namespace System.Text.Json.Serialization.Tests Assert.Null(MyDateTimeArray); Assert.Null(MyDateTimeOffsetArray); Assert.Null(MyEnumArray); + Assert.Null(MyStringToStringDict); } public static readonly string s_json = @"{" + @@ -123,7 +126,8 @@ namespace System.Text.Json.Serialization.Tests @"""MyDecimalArray"" : null," + @"""MyDateTimeArray"" : null," + @"""MyDateTimeOffsetArray"" : null," + - @"""MyEnumArray"" : null" + + @"""MyEnumArray"" : null," + + @"""MyStringToStringDict"" : null" + @"}"; public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); @@ -166,7 +170,8 @@ namespace System.Text.Json.Serialization.Tests @"""MyDecimalArray"" : [3.3]," + @"""MyDateTimeArray"" : [""2019-01-30T12:01:02.0000000Z""]," + @"""MyDateTimeOffsetArray"" : [""2019-01-30T12:01:02.0000000+01:00""]," + - @"""MyEnumArray"" : [2]" + + @"""MyEnumArray"" : [2]," + + @"""MyStringToStringDict"" : {""key"" : ""value""}" + @"}"; public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); @@ -208,6 +213,7 @@ namespace System.Text.Json.Serialization.Tests MyDateTimeArray = new DateTime?[] { new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc) }; MyDateTimeOffsetArray = new DateTimeOffset?[] { new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)) }; MyEnumArray = new SampleEnum?[] { SampleEnum.Two }; + MyStringToStringDict = new Dictionary { { "key", "value" } }; } public void Verify() @@ -247,6 +253,7 @@ namespace System.Text.Json.Serialization.Tests Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTimeArray[0]); Assert.Equal(new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)), MyDateTimeOffsetArray[0]); Assert.Equal(SampleEnum.Two, MyEnumArray[0]); + Assert.Equal("value", MyStringToStringDict["key"]); } } }