<Compile Include="System\Text\Json\JsonHelpers.cs" />
<Compile Include="System\Text\Json\JsonHelpers.Date.cs" />
<Compile Include="System\Text\Json\JsonTokenType.cs" />
- <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.HandleDictionary.cs" />
<Compile Include="System\Text\Json\ThrowHelper.cs" />
<Compile Include="System\Text\Json\ThrowHelper.Serialization.cs" />
<Compile Include="System\Text\Json\Document\JsonDocument.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.ByteArray.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Stream.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.cs" />
+ <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.HandleDictionary.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.HandleEnumerable.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.HandleObject.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.String.cs" />
{
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);
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);
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
{
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<TKey, TValue, TSomeExtension>.
{
+
elementType = args[1];
}
- else
+ else if (args.Length >= 1) // It is >= 1 in case there is an IEnumerable<T, TSomeExtension>.
{
+ 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);
}
}
}
other._escapedName = _escapedName;
}
- public abstract object GetValueAsObject(object obj, JsonSerializerOptions options);
+ public abstract object GetValueAsObject(object obj);
public TAttribute GetAttribute<TAttribute>() where TAttribute : Attribute
{
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);
_isPropertyPolicy = true;
HasGetter = true;
HasSetter = true;
-
- if (ClassType == ClassType.Dictionary)
- {
- ValueConverter = DefaultConverters<TRuntimeProperty>.s_converter;
- }
+ ValueConverter = DefaultConverters<TRuntimeProperty>.s_converter;
}
GetPolicies(options);
base.GetPolicies(options);
}
- public override object GetValueAsObject(object obj, JsonSerializerOptions options)
+ public override object GetValueAsObject(object obj)
{
if (_isPropertyPolicy)
{
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;
// 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;
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.
// 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;
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);
}
}
}
setPropertyDirectly = false;
}
- bool valueReturning = state.Current.PopStackOnEndArray;
- if (state.Current.PopStackOnEndArray)
+ bool valueReturning = state.Current.PopStackOnEnd;
+ if (state.Current.PopStackOnEnd)
{
state.Pop();
}
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;
// 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)
{
((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);
}
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);
}
}
JsonSerializerOptions options,
ref ReadStackFrame frame)
{
- if (frame.IsEnumerable())
+ if (frame.IsEnumerable)
{
if (frame.TempEnumerableValues != null)
{
((IList<TProperty>)frame.ReturnValue).Add(value);
}
}
- else if (frame.IsPropertyEnumerable())
+ else if (frame.IsPropertyEnumerable)
{
Debug.Assert(frame.JsonPropertyInfo != null);
Debug.Assert(frame.ReturnValue != null);
}
else
{
- ((IList<TProperty>)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue, options)).Add(value);
+ ((IList<TProperty>)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue)).Add(value);
}
}
- else if (frame.IsDictionary())
+ else if (frame.IsDictionary)
{
- // todo: use TryAdd and throw JsonReaderException
- ((IDictionary<string, TProperty>)frame.ReturnValue).Add(frame.KeyName, value);
- }
- else if (frame.IsPropertyADictionary())
- {
- Debug.Assert(frame.JsonPropertyInfo != null);
Debug.Assert(frame.ReturnValue != null);
- ((IDictionary<string, TProperty>)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue, options)).Add(frame.KeyName, value);
+ ((IDictionary<string, TProperty>)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);
}
}
}
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;
if (!propertyInfo.IgnoreNullValues)
{
- state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null, options);
+ state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value : null);
}
return false;
// 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())
{
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 <string> 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;
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;
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)
}
else if (tokenType == JsonTokenType.StartObject)
{
- HandleStartObject(options, ref state);
+ HandleStartObject(options, ref reader, ref state);
}
else if (tokenType == JsonTokenType.EndObject)
{
Utf8JsonWriter writer,
ref WriteStack state)
{
- Debug.Assert(state.Current.JsonPropertyInfo.ClassType == ClassType.Dictionary);
-
JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
if (!jsonPropertyInfo.ShouldSerialize)
{
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())
// 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);
}
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;
// We are done enumerating.
writer.WriteEndObject();
- state.Current.EndDictionary();
+ if (state.Current.PopStackOnEnd)
+ {
+ state.Pop();
+ }
+ else
+ {
+ state.Current.EndDictionary();
+ }
return true;
}
}
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<byte> escapedKey = length <= JsonConstants.StackallocThreshold ?
- stackalloc byte[length] :
- (pooledKey = ArrayPool<byte>.Shared.Rent(length));
+ Span<byte> escapedKey = length <= JsonConstants.StackallocThreshold ?
+ stackalloc byte[length] :
+ (pooledKey = ArrayPool<byte>.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<byte>(pooledKey, 0, written).Clear();
- ArrayPool<byte>.Shared.Return(pooledKey);
+ if (pooledKey != null)
+ {
+ // We clear the array because it is "user data" (although a property name).
+ new Span<byte>(pooledKey, 0, written).Clear();
+ ArrayPool<byte>.Shared.Return(pooledKey);
+ }
}
#endif
}
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())
// We are done enumerating.
writer.WriteEndArray();
- if (state.Current.PopStackOnEndArray)
+ if (state.Current.PopStackOnEnd)
{
state.Pop();
}
// 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.
// 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);
}
// A property that returns an object.
if (!obtainedValue)
{
- currentValue = jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue, options);
+ currentValue = jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue);
}
if (currentValue != null)
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<PropertyRef> PropertyRefCache;
+ public int PropertyIndex;
+ public List<PropertyRef> 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;
}
}
- 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)
{
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);
+ }
}
}
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;
}
}
// 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)
}
}
- 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<byte> escapedKey = length <= JsonConstants.StackallocThreshold ?
+ stackalloc byte[length] :
+ (pooledKey = ArrayPool<byte>.Shared.Rent(length));
+
+ JsonWriterHelper.EscapeString(utf8Key, escapedKey, 0, out int written);
+ Span<byte> propertyName = escapedKey.Slice(0, written);
+
+ WriteObjectOrArrayStart(classType, propertyName, writer, writeNull);
+
+ if (pooledKey != null)
+ {
+ ArrayPool<byte>.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<byte> 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++;
public static partial class DictionaryTests
{
[Fact]
- public static void DirectReturn()
+ public static void DictionaryOfString()
{
+ const string JsonString = @"{""Hello"":""World"",""Hello2"":""World2""}";
+
{
- Dictionary<string, string> obj = JsonSerializer.Parse<Dictionary<string, string>>(@"{""Hello"":""World"", ""Hello2"":""World2""}");
+ Dictionary<string, string> obj = JsonSerializer.Parse<Dictionary<string, string>>(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<Dictionary<string, string>>(json);
- Assert.Equal("World", obj["Hello"]);
- Assert.Equal("World2", obj["Hello2"]);
+ json = JsonSerializer.ToString<object>(obj);
+ Assert.Equal(JsonString, json);
}
{
- IDictionary<string, string> obj = JsonSerializer.Parse<IDictionary<string, string>>(@"{""Hello"":""World""}");
+ IDictionary<string, string> obj = JsonSerializer.Parse<IDictionary<string, string>>(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<Dictionary<string, string>>(json);
- Assert.Equal("World", obj["Hello"]);
+ json = JsonSerializer.ToString<object>(obj);
+ Assert.Equal(JsonString, json);
}
{
- IReadOnlyDictionary<string, string> obj = JsonSerializer.Parse<IReadOnlyDictionary<string, string>>(@"{""Hello"":""World""}");
+ IReadOnlyDictionary<string, string> obj = JsonSerializer.Parse<IReadOnlyDictionary<string, string>>(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<Dictionary<string, string>>(json);
- Assert.Equal("World", obj["Hello"]);
+ json = JsonSerializer.ToString<object>(obj);
+ Assert.Equal(JsonString, json);
}
}
[Fact]
- public static void ThrowsOnDuplicateKeys()
+ public static void DuplicateKeysFail()
{
// todo: this should throw a JsonReaderException
- Assert.Throws<ArgumentException>(() => JsonSerializer.Parse<Dictionary<string, string>>(@"{""Hello"":""World"", ""Hello"":""World""}"));
+ Assert.Throws<ArgumentException>(() => JsonSerializer.Parse<Dictionary<string, string>>(
+ @"{""Hello"":""World"", ""Hello"":""World""}"));
+ }
+
+ [Fact]
+ public static void DictionaryOfObjectFail()
+ {
+ Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<Dictionary<string, object>>(@"{""Key1"":1"));
+ }
+
+ [Fact]
+ public static void FirstGenericArgNotStringFail()
+ {
+ Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<Dictionary<int, int>>(@"{""Key1"":1}"));
+ }
+
+ [Fact]
+ public static void DictionaryOfList()
+ {
+ const string JsonString = @"{""Key1"":[1,2],""Key2"":[3,4]}";
+
+ IDictionary<string, List<int>> obj = JsonSerializer.Parse<IDictionary<string, List<int>>>(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<string, int[]> obj = JsonSerializer.Parse<Dictionary<string, int[]>>(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<Dictionary<string, int>> obj = JsonSerializer.Parse<List<Dictionary<string, int>>>(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<object>(obj);
+ Assert.Equal(JsonString, json);
+ }
+
+ [Fact]
+ public static void ArrayOfDictionary()
+ {
+ const string JsonString = @"[{""Key1"":1,""Key2"":2},{""Key1"":3,""Key2"":4}]";
+ Dictionary<string, int>[] obj = JsonSerializer.Parse<Dictionary<string, int>[]>(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<object>(obj);
+ Assert.Equal(JsonString, json);
+ }
+
+ [Fact]
+ public static void DictionaryOfDictionary()
+ {
+ const string JsonString = @"{""Key1"":{""Key1a"":1,""Key1b"":2},""Key2"":{""Key2a"":3,""Key2b"":4}}";
+ Dictionary<string, Dictionary<string, int>> obj = JsonSerializer.Parse<Dictionary<string, Dictionary<string, int>>>(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<object>(obj);
+ Assert.Equal(JsonString, json);
+ }
+
+ [Fact]
+ public static void DictionaryOfClasses()
+ {
+ Dictionary<string, SimpleTestClass> obj;
+
+ {
+ string json = @"{""Key1"":" + SimpleTestClass.s_json + @",""Key2"":" + SimpleTestClass.s_json + "}";
+ obj = JsonSerializer.Parse<Dictionary<string, SimpleTestClass>>(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<Dictionary<string, SimpleTestClass>>(json);
+ Assert.Equal(2, obj.Count);
+ obj["Key1"].Verify();
+ obj["Key2"].Verify();
+ }
+
+ {
+ string json = JsonSerializer.ToString<object>(obj);
+ obj = JsonSerializer.Parse<Dictionary<string, SimpleTestClass>>(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<string, int>[] obj = JsonSerializer.Parse<Dictionary<string, int>[]>(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<object>(obj);
+ Assert.Equal(JsonCamel, jsonCamel);
+
+ jsonCamel = JsonSerializer.ToString<object>(obj, options);
+ Assert.Equal(JsonCamel, jsonCamel);
}
[Fact]
json = JsonSerializer.ToString(null, typeof(object));
Assert.Equal(@"null", json);
- Decimal pi = 3.1415926535897932384626433833m;
+ decimal pi = 3.1415926535897932384626433833m;
json = JsonSerializer.ToString<object>(pi);
Assert.Equal(@"3.1415926535897932384626433833", json);
json = JsonSerializer.ToString(pi, typeof(object));
{
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<string, string> { { "City", "MyCity" } };
+ var arrayWithDictionary = new object[] { 1, true, dictionary, null, "foo" };
+ json = JsonSerializer.ToString(arrayWithDictionary);
+ Assert.Equal(ExpectedJson, json);
+
json = JsonSerializer.ToString<object>(array);
Assert.Equal(ExpectedJson, json);
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Collections.Generic;
using Xunit;
namespace System.Text.Json.Serialization.Tests
public DateTime?[] MyDateTimeArray { get; set; }
public DateTimeOffset?[] MyDateTimeOffsetArray { get; set; }
public SampleEnum?[] MyEnumArray { get; set; }
+ public Dictionary<string, string> MyStringToStringDict { get; set; }
}
public class SimpleTestClassWithNulls : SimpleBaseClassWithNullables, ITestClass
Assert.Null(MyDateTimeArray);
Assert.Null(MyDateTimeOffsetArray);
Assert.Null(MyEnumArray);
+ Assert.Null(MyStringToStringDict);
}
public static readonly string s_json =
@"{" +
@"""MyDecimalArray"" : null," +
@"""MyDateTimeArray"" : null," +
@"""MyDateTimeOffsetArray"" : null," +
- @"""MyEnumArray"" : null" +
+ @"""MyEnumArray"" : null," +
+ @"""MyStringToStringDict"" : null" +
@"}";
public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
@"""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);
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<string, string> { { "key", "value" } };
}
public void Verify()
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"]);
}
}
}