protected JsonAttribute() { }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false)]
+ public sealed partial class JsonExtensionDataAttribute : System.Text.Json.Serialization.JsonAttribute
+ {
+ public JsonExtensionDataAttribute() { }
+ }
+ [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false)]
public sealed partial class JsonIgnoreAttribute : System.Text.Json.Serialization.JsonAttribute
{
public JsonIgnoreAttribute() { }
<data name="DeserializeTypeNotSupported" xml:space="preserve">
<value>Deserialization of type {0} is not supported.</value>
</data>
+ <data name="SerializationDataExtensionPropertyInvalid" xml:space="preserve">
+ <value>The data extension property '{0}.{1}' does not match the required signature of IDictionary<string, JsonElement> or IDictionary<string, object>.</value>
+ </data>
+ <data name="SerializationDuplicateAttribute" xml:space="preserve">
+ <value>A class cannot have more than one property that has the attribute '{0}'.</value>
+ </data>
+ <data name="SerializationNotSupportedCollectionType" xml:space="preserve">
+ <value>The collection type '{0}' is not supported.</value>
+ </data>
+ <data name="SerializationDataExtensionPropertyInvalidElement" xml:space="preserve">
+ <value>The data extension property '{0}.{1}' cannot contain dictionary values of type '{2}'. Dictionary values must be of type JsonElement.</value>
+ </data>
+ <data name="SerializationNotSupportedCollection" xml:space="preserve">
+ <value>The collection type '{0}' on '{1}' is not supported.</value>
+ </data>
</root>
\ No newline at end of file
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.cs" />
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.AddProperty.cs" />
<Compile Include="System\Text\Json\Serialization\JsonEnumerableConverter.cs" />
+ <Compile Include="System\Text\Json\Serialization\JsonExtensionDataAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\JsonIgnoreAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\JsonNamingPolicy.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyInfo.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyInfoNullable.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyNameAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleArray.cs" />
+ <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleDictionary.cs" />
+ <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleObject.cs" />
+ <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandlePropertyName.cs" />
+ <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleValue.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Helpers.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Stream.cs" />
- <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleValue.cs" />
- <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleObject.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.String.cs" />
- <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Helpers.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleNull.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Span.cs" />
- <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.ByteArray.cs" />
- <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Stream.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.cs" />
+ <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.ByteArray.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.Helpers.cs" />
+ <Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Stream.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.String.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializerOptions.cs" />
<Compile Include="System\Text\Json\Serialization\Policies\JsonValueConverter.cs" />
/// Otherwise, returns <see langword="false"/>.
/// </summary>
public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0';
+
+ /// <summary>
+ /// Calls Encoding.UTF8.GetString that supports netstandard.
+ /// </summary>
+ /// <param name="bytes">The utf8 bytes to convert.</param>
+ /// <returns></returns>
+ internal static string Utf8GetString(ReadOnlySpan<byte> bytes)
+ {
+ return Encoding.UTF8.GetString(bytes
+#if netstandard
+ .ToArray()
+#endif
+ );
+ }
}
}
internal static JsonPropertyInfo CreateProperty(Type declaredPropertyType, Type runtimePropertyType, PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options)
{
+ bool hasIgnoreAttribute = (JsonPropertyInfo.GetAttribute<JsonIgnoreAttribute>(propertyInfo) != null);
+ if (hasIgnoreAttribute)
+ {
+ return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(propertyInfo, options);
+ }
+
Type collectionElementType = null;
switch (GetClassType(runtimePropertyType))
{
case ClassType.Enumerable:
case ClassType.Dictionary:
case ClassType.Unknown:
- collectionElementType = GetElementType(runtimePropertyType);
+ collectionElementType = GetElementType(runtimePropertyType, parentClassType, propertyInfo);
break;
}
JsonPropertyInfo jsonInfo = (JsonPropertyInfo)Activator.CreateInstance(
propertyInfoClassType,
BindingFlags.Instance | BindingFlags.Public,
- binder: null,
- new object[] { parentClassType, declaredPropertyType, runtimePropertyType, propertyInfo, collectionElementType, options },
+ binder: null,
+ args: null,
culture: null);
+ jsonInfo.Initialize(parentClassType, declaredPropertyType, runtimePropertyType, propertyInfo, collectionElementType, options);
+
return jsonInfo;
}
namespace System.Text.Json.Serialization
{
- [DebuggerDisplay("ClassType.{ClassType} {Type.Name}")]
+ [DebuggerDisplay("ClassType.{ClassType}, {Type.Name}")]
internal sealed partial class JsonClassInfo
{
// The length of the property name embedded in the key (in bytes).
internal ClassType ClassType { get; private set; }
+ public JsonPropertyInfo DataExtensionProperty { get; private set; }
+
// If enumerable, the JsonClassInfo for the element type.
internal JsonClassInfo ElementClassInfo { get; private set; }
// Todo: when using PropertyNameCaseInsensitive we also need to use the hashtable with case-insensitive
// comparison to handle Turkish etc. cultures properly.
+ Debug.Assert(_propertyRefs != null);
+
// Set the sorted property cache. Overwrite any existing cache which can occur in multi-threaded cases.
if (frame.PropertyRefCache != null)
{
for (int iProperty = 0; iProperty < _propertyRefs.Count; iProperty++)
{
PropertyRef propertyRef = _propertyRefs[iProperty];
-
bool found = false;
int iCacheProperty = 0;
+
for (; iCacheProperty < cache.Count; iCacheProperty++)
{
if (IsPropertyRefEqual(ref propertyRef, cache[iCacheProperty]))
{
JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options);
- if (jsonPropertyInfo.NameAsString == null)
- {
- ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(this, jsonPropertyInfo);
- }
+ Debug.Assert(jsonPropertyInfo.NameUsedToCompareAsString != null);
// If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception.
if (!propertyNames.Add(jsonPropertyInfo.NameUsedToCompareAsString))
jsonPropertyInfo.ClearUnusedValuesAfterAdd();
}
}
+
+ DetermineExtensionDataProperty();
break;
+
case ClassType.Enumerable:
case ClassType.Dictionary:
// Add a single property that maps to the class type so we can have policies applied.
CreateObject = options.ClassMaterializerStrategy.CreateConstructor(policyProperty.RuntimePropertyType);
// Create a ClassInfo that maps to the element type which is used for (de)serialization and policies.
- Type elementType = GetElementType(type);
+ Type elementType = GetElementType(type, parentType : null, memberInfo: null);
ElementClassInfo = options.GetOrAddClass(elementType);
break;
case ClassType.Value:
}
}
+ private void DetermineExtensionDataProperty()
+ {
+ JsonPropertyInfo jsonPropertyInfo = GetPropertyThatHasAttribute(typeof(JsonExtensionDataAttribute));
+ if (jsonPropertyInfo != null)
+ {
+ Type declaredPropertyType = jsonPropertyInfo.DeclaredPropertyType;
+ if (!typeof(IDictionary<string, JsonElement>).IsAssignableFrom(declaredPropertyType) &&
+ !typeof(IDictionary<string, object>).IsAssignableFrom(declaredPropertyType))
+ {
+ ThrowHelper.ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(this, jsonPropertyInfo);
+ }
+
+ DataExtensionProperty = jsonPropertyInfo;
+ }
+ }
+
+ private JsonPropertyInfo GetPropertyThatHasAttribute(Type attributeType)
+ {
+ Debug.Assert(_propertyRefs != null);
+
+ JsonPropertyInfo property = null;
+
+ for (int iProperty = 0; iProperty < _propertyRefs.Count; iProperty++)
+ {
+ PropertyRef propertyRef = _propertyRefs[iProperty];
+ JsonPropertyInfo jsonPropertyInfo = propertyRef.Info;
+ Attribute attribute = jsonPropertyInfo.PropertyInfo.GetCustomAttribute(attributeType);
+ if (attribute != null)
+ {
+ if (property != null)
+ {
+ ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateAttribute(attributeType);
+ }
+
+ property = jsonPropertyInfo;
+ }
+ }
+
+ return property;
+ }
+
internal JsonPropertyInfo GetProperty(JsonSerializerOptions options, ReadOnlySpan<byte> propertyName, ref ReadStackFrame frame)
{
// If we should compare with case-insensitive, normalize to an uppercase format since that is what is cached on the propertyInfo.
if (options.PropertyNameCaseInsensitive)
{
- string utf16PropertyName = Encoding.UTF8.GetString(propertyName.ToArray());
+ string utf16PropertyName = JsonHelpers.Utf8GetString(propertyName);
string upper = utf16PropertyName.ToUpperInvariant();
propertyName = Encoding.UTF8.GetBytes(upper);
}
if (!hasPropertyCache)
{
- if (propertyIndex == 0)
+ if (propertyIndex == 0 && frame.PropertyRefCache == null)
{
// Create the temporary list on first property access to prevent a partially filled List.
- Debug.Assert(frame.PropertyRefCache == null);
frame.PropertyRefCache = new List<PropertyRef>();
}
if (propertyRef.Key == other.Key)
{
if (propertyRef.Info.Name.Length <= PropertyNameKeyLength ||
- propertyRef.Info.Name.SequenceEqual(other.Info.Name))
+ propertyRef.Info.Name.AsSpan().SequenceEqual(other.Info.Name.AsSpan()))
{
return true;
}
return key;
}
- public static Type GetElementType(Type propertyType)
+ // Return the element type of the IEnumerable, or return null if not an IEnumerable.
+ public static Type GetElementType(Type propertyType, Type parentType, MemberInfo memberInfo)
{
- Type elementType = null;
- if (typeof(IEnumerable).IsAssignableFrom(propertyType))
+ if (!typeof(IEnumerable).IsAssignableFrom(propertyType))
+ {
+ return null;
+ }
+
+ // Check for Array.
+ Type elementType = propertyType.GetElementType();
+ if (elementType != null)
+ {
+ return elementType;
+ }
+
+ // Check for Dictionary<TKey, TValue> or IEnumerable<T>
+ if (propertyType.IsGenericType)
{
- elementType = propertyType.GetElementType();
- if (elementType == null)
+ Type[] args = propertyType.GetGenericArguments();
+ ClassType classType = GetClassType(propertyType);
+
+ if (classType == ClassType.Dictionary &&
+ args.Length >= 2 && // It is >= 2 in case there is a IDictionary<TKey, TValue, TSomeExtension>.
+ args[0].UnderlyingSystemType == typeof(string))
{
- Type[] args = propertyType.GetGenericArguments();
+ return args[1];
+ }
- if (propertyType.IsGenericType)
- {
- if (GetClassType(propertyType) == ClassType.Dictionary &&
- args.Length >= 2) // It is >= 2 in case there is a Dictionary<TKey, TValue, TSomeExtension>.
- {
- elementType = args[1];
- }
- else if (GetClassType(propertyType) == ClassType.Enumerable && args.Length >= 1) // It is >= 1 in case there is an IEnumerable<T, TSomeExtension>.
- {
- elementType = args[0];
- }
- }
- else
- {
- // Unable to determine collection type; attempt to use object which will be used to create loosely-typed collection.
- elementType = typeof(object);
- }
+ if (classType == ClassType.Enumerable && args.Length >= 1) // It is >= 1 in case there is an IEnumerable<T, TSomeExtension>.
+ {
+ return args[0];
}
}
- return elementType;
+ throw ThrowHelper.GetNotSupportedException_SerializationNotSupportedCollection(propertyType, parentType, memberInfo);
}
internal static ClassType GetClassType(Type type)
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Text.Json.Serialization
+{
+ /// <summary>
+ /// When placed on a property of type <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/>, any
+ /// properties that do not have a matching member are added to that Dictionary during deserialization and written during serialization.
+ /// </summary>
+ /// <remarks>
+ /// The TKey value must be <see cref="string"/> and TValue must be <see cref="JsonElement"/> or <see cref="object"/>.
+ /// If there is more than one extension property on a type, or it the property is not of the correct type,
+ /// an <see cref="InvalidOperationException"/> is thrown during the first serialization or deserialization of that type.
+ /// </remarks>
+ [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
+ public sealed class JsonExtensionDataAttribute : JsonAttribute
+ {
+ }
+}
namespace System.Text.Json.Serialization
{
- [DebuggerDisplay("{PropertyInfo}")]
+ [DebuggerDisplay("PropertyInfo={PropertyInfo}, Element={ElementClassInfo}")]
internal abstract class JsonPropertyInfo
{
// Cache the converters so they don't get created for every enumerable property.
private static readonly JsonEnumerableConverter s_jsonIEnumerableConstuctibleConverter = new DefaultIEnumerableConstructibleConverter();
private static readonly JsonEnumerableConverter s_jsonImmutableConverter = new DefaultImmutableConverter();
+ public static readonly JsonPropertyInfo s_missingProperty = new JsonPropertyInfoNotNullable<object, object, object>();
+
public ClassType ClassType;
// The name of the property with any casing policy or the name specified from JsonPropertyNameAttribute.
- private byte[] _name { get; set; }
- public ReadOnlySpan<byte> Name => _name;
+ public byte[] Name { get; private set; }
public string NameAsString { get; private set; }
// Used to support case-insensitive comparison
- private byte[] _nameUsedToCompare { get; set; }
- public ReadOnlySpan<byte> NameUsedToCompare => _nameUsedToCompare;
+ public byte[] NameUsedToCompare { get; private set; }
public string NameUsedToCompareAsString { get; private set; }
// The escaped name passed to the writer.
- public byte[] _escapedName { get; private set; }
+ public byte[] EscapedName { get; private set; }
public bool HasGetter { get; set; }
public bool HasSetter { get; set; }
public bool ShouldSerialize { get; private set; }
public bool ShouldDeserialize { get; private set; }
+ public bool IsPropertyPolicy {get; protected set;}
public bool IgnoreNullValues { get; private set; }
// todo: to minimize hashtable lookups, cache JsonClassInfo:
//public JsonClassInfo ClassInfo;
- // Constructor used for internal identifiers
- public JsonPropertyInfo() { }
-
- public JsonPropertyInfo(
+ public virtual void Initialize(
Type parentClassType,
Type declaredPropertyType,
Type runtimePropertyType,
private void DeterminePropertyName(JsonSerializerOptions options)
{
- if (PropertyInfo != null)
+ if (PropertyInfo == null)
{
- JsonPropertyNameAttribute nameAttribute = GetAttribute<JsonPropertyNameAttribute>();
- if (nameAttribute != null)
- {
- NameAsString = nameAttribute.Name;
+ return;
+ }
- // null is not valid; JsonClassInfo throws an InvalidOperationException after this return.
- if (NameAsString == null)
- {
- return;
- }
- }
- else if (options.PropertyNamingPolicy != null)
+ JsonPropertyNameAttribute nameAttribute = GetAttribute<JsonPropertyNameAttribute>(PropertyInfo);
+ if (nameAttribute != null)
+ {
+ string name = nameAttribute.Name;
+ if (name == null)
{
- NameAsString = options.PropertyNamingPolicy.ConvertName(PropertyInfo.Name);
-
- // null is not valid; JsonClassInfo throws an InvalidOperationException after this return.
- if (NameAsString == null)
- {
- return;
- }
+ ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(ParentClassType, this);
}
- else
+
+ NameAsString = name;
+ }
+ else if (options.PropertyNamingPolicy != null)
+ {
+ string name = options.PropertyNamingPolicy.ConvertName(PropertyInfo.Name);
+ if (name == null)
{
- NameAsString = PropertyInfo.Name;
+ ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(ParentClassType, this);
}
- // At this point propertyName is valid UTF16, so just call the simple UTF16->UTF8 encoder.
- _name = Encoding.UTF8.GetBytes(NameAsString);
+ NameAsString = name;
+ }
+ else
+ {
+ NameAsString = PropertyInfo.Name;
+ }
- // Set the compare name.
- if (options.PropertyNameCaseInsensitive)
- {
- NameUsedToCompareAsString = NameAsString.ToUpperInvariant();
- _nameUsedToCompare = Encoding.UTF8.GetBytes(NameUsedToCompareAsString);
- }
- else
- {
- NameUsedToCompareAsString = NameAsString;
- _nameUsedToCompare = _name;
- }
+ Debug.Assert(NameAsString != null);
+
+ // At this point propertyName is valid UTF16, so just call the simple UTF16->UTF8 encoder.
+ Name = Encoding.UTF8.GetBytes(NameAsString);
- // Cache the escaped name.
+ // Set the compare name.
+ if (options.PropertyNameCaseInsensitive)
+ {
+ NameUsedToCompareAsString = NameAsString.ToUpperInvariant();
+ NameUsedToCompare = Encoding.UTF8.GetBytes(NameUsedToCompareAsString);
+ }
+ else
+ {
+ NameUsedToCompareAsString = NameAsString;
+ NameUsedToCompare = Name;
+ }
+
+ // Cache the escaped name.
#if true
- // temporary behavior until the writer can accept escaped string.
- _escapedName = _name;
+ // temporary behavior until the writer can accept escaped string.
+ EscapedName = Name;
#else
-
- int valueIdx = JsonWriterHelper.NeedsEscaping(_name);
- if (valueIdx == -1)
- {
- _escapedName = _name;
- }
- else
- {
- byte[] pooledName = null;
- int length = JsonWriterHelper.GetMaxEscapedLength(_name.Length, valueIdx);
+ int valueIdx = JsonWriterHelper.NeedsEscaping(_name);
+ if (valueIdx == -1)
+ {
+ _escapedName = _name;
+ }
+ else
+ {
+ byte[] pooledName = null;
+ int length = JsonWriterHelper.GetMaxEscapedLength(_name.Length, valueIdx);
- Span<byte> escapedName = length <= JsonConstants.StackallocThreshold ?
- stackalloc byte[length] :
- (pooledName = ArrayPool<byte>.Shared.Rent(length));
+ Span<byte> escapedName = length <= JsonConstants.StackallocThreshold ?
+ stackalloc byte[length] :
+ (pooledName = ArrayPool<byte>.Shared.Rent(length));
- JsonWriterHelper.EscapeString(_name, escapedName, 0, out int written);
+ JsonWriterHelper.EscapeString(_name, escapedName, 0, out int written);
- _escapedName = escapedName.Slice(0, written).ToArray();
+ _escapedName = escapedName.Slice(0, written).ToArray();
- if (pooledName != null)
- {
- // We clear the array because it is "user data" (although a property name).
- new Span<byte>(pooledName, 0, written).Clear();
- ArrayPool<byte>.Shared.Return(pooledName);
- }
+ if (pooledName != null)
+ {
+ // We clear the array because it is "user data" (although a property name).
+ new Span<byte>(pooledName, 0, written).Clear();
+ ArrayPool<byte>.Shared.Return(pooledName);
}
-#endif
}
+#endif
}
private void DetermineSerializationCapabilities(JsonSerializerOptions options)
{
- bool hasIgnoreAttribute = (GetAttribute<JsonIgnoreAttribute>() != null);
-
- if (hasIgnoreAttribute)
- {
- // We don't serialize or deserialize.
- return;
- }
-
- if (ClassType != ClassType.Enumerable)
+ if (ClassType != ClassType.Enumerable && ClassType != ClassType.Dictionary)
{
- // We serialize if there is a getter + no [Ignore] attribute + not ignoring readonly properties.
+ // We serialize if there is a getter + not ignoring readonly properties.
ShouldSerialize = HasGetter && (HasSetter || !options.IgnoreReadOnlyProperties);
- // We deserialize if there is a setter + no [Ignore] attribute.
+ // We deserialize if there is a setter.
ShouldDeserialize = HasSetter;
}
else
{
ShouldDeserialize = true;
}
- else if (RuntimePropertyType.IsAssignableFrom(typeof(IList)))
+ else if (!RuntimePropertyType.IsArray &&
+ (typeof(IList).IsAssignableFrom(RuntimePropertyType) || typeof(IDictionary).IsAssignableFrom(RuntimePropertyType)))
{
ShouldDeserialize = true;
}
}
else if (typeof(IEnumerable).IsAssignableFrom(RuntimePropertyType))
{
- Type elementType = JsonClassInfo.GetElementType(RuntimePropertyType);
+ Type elementType = JsonClassInfo.GetElementType(RuntimePropertyType, ParentClassType, PropertyInfo);
// If the property type only has interface(s) exposed by JsonEnumerableT<T> then use JsonEnumerableT as the converter.
if (RuntimePropertyType.IsAssignableFrom(typeof(JsonEnumerableT<>).MakeGenericType(elementType)))
// Copy any settings defined at run-time to the new property.
public void CopyRuntimeSettingsTo(JsonPropertyInfo other)
{
- other._name = _name;
- other._nameUsedToCompare = _nameUsedToCompare;
- other._escapedName = _escapedName;
+ other.Name = Name;
+ other.NameUsedToCompare = NameUsedToCompare;
+ other.EscapedName = EscapedName;
+ }
+
+ // Create a property that is either ignored at run-time. It uses typeof(int) in order to prevent
+ // issues with unsupported types and helps ensure we don't accidently (de)serialize it.
+ public static JsonPropertyInfo CreateIgnoredPropertyPlaceholder(PropertyInfo propertyInfo, JsonSerializerOptions options)
+ {
+ JsonPropertyInfo jsonPropertyInfo = new JsonPropertyInfoNotNullable<int, int, int>();
+ jsonPropertyInfo.PropertyInfo = propertyInfo;
+ jsonPropertyInfo.DeterminePropertyName(options);
+
+ Debug.Assert(!jsonPropertyInfo.ShouldDeserialize);
+ Debug.Assert(!jsonPropertyInfo.ShouldSerialize);
+
+ return jsonPropertyInfo;
}
public abstract object GetValueAsObject(object obj);
- public TAttribute GetAttribute<TAttribute>() where TAttribute : Attribute
+ public static TAttribute GetAttribute<TAttribute>(PropertyInfo propertyInfo) where TAttribute : Attribute
{
- return (TAttribute)PropertyInfo?.GetCustomAttribute(typeof(TAttribute), inherit: false);
+ return (TAttribute)propertyInfo?.GetCustomAttribute(typeof(TAttribute), inherit: false);
}
public abstract IEnumerable CreateImmutableCollectionFromList(string delegateKey, IList sourceList);
/// </summary>
internal abstract class JsonPropertyInfoCommon<TClass, TDeclaredProperty, TRuntimeProperty> : JsonPropertyInfo
{
- public bool _isPropertyPolicy;
public Func<TClass, TDeclaredProperty> Get { get; private set; }
public Action<TClass, TDeclaredProperty> Set { get; private set; }
public JsonValueConverter<TRuntimeProperty> ValueConverter { get; internal set; }
- // Constructor used for internal identifiers
- public JsonPropertyInfoCommon() { }
-
- public JsonPropertyInfoCommon(
+ public override void Initialize(
Type parentClassType,
Type declaredPropertyType,
Type runtimePropertyType,
PropertyInfo propertyInfo,
Type elementType,
- JsonSerializerOptions options) :
- base(parentClassType, declaredPropertyType, runtimePropertyType, propertyInfo, elementType, options)
+ JsonSerializerOptions options)
{
+ base.Initialize(parentClassType, declaredPropertyType, runtimePropertyType, propertyInfo, elementType, options);
+
if (propertyInfo != null)
{
if (propertyInfo.GetMethod?.IsPublic == true)
}
else
{
- _isPropertyPolicy = true;
+ IsPropertyPolicy = true;
HasGetter = true;
HasSetter = true;
ValueConverter = DefaultConverters<TRuntimeProperty>.s_converter;
public override object GetValueAsObject(object obj)
{
- if (_isPropertyPolicy)
+ if (IsPropertyPolicy)
{
return obj;
}
using System.Collections.Generic;
using System.Diagnostics;
-using System.Reflection;
namespace System.Text.Json.Serialization
{
JsonPropertyInfoCommon<TClass, TDeclaredProperty, TRuntimeProperty>
where TRuntimeProperty : TDeclaredProperty
{
- // Constructor used for internal identifiers
- public JsonPropertyInfoNotNullable() { }
-
- public JsonPropertyInfoNotNullable(
- Type parentClassType,
- Type declaredPropertyType,
- Type runtimePropertyType,
- PropertyInfo propertyInfo,
- Type elementType,
- JsonSerializerOptions options) :
- base(parentClassType, declaredPropertyType, runtimePropertyType, propertyInfo, elementType, options)
- {
- }
-
public override void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
{
+ Debug.Assert(ShouldDeserialize);
+
if (ElementClassInfo != null)
{
// Forward the setter to the value-based JsonPropertyInfo.
JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
propertyInfo.ReadEnumerable(tokenType, options, ref state, ref reader);
}
- else if (ShouldDeserialize)
+ else
{
- if (ValueConverter != null)
+ if (ValueConverter != null && ValueConverter.TryRead(RuntimePropertyType, ref reader, out TRuntimeProperty value))
{
- if (ValueConverter.TryRead(RuntimePropertyType, ref reader, out TRuntimeProperty value))
+ if (state.Current.ReturnValue == null)
{
- if (state.Current.ReturnValue == null)
- {
- state.Current.ReturnValue = value;
- }
- else
- {
- // Null values were already handled.
- Debug.Assert(value != null);
-
- Set((TClass)state.Current.ReturnValue, value);
- }
-
- return;
+ state.Current.ReturnValue = value;
}
+ else
+ {
+ // Null values were already handled.
+ Debug.Assert(value != null);
+
+ Set((TClass)state.Current.ReturnValue, value);
+ }
+
+ return;
}
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.PropertyPath);
// If this method is changed, also change JsonPropertyInfoNullable.ReadEnumerable and JsonSerializer.ApplyObjectToEnumerable
public override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
{
+ Debug.Assert(ShouldDeserialize);
+
if (ValueConverter == null || !ValueConverter.TryRead(RuntimePropertyType, ref reader, out TRuntimeProperty value))
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.PropertyPath);
return;
}
- JsonSerializer.ApplyValueToEnumerable(ref value, options, ref state, ref reader);
+ JsonSerializer.ApplyValueToEnumerable(ref value, ref state, ref reader);
}
public override void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
{
Debug.Assert(current.Enumerator == null);
+ Debug.Assert(ShouldSerialize);
- if (ShouldSerialize)
+ TRuntimeProperty value;
+ if (IsPropertyPolicy)
{
- TRuntimeProperty value;
- if (_isPropertyPolicy)
- {
- value = (TRuntimeProperty)current.CurrentValue;
- }
- else
+ value = (TRuntimeProperty)current.CurrentValue;
+ }
+ else
+ {
+ value = (TRuntimeProperty)Get((TClass)current.CurrentValue);
+ }
+
+ if (value == null)
+ {
+ Debug.Assert(EscapedName != null);
+
+ if (!IgnoreNullValues)
{
- value = (TRuntimeProperty)Get((TClass)current.CurrentValue);
+ writer.WriteNull(EscapedName);
}
-
- if (value == null)
+ }
+ else if (ValueConverter != null)
+ {
+ if (EscapedName != null)
{
- Debug.Assert(_escapedName != null);
-
- if (!IgnoreNullValues)
- {
- writer.WriteNull(_escapedName);
- }
+ ValueConverter.Write(EscapedName, value, writer);
}
- else if (ValueConverter != null)
+ else
{
- if (_escapedName != null)
- {
- ValueConverter.Write(_escapedName, value, writer);
- }
- else
- {
- ValueConverter.Write(value, writer);
- }
+ ValueConverter.Write(value, writer);
}
}
}
public override void WriteDictionary(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
{
+ Debug.Assert(ShouldSerialize);
JsonSerializer.WriteDictionary(ValueConverter, options, ref current, writer);
}
public override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
{
+ Debug.Assert(ShouldSerialize);
+
if (ValueConverter != null)
{
Debug.Assert(current.Enumerator != null);
using System.Collections.Generic;
using System.Diagnostics;
-using System.Reflection;
namespace System.Text.Json.Serialization
{
// should this be cached somewhere else so that it's not populated per TClass as well as TProperty?
private static readonly Type s_underlyingType = typeof(TProperty);
- public JsonPropertyInfoNullable(
- Type parentClassType,
- Type declaredPropertyType,
- Type runtimePropertyType,
- PropertyInfo propertyInfo,
- Type elementType,
- JsonSerializerOptions options) :
- base(parentClassType, declaredPropertyType, runtimePropertyType, propertyInfo, elementType, options)
- {
- }
-
public override void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
{
Debug.Assert(ElementClassInfo == null);
+ Debug.Assert(ShouldDeserialize);
- if (ShouldDeserialize)
+ if (ValueConverter != null && ValueConverter.TryRead(s_underlyingType, ref reader, out TProperty value))
{
- if (ValueConverter != null)
+ if (state.Current.ReturnValue == null)
{
- if (ValueConverter.TryRead(s_underlyingType, ref reader, out TProperty value))
- {
- if (state.Current.ReturnValue == null)
- {
- state.Current.ReturnValue = value;
- }
- else
- {
- Set((TClass)state.Current.ReturnValue, value);
- }
-
- return;
- }
+ state.Current.ReturnValue = value;
+ }
+ else
+ {
+ Set((TClass)state.Current.ReturnValue, value);
}
- ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.PropertyPath);
+ return;
}
+
+ ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.PropertyPath);
}
public override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
{
+ Debug.Assert(ShouldDeserialize);
+
if (ValueConverter == null || !ValueConverter.TryRead(typeof(TProperty), ref reader, out TProperty value))
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state.PropertyPath);
}
TProperty? nullableValue = new TProperty?(value);
- JsonSerializer.ApplyValueToEnumerable(ref nullableValue, options, ref state, ref reader);
+ JsonSerializer.ApplyValueToEnumerable(ref nullableValue, ref state, ref reader);
}
public override void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
{
+ Debug.Assert(ShouldSerialize);
+
if (current.Enumerator != null)
{
// Forward the setter to the value-based JsonPropertyInfo.
JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
propertyInfo.WriteEnumerable(options, ref current, writer);
}
- else if (ShouldSerialize)
+ else
{
TProperty? value;
- if (_isPropertyPolicy)
+ if (IsPropertyPolicy)
{
value = (TProperty?)current.CurrentValue;
}
if (value == null)
{
- Debug.Assert(_escapedName != null);
+ Debug.Assert(EscapedName != null);
if (!IgnoreNullValues)
{
- writer.WriteNull(_escapedName);
+ writer.WriteNull(EscapedName);
}
}
else if (ValueConverter != null)
{
- if (_escapedName != null)
+ if (EscapedName != null)
{
- ValueConverter.Write(_escapedName, value.GetValueOrDefault(), writer);
+ ValueConverter.Write(EscapedName, value.GetValueOrDefault(), writer);
}
else
{
public override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
{
+ Debug.Assert(ShouldSerialize);
+
if (ValueConverter != null)
{
Debug.Assert(current.Enumerator != null);
ref Utf8JsonReader reader,
ref ReadStack state)
{
- JsonPropertyInfo jsonPropertyInfo;
+ JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
- jsonPropertyInfo = state.Current.JsonPropertyInfo;
-
- bool skip = jsonPropertyInfo != null && !jsonPropertyInfo.ShouldDeserialize;
- if (skip || state.Current.Skip())
+ if (state.Current.SkipProperty)
{
// The array is not being applied to the object.
state.Push();
jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options);
}
+ // Verify that we don't have a multidimensional array.
Type arrayType = jsonPropertyInfo.RuntimePropertyType;
if (!typeof(IEnumerable).IsAssignableFrom(arrayType) || (arrayType.IsArray && arrayType.GetArrayRank() > 1))
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(arrayType, reader, state.PropertyPath);
}
- Debug.Assert(state.Current.IsPropertyEnumerable || state.Current.IsDictionary);
+ Debug.Assert(state.Current.IsProcessingEnumerableOrDictionary);
- if (state.Current.EnumerableCreated)
+ if (state.Current.PropertyInitialized)
{
// A nested json array so push a new stack frame.
Type elementType = state.Current.JsonClassInfo.ElementClassInfo.GetPolicyProperty().RuntimePropertyType;
state.Push();
-
state.Current.Initialize(elementType, options);
- state.Current.PopStackOnEnd = true;
}
else
{
- state.Current.EnumerableCreated = true;
+ state.Current.PropertyInitialized = true;
}
jsonPropertyInfo = state.Current.JsonPropertyInfo;
private static bool HandleEndArray(
JsonSerializerOptions options,
- ref ReadStack state,
- ref Utf8JsonReader reader)
+ ref Utf8JsonReader reader,
+ ref ReadStack state)
{
bool lastFrame = state.IsLastFrame;
IEnumerable value = ReadStackFrame.GetEnumerableValue(state.Current);
bool setPropertyDirectly;
- bool popStackOnEnd = state.Current.PopStackOnEnd;
if (state.Current.TempEnumerableValues != null)
{
value = converter.CreateFromList(ref state, (IList)value, options);
setPropertyDirectly = true;
}
- else if (!popStackOnEnd)
+ else if (state.Current.IsEnumerableProperty)
{
- Debug.Assert(state.Current.IsPropertyEnumerable);
-
- // We added the items to the list property already.
+ // We added the items to the list already.
state.Current.ResetProperty();
return false;
}
setPropertyDirectly = false;
}
- if (popStackOnEnd)
- {
- state.Pop();
- }
-
if (lastFrame)
{
if (state.Current.ReturnValue == null)
}
// else there must be an outer object, so we'll return false here.
}
+ else if (state.Current.IsEnumerable)
+ {
+ state.Pop();
+ }
- ApplyObjectToEnumerable(value, options, ref state, ref reader, setPropertyDirectly: setPropertyDirectly);
+ ApplyObjectToEnumerable(value, ref state, ref reader, setPropertyDirectly: setPropertyDirectly);
- if (!popStackOnEnd)
+ if (state.Current.IsEnumerableProperty)
{
- Debug.Assert(state.Current.IsPropertyEnumerable);
state.Current.ResetProperty();
}
// If this method is changed, also change ApplyValueToEnumerable.
internal static void ApplyObjectToEnumerable(
object value,
- JsonSerializerOptions options,
ref ReadStack state,
ref Utf8JsonReader reader,
bool setPropertyDirectly = false)
{
+ Debug.Assert(!state.Current.SkipProperty);
+
if (state.Current.IsEnumerable)
{
if (state.Current.TempEnumerableValues != null)
((IList)state.Current.ReturnValue).Add(value);
}
}
- else if (!setPropertyDirectly && state.Current.IsPropertyEnumerable)
+ else if (!setPropertyDirectly && state.Current.IsEnumerableProperty)
{
Debug.Assert(state.Current.JsonPropertyInfo != null);
Debug.Assert(state.Current.ReturnValue != null);
list.Add(value);
}
}
- else if (state.Current.IsDictionary)
+ else if (state.Current.IsDictionary || (state.Current.IsDictionaryProperty && !setPropertyDirectly))
{
Debug.Assert(state.Current.ReturnValue != null);
IDictionary dictionary = (IDictionary)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
+
string key = state.Current.KeyName;
Debug.Assert(!string.IsNullOrEmpty(key));
if (!dictionary.Contains(key))
// If this method is changed, also change ApplyObjectToEnumerable.
internal static void ApplyValueToEnumerable<TProperty>(
ref TProperty value,
- JsonSerializerOptions options,
ref ReadStack state,
ref Utf8JsonReader reader)
{
+ Debug.Assert(!state.Current.SkipProperty);
+
if (state.Current.IsEnumerable)
{
if (state.Current.TempEnumerableValues != null)
((IList<TProperty>)state.Current.ReturnValue).Add(value);
}
}
- else if (state.Current.IsPropertyEnumerable)
+ else if (state.Current.IsEnumerableProperty)
{
Debug.Assert(state.Current.JsonPropertyInfo != null);
Debug.Assert(state.Current.ReturnValue != null);
list.Add(value);
}
}
- else if (state.Current.IsDictionary)
+ else if (state.Current.IsProcessingDictionary)
{
Debug.Assert(state.Current.ReturnValue != null);
IDictionary<string, TProperty> dictionary = (IDictionary<string, TProperty>)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization
+{
+ public static partial class JsonSerializer
+ {
+ private static void HandleStartDictionary(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state)
+ {
+ Debug.Assert(!state.Current.IsProcessingEnumerable);
+
+ JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
+ if (jsonPropertyInfo == null)
+ {
+ jsonPropertyInfo = state.Current.JsonClassInfo.CreateRootObject(options);
+ }
+
+ Debug.Assert(jsonPropertyInfo != null);
+
+ // A nested object or dictionary so push new frame.
+ if (state.Current.PropertyInitialized)
+ {
+ Debug.Assert(state.Current.IsDictionary);
+
+ JsonClassInfo classInfoTemp = state.Current.JsonClassInfo;
+ state.Push();
+ state.Current.JsonClassInfo = classInfoTemp.ElementClassInfo;
+ state.Current.InitializeJsonPropertyInfo();
+
+ ClassType classType = state.Current.JsonClassInfo.ClassType;
+ if (classType == ClassType.Value &&
+ jsonPropertyInfo.ElementClassInfo.Type != typeof(object) &&
+ jsonPropertyInfo.ElementClassInfo.Type != typeof(JsonElement))
+ {
+ ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.JsonClassInfo.Type, reader, state.PropertyPath);
+ }
+
+ JsonClassInfo classInfo = state.Current.JsonClassInfo;
+ state.Current.ReturnValue = classInfo.CreateObject();
+ return;
+ }
+
+ state.Current.PropertyInitialized = true;
+
+ // If current property is already set (from a constructor, for example) leave as-is.
+ if (jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue) == null)
+ {
+ // Create the dictionary.
+ JsonClassInfo dictionaryClassInfo = options.GetOrAddClass(jsonPropertyInfo.RuntimePropertyType);
+ IDictionary value = (IDictionary)dictionaryClassInfo.CreateObject();
+ if (value != null)
+ {
+ if (state.Current.ReturnValue != null)
+ {
+ state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value);
+ }
+ else
+ {
+ // A dictionary is being returned directly, or a nested dictionary.
+ state.Current.SetReturnValue(value);
+ }
+ }
+ }
+ }
+
+ private static void HandleEndDictionary(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state)
+ {
+ if (state.Current.IsDictionaryProperty)
+ {
+ // We added the items to the dictionary already.
+ state.Current.ResetProperty();
+ }
+ else
+ {
+ object value = state.Current.ReturnValue;
+
+ if (state.IsLastFrame)
+ {
+ // Set the return value directly since this will be returned to the user.
+ state.Current.Reset();
+ state.Current.ReturnValue = value;
+ }
+ else
+ {
+ state.Pop();
+ ApplyObjectToEnumerable(value, ref state, ref reader);
+ }
+ }
+ }
+ }
+}
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.Collections;
using System.Diagnostics;
namespace System.Text.Json.Serialization
{
private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options)
{
- if (state.Current.Skip())
+ if (state.Current.SkipProperty)
{
return false;
}
if (state.Current.IsEnumerable || state.Current.IsDictionary)
{
- ApplyObjectToEnumerable(null, options, ref state, ref reader);
+ ApplyObjectToEnumerable(null, ref state, ref reader);
return false;
}
- if (state.Current.IsPropertyEnumerable)
+ if (state.Current.IsEnumerableProperty || state.Current.IsDictionaryProperty)
{
- bool setPropertyToNull = !state.Current.EnumerableCreated;
- ApplyObjectToEnumerable(null, options, ref state, ref reader, setPropertyDirectly: setPropertyToNull);
+ bool setPropertyToNull = !state.Current.PropertyInitialized;
+ ApplyObjectToEnumerable(null, ref state, ref reader, setPropertyDirectly: setPropertyToNull);
return false;
}
{
private static void HandleStartObject(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state)
{
- if (state.Current.Skip())
- {
- state.Push();
- state.Current.Drain = true;
- return;
- }
+ Debug.Assert(!state.Current.IsProcessingDictionary);
if (state.Current.IsProcessingEnumerable)
{
+ // A nested object within an enumerable.
Type objType = state.Current.GetElementType();
state.Push();
state.Current.Initialize(objType, options);
}
else if (state.Current.JsonPropertyInfo != null)
{
- if (state.Current.IsDictionary)
- {
- // Verify that the Dictionary can be deserialized by having <string> as first generic argument.
- Type[] args = state.Current.JsonClassInfo.Type.GetGenericArguments();
- if (args.Length == 0 || args[0].UnderlyingSystemType != typeof(string))
- {
- ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.JsonClassInfo.Type, reader, state.PropertyPath);
- }
-
- 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
- {
- ClassType classType = state.Current.JsonClassInfo.ElementClassInfo.ClassType;
-
- // Verify that the second parameter is not a value.
- if (state.Current.JsonClassInfo.ElementClassInfo.ClassType == ClassType.Value)
- {
- ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.JsonClassInfo.Type, reader, state.PropertyPath);
- }
-
- // A nested object, dictionary or enumerable.
- 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);
- }
+ // Nested object.
+ Type objType = state.Current.JsonPropertyInfo.RuntimePropertyType;
+ state.Push();
+ state.Current.Initialize(objType, options);
}
JsonClassInfo classInfo = state.Current.JsonClassInfo;
state.Current.ReturnValue = classInfo.CreateObject();
}
- private static bool HandleEndObject(JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
+ private static void HandleEndObject(JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state)
{
- bool isLastFrame = state.IsLastFrame;
- if (state.Current.Drain)
- {
- state.Pop();
- return isLastFrame;
- }
+ Debug.Assert(!state.Current.IsProcessingDictionary);
state.Current.JsonClassInfo.UpdateSortedPropertyCache(ref state.Current);
object value = state.Current.ReturnValue;
- if (isLastFrame)
+ if (state.IsLastFrame)
{
state.Current.Reset();
state.Current.ReturnValue = value;
- return true;
}
-
- state.Pop();
- ApplyObjectToEnumerable(value, options, ref state, ref reader);
- return false;
+ else
+ {
+ state.Pop();
+ ApplyObjectToEnumerable(value, ref state, ref reader);
+ }
}
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Collections;
+using System.Diagnostics;
+
+namespace System.Text.Json.Serialization
+{
+ public static partial class JsonSerializer
+ {
+ private static void HandlePropertyName(
+ JsonSerializerOptions options,
+ ref Utf8JsonReader reader,
+ ref ReadStack state)
+ {
+ if (state.Current.Drain)
+ {
+ return;
+ }
+
+ Debug.Assert(state.Current.ReturnValue != default);
+ Debug.Assert(state.Current.JsonClassInfo != default);
+
+ if (state.Current.IsProcessingDictionary)
+ {
+ if (ReferenceEquals(state.Current.JsonClassInfo.DataExtensionProperty, state.Current.JsonPropertyInfo))
+ {
+ ReadOnlySpan<byte> propertyName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
+
+ // todo: use a cleaner call to get the unescaped string once https://github.com/dotnet/corefx/issues/35386 is implemented.
+ if (reader._stringHasEscaping)
+ {
+ int idx = propertyName.IndexOf(JsonConstants.BackSlash);
+ Debug.Assert(idx != -1);
+ propertyName = GetUnescapedString(propertyName, idx);
+ }
+
+ ProcessMissingProperty(propertyName, options, ref reader, ref state);
+ }
+ else
+ {
+ string keyName = reader.GetString();
+ if (options.DictionaryKeyPolicy != null)
+ {
+ keyName = options.DictionaryKeyPolicy.ConvertName(keyName);
+ }
+
+ if (state.Current.IsDictionary)
+ {
+ state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetPolicyProperty();
+ }
+
+ Debug.Assert(state.Current.IsDictionary ||
+ (state.Current.IsDictionaryProperty && state.Current.JsonPropertyInfo != null));
+
+ state.Current.KeyName = keyName;
+ }
+ }
+ else
+ {
+ ReadOnlySpan<byte> propertyName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
+ if (reader._stringHasEscaping)
+ {
+ int idx = propertyName.IndexOf(JsonConstants.BackSlash);
+ Debug.Assert(idx != -1);
+ propertyName = GetUnescapedString(propertyName, idx);
+ }
+
+ state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(options, propertyName, ref state.Current);
+ if (state.Current.JsonPropertyInfo == null)
+ {
+ if (state.Current.JsonClassInfo.DataExtensionProperty == null)
+ {
+ state.Current.JsonPropertyInfo = JsonPropertyInfo.s_missingProperty;
+ }
+ else
+ {
+ ProcessMissingProperty(propertyName, options, ref reader, ref state);
+ }
+ }
+ else
+ {
+ state.Current.PropertyIndex++;
+ }
+ }
+ }
+
+ private static void ProcessMissingProperty(
+ ReadOnlySpan<byte> unescapedPropertyName,
+ JsonSerializerOptions options,
+ ref Utf8JsonReader reader,
+ ref ReadStack state)
+ {
+ JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo.DataExtensionProperty;
+
+ Debug.Assert(jsonPropertyInfo != null);
+ Debug.Assert(state.Current.ReturnValue != null);
+
+ IDictionary extensionData = (IDictionary)jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
+ if (extensionData == null)
+ {
+ Type type = jsonPropertyInfo.DeclaredPropertyType;
+
+ // Create the appropriate dictionary type. We already verified the types.
+ Debug.Assert(type.IsGenericType);
+ Debug.Assert(type.GetGenericArguments().Length == 2);
+ Debug.Assert(type.GetGenericArguments()[0].UnderlyingSystemType == typeof(string));
+ Debug.Assert(
+ type.GetGenericArguments()[1].UnderlyingSystemType == typeof(object) ||
+ type.GetGenericArguments()[1].UnderlyingSystemType == typeof(JsonElement));
+
+ extensionData = (IDictionary)options.GetOrAddClass(type).CreateObject();
+ jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, extensionData);
+ }
+
+ JsonElement jsonElement;
+ using (JsonDocument jsonDocument = JsonDocument.ParseValue(ref reader))
+ {
+ jsonElement = jsonDocument.RootElement.Clone();
+ }
+
+ string keyName = JsonHelpers.Utf8GetString(unescapedPropertyName);
+
+ // Currently we don't apply any naming policy. If we do, we'd have to pass it onto the JsonDocument.
+
+ extensionData.Add(keyName, jsonElement);
+ }
+ }
+}
{
private static bool HandleValue(JsonTokenType tokenType, JsonSerializerOptions options, ref Utf8JsonReader reader, ref ReadStack state)
{
- if (state.Current.Skip())
+ if (state.Current.SkipProperty)
{
return false;
}
// 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
/// </summary>
public static partial class JsonSerializer
{
- internal static readonly JsonPropertyInfo s_missingProperty = new JsonPropertyInfoNotNullable<object, object, object>();
-
- // todo: for readability, refactor this method to split by ClassType(Enumerable, Object, or Value) like Write()
private static void ReadCore(
JsonSerializerOptions options,
ref Utf8JsonReader reader,
}
else if (tokenType == JsonTokenType.PropertyName)
{
- if (!state.Current.Drain)
+ HandlePropertyName(options, ref reader, ref state);
+ }
+ else if (tokenType == JsonTokenType.StartObject)
+ {
+ if (state.Current.SkipProperty)
{
- Debug.Assert(state.Current.ReturnValue != default);
- Debug.Assert(state.Current.JsonClassInfo != default);
-
- if (state.Current.IsDictionary)
- {
- string keyName = reader.GetString();
- if (options.DictionaryKeyPolicy != null)
- {
- keyName = options.DictionaryKeyPolicy.ConvertName(keyName);
- }
-
- state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetPolicyProperty();
- state.Current.KeyName = keyName;
- }
- else
+ state.Push();
+ state.Current.Drain = true;
+ }
+ else if (state.Current.IsProcessingValue)
+ {
+ if (HandleValue(tokenType, options, ref reader, ref state))
{
- ReadOnlySpan<byte> propertyName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
- if (reader._stringHasEscaping)
- {
- int idx = propertyName.IndexOf(JsonConstants.BackSlash);
- Debug.Assert(idx != -1);
- propertyName = GetUnescapedString(propertyName, idx);
- }
-
- state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(options, propertyName, ref state.Current);
- if (state.Current.JsonPropertyInfo == null)
- {
- state.Current.JsonPropertyInfo = s_missingProperty;
- }
-
- state.Current.PropertyIndex++;
+ continue;
}
}
- }
- else if (tokenType == JsonTokenType.StartObject)
- {
- if (!state.Current.IsProcessingProperty)
+ else if (state.Current.IsProcessingDictionary)
{
- HandleStartObject(options, ref reader, ref state);
+ HandleStartDictionary(options, ref reader, ref state);
}
- else if (HandleValue(tokenType, options, ref reader, ref state))
+ else
{
- continue;
+ HandleStartObject(options, ref reader, ref state);
}
}
else if (tokenType == JsonTokenType.EndObject)
{
- if (HandleEndObject(options, ref state, ref reader))
+ if (state.Current.Drain)
{
- continue;
+ state.Pop();
+ }
+ else if (state.Current.IsProcessingDictionary)
+ {
+ HandleEndDictionary(options, ref reader, ref state);
+ }
+ else
+ {
+ HandleEndObject(options, ref reader, ref state);
}
}
else if (tokenType == JsonTokenType.StartArray)
{
- if (!state.Current.IsProcessingProperty)
+ if (!state.Current.IsProcessingValue)
{
HandleStartArray(options, ref reader, ref state);
}
}
else if (tokenType == JsonTokenType.EndArray)
{
- if (HandleEndArray(options, ref state, ref reader))
+ if (HandleEndArray(options, ref reader, ref state))
{
continue;
}
// 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.Collections.Generic;
using System.Diagnostics;
ref WriteStack state)
{
JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
- if (!jsonPropertyInfo.ShouldSerialize)
- {
- // Ignore writing this property.
- return true;
- }
-
if (state.Current.Enumerator == null)
{
- // Verify that the Dictionary can be serialized by having <string> as first generic argument.
- Type[] args = jsonPropertyInfo.RuntimePropertyType.GetGenericArguments();
- if (args.Length == 0 || args[0].UnderlyingSystemType != typeof(string))
- {
- ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.JsonClassInfo.Type, state.PropertyPath);
- }
+ IEnumerable enumerable;
- IEnumerable enumerable = (IEnumerable)jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue);
+ enumerable = (IEnumerable)jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue);
if (enumerable == null)
{
// Write a null object or enumerable.
if (state.Current.Enumerator.MoveNext())
{
- // Check for polymorphism.
- if (elementClassInfo.ClassType == ClassType.Unknown)
- {
- object currentValue = ((IDictionaryEnumerator)state.Current.Enumerator).Entry.Value;
- GetRuntimeClassInfo(currentValue, ref elementClassInfo, options);
- }
-
- if (elementClassInfo.ClassType == ClassType.Value)
- {
- elementClassInfo.GetPolicyProperty().WriteDictionary(options, ref state.Current, writer);
- }
- else if (state.Current.Enumerator.Current == null)
+ // Handle DataExtension.
+ if (ReferenceEquals(jsonPropertyInfo, state.Current.JsonClassInfo.DataExtensionProperty))
{
- writer.WriteNull(jsonPropertyInfo.Name);
+ WriteExtensionData(writer, ref state.Current);
}
else
{
- // An object or another enumerator requires a new stack frame.
- var enumerator = (IDictionaryEnumerator)state.Current.Enumerator;
- object value = enumerator.Value;
- state.Push(elementClassInfo, value);
- state.Current.KeyName = (string)enumerator.Key;
+ // Check for polymorphism.
+ if (elementClassInfo.ClassType == ClassType.Unknown)
+ {
+ object currentValue = ((IDictionaryEnumerator)state.Current.Enumerator).Entry.Value;
+ GetRuntimeClassInfo(currentValue, ref elementClassInfo, options);
+ }
+
+ if (elementClassInfo.ClassType == ClassType.Value)
+ {
+ elementClassInfo.GetPolicyProperty().WriteDictionary(options, ref state.Current, writer);
+ }
+ else if (state.Current.Enumerator.Current == null)
+ {
+ writer.WriteNull(jsonPropertyInfo.Name);
+ }
+ else
+ {
+ // An object or another enumerator requires a new stack frame.
+ var enumerator = (IDictionaryEnumerator)state.Current.Enumerator;
+ object value = enumerator.Value;
+ state.Push(elementClassInfo, value);
+ state.Current.KeyName = (string)enumerator.Key;
+ }
}
return false;
#endif
}
}
+
+ private static void WriteExtensionData(Utf8JsonWriter writer, ref WriteStackFrame frame)
+ {
+ DictionaryEntry entry = ((IDictionaryEnumerator)frame.Enumerator).Entry;
+ if (entry.Value is JsonElement element)
+ {
+ Debug.Assert(entry.Key is string);
+
+ string propertyName = (string)entry.Key;
+ element.WriteAsProperty(propertyName.AsSpan(), writer);
+ }
+ else
+ {
+ ThrowHelper.ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(frame.JsonClassInfo, entry.Value.GetType());
+ }
+ }
}
}
{
Debug.Assert(state.Current.JsonPropertyInfo.ClassType == ClassType.Enumerable);
- JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
- if (!jsonPropertyInfo.ShouldSerialize)
- {
- // Ignore writing this property.
- return true;
- }
-
if (state.Current.Enumerator == null)
{
- IEnumerable enumerable = (IEnumerable)jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue);
+ IEnumerable enumerable = (IEnumerable)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue);
if (enumerable == null)
{
Utf8JsonWriter writer,
ref WriteStack state)
{
- JsonClassInfo classInfo = state.Current.JsonClassInfo;
-
// Write the start.
if (!state.Current.StartObjectWritten)
{
// Determine if we are done enumerating properties.
// If the ClassType is unknown, there will be a policy property applied. There is probably
// a better way to identify policy properties- maybe not put them in the normal property bag?
+ JsonClassInfo classInfo = state.Current.JsonClassInfo;
if (classInfo.ClassType != ClassType.Unknown && state.Current.PropertyIndex != classInfo.PropertyCount)
{
HandleObject(options, writer, ref state);
state.Current.JsonClassInfo.ClassType == ClassType.Unknown);
JsonPropertyInfo jsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(state.Current.PropertyIndex);
+ if (!jsonPropertyInfo.ShouldSerialize)
+ {
+ state.Current.NextProperty();
+ return true;
+ }
bool obtainedValue = false;
object currentValue = null;
{
if (!jsonPropertyInfo.IgnoreNullValues)
{
- writer.WriteNull(jsonPropertyInfo._escapedName);
+ writer.WriteNull(jsonPropertyInfo.EscapedName);
}
state.Current.NextProperty();
namespace System.Text.Json.Serialization
{
- [DebuggerDisplay("Current: ClassType.{Current.JsonClassInfo.ClassType} {Current.JsonClassInfo.Type.Name}")]
+ [DebuggerDisplay("Current: ClassType.{Current.JsonClassInfo.ClassType}, {Current.JsonClassInfo.Type.Name}")]
internal struct ReadStack
{
// A fields is used instead of a property to avoid value semantics.
private string GetPropertyName(in ReadStackFrame frame)
{
- if (frame.JsonPropertyInfo != null && frame.JsonClassInfo.ClassType == ClassType.Object)
+ if (frame.JsonPropertyInfo?.PropertyInfo != null && frame.JsonClassInfo.ClassType == ClassType.Object)
{
return $".{frame.JsonPropertyInfo.PropertyInfo.Name}";
}
namespace System.Text.Json.Serialization
{
- [DebuggerDisplay("ClassType.{JsonClassInfo.ClassType} {JsonClassInfo.Type.Name}")]
+ [DebuggerDisplay("ClassType.{JsonClassInfo.ClassType}, {JsonClassInfo.Type.Name}")]
internal struct ReadStackFrame
{
// The object (POCO or IEnumerable) that is being populated
// Current property values.
public JsonPropertyInfo JsonPropertyInfo;
- // 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;
+
+ // Has an array or dictionary property been initialized.
+ public bool PropertyInitialized;
// For performance, we order the properties by the first deserialize and PropertyIndex helps find the right slot quicker.
public int PropertyIndex;
public bool Drain;
public bool IsDictionary => JsonClassInfo.ClassType == ClassType.Dictionary;
+
+ public bool IsDictionaryProperty => JsonPropertyInfo != null &&
+ !JsonPropertyInfo.IsPropertyPolicy &&
+ JsonPropertyInfo.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;
- public bool IsProcessingProperty
+ public bool IsEnumerableProperty =>
+ JsonPropertyInfo != null &&
+ !JsonPropertyInfo.IsPropertyPolicy &&
+ JsonPropertyInfo.ClassType == ClassType.Enumerable;
+
+ public bool IsProcessingEnumerableOrDictionary => IsProcessingEnumerable || IsProcessingDictionary;
+ public bool IsProcessingDictionary => IsDictionary || IsDictionaryProperty;
+ public bool IsProcessingEnumerable => IsEnumerable || IsEnumerableProperty;
+
+ public bool IsProcessingValue
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
- if (JsonPropertyInfo == null || Skip())
+ if (JsonPropertyInfo == null || SkipProperty)
{
return false;
}
// We've got a property info. If we're a Value or polymorphic Value
// (ClassType.Unknown), return true.
ClassType type = JsonPropertyInfo.ClassType;
- return type == ClassType.Value || type == ClassType.Unknown
- || (type == ClassType.Dictionary && KeyName != null && JsonClassInfo.ElementClassInfo.ClassType == ClassType.Unknown);
+ return type == ClassType.Value || type == ClassType.Unknown ||
+ KeyName != null && (
+ (IsDictionary && JsonClassInfo.ElementClassInfo.ClassType == ClassType.Unknown) ||
+ (IsDictionaryProperty && JsonPropertyInfo.ElementClassInfo.ClassType == ClassType.Unknown)
+ );
}
}
public void ResetProperty()
{
- EnumerableCreated = false;
+ PropertyInitialized = false;
JsonPropertyInfo = null;
- PopStackOnEnd = false;
TempEnumerableValues = null;
}
}
else
{
- converterList = new List<object>();
+ converterList = new List<object>();
}
state.Current.TempEnumerableValues = converterList;
public Type GetElementType()
{
- if (IsPropertyEnumerable)
+ if (IsEnumerableProperty || IsDictionaryProperty)
{
return JsonPropertyInfo.ElementClassInfo.Type;
}
- if (IsEnumerable)
- {
- return JsonClassInfo.ElementClassInfo.Type;
- }
-
- if (IsDictionary)
+ if (IsEnumerable || IsDictionary)
{
return JsonClassInfo.ElementClassInfo.Type;
}
ReturnValue = value;
}
- public bool Skip()
- {
- return Drain || ReferenceEquals(JsonPropertyInfo, JsonSerializer.s_missingProperty);
- }
+ public bool SkipProperty => Drain ||
+ ReferenceEquals(JsonPropertyInfo, JsonPropertyInfo.s_missingProperty) ||
+ (JsonPropertyInfo?.IsPropertyPolicy == false && JsonPropertyInfo?.ShouldDeserialize == false);
}
}
public void WriteObjectOrArrayStart(ClassType classType, Utf8JsonWriter writer, bool writeNull = false)
{
- if (JsonPropertyInfo?._escapedName != null)
+ if (JsonPropertyInfo?.EscapedName != null)
{
- WriteObjectOrArrayStart(classType, JsonPropertyInfo?._escapedName, writer, writeNull);
+ WriteObjectOrArrayStart(classType, JsonPropertyInfo?.EscapedName, writer, writeNull);
}
else if (KeyName != null)
{
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
+using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowArgumentException_DeserializeWrongType(Type type, object value)
{
- throw new ArgumentException(SR.Format(SR.DeserializeWrongType, type.FullName, value.GetType().FullName));
+ throw new ArgumentException(SR.Format(SR.DeserializeWrongType, type, value.GetType()));
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static NotSupportedException GetNotSupportedException_SerializationNotSupportedCollection(Type propertyType, Type parentType, MemberInfo memberInfo)
+ {
+ if (parentType != null && parentType != typeof(object) && memberInfo != null)
+ {
+ return new NotSupportedException(SR.Format(SR.SerializationNotSupportedCollection, propertyType, $"{parentType}.{memberInfo.Name}"));
+ }
+
+ return new NotSupportedException(SR.Format(SR.SerializationNotSupportedCollectionType, propertyType));
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType, in Utf8JsonReader reader, string path)
{
- ThowJsonException(SR.Format(SR.DeserializeUnableToConvertValue, propertyType.FullName), in reader, path);
+ ThowJsonException(SR.Format(SR.DeserializeUnableToConvertValue, propertyType), in reader, path);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType, string path)
{
- string message = SR.Format(SR.DeserializeUnableToConvertValue, propertyType.FullName) + $" Path: {path}.";
+ string message = SR.Format(SR.DeserializeUnableToConvertValue, propertyType) + $" Path: {path}.";
throw new JsonException(message, path, null, null);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowInvalidOperationException_SerializerPropertyNameConflict(JsonClassInfo jsonClassInfo, JsonPropertyInfo jsonPropertyInfo)
{
- throw new InvalidOperationException(SR.Format(SR.SerializerPropertyNameConflict, jsonClassInfo.Type.FullName, jsonPropertyInfo.PropertyInfo.Name));
+ throw new InvalidOperationException(SR.Format(SR.SerializerPropertyNameConflict, jsonClassInfo.Type, jsonPropertyInfo.PropertyInfo.Name));
}
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowInvalidOperationException_SerializerPropertyNameNull(JsonClassInfo jsonClassInfo, JsonPropertyInfo jsonPropertyInfo)
+ public static void ThrowInvalidOperationException_SerializerPropertyNameNull(Type parentType, JsonPropertyInfo jsonPropertyInfo)
{
- throw new InvalidOperationException(SR.Format(SR.SerializerPropertyNameNull, jsonClassInfo.Type.FullName, jsonPropertyInfo.PropertyInfo.Name));
+ throw new InvalidOperationException(SR.Format(SR.SerializerPropertyNameNull, parentType, jsonPropertyInfo.PropertyInfo.Name));
}
public static void ThrowJsonException_DeserializeDataRemaining(long length, long bytesRemaining)
throw new JsonException(message, path, exception.LineNumber, exception.BytePositionInLine, exception);
}
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowInvalidOperationException_SerializationDuplicateAttribute(Type attribute)
+ {
+ throw new InvalidOperationException(SR.Format(SR.SerializationDuplicateAttribute, attribute));
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(JsonClassInfo jsonClassInfo, JsonPropertyInfo jsonPropertyInfo)
+ {
+ throw new InvalidOperationException(SR.Format(SR.SerializationDataExtensionPropertyInvalid, jsonClassInfo.Type, jsonPropertyInfo.PropertyInfo.Name));
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(JsonClassInfo jsonClassInfo, Type invalidType)
+ {
+ throw new InvalidOperationException(SR.Format(SR.SerializationDataExtensionPropertyInvalidElement, jsonClassInfo.Type, jsonClassInfo.DataExtensionProperty.PropertyInfo.Name, invalidType));
+ }
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static JsonException GetJsonReaderException(ref Utf8JsonReader json, ExceptionResource resource, byte nextByte, ReadOnlySpan<byte> bytes)
{
- string message = GetResourceString(ref json, resource, nextByte, Encoding.UTF8.GetString(bytes.ToArray(), 0, bytes.Length));
+ string message = GetResourceString(ref json, resource, nextByte, JsonHelpers.Utf8GetString(bytes));
long lineNumber = json.CurrentState._lineNumber;
long bytePositionInLine = json.CurrentState._bytePositionInLine;
}
}
- private static void VerifyReadNull(SimpleTestClass obj, bool isNull)
- {
- if (isNull)
- {
- Assert.Null(obj);
- }
- else
- {
- obj.Verify();
- }
- }
-
[Theory]
[MemberData(nameof(ReadNullJson))]
public static void ReadNull(string json, bool element0Null, bool element1Null, bool element2Null)
VerifyReadNull(list[0], element0Null);
VerifyReadNull(list[1], element1Null);
VerifyReadNull(list[2], element2Null);
+
+ static void VerifyReadNull(SimpleTestClass obj, bool isNull)
+ {
+ if (isNull)
+ {
+ Assert.Null(obj);
+ }
+ else
+ {
+ obj.Verify();
+ }
+ }
}
[Fact]
TestClassWithObjectImmutableTypes obj = JsonSerializer.Parse<TestClassWithObjectImmutableTypes>(TestClassWithObjectImmutableTypes.s_data);
obj.Verify();
}
+
+ public static void ClassWithNoSetter()
+ {
+ string json = @"{""MyList"":[1]}";
+ ClassWithListButNoSetter obj = JsonSerializer.Parse<ClassWithListButNoSetter>(json);
+ Assert.Equal(1, obj.MyList[0]);
+ }
+
+ public class ClassWithListButNoSetter
+ {
+ public List<int> MyList { get; } = new List<int>();
+ }
}
}
}
}
-
-
[Fact]
public static void WriteClassWithGenericList()
{
[Fact]
public static void FirstGenericArgNotStringFail()
{
- Assert.Throws<JsonException>(() => JsonSerializer.Parse<Dictionary<int, int>>(@"{""Key1"":1}"));
+ Assert.Throws<NotSupportedException>(() => JsonSerializer.Parse<Dictionary<int, int>>(@"{""Key1"":1}"));
}
[Fact]
[Fact]
public static void ObjectToStringFail()
{
+ // Baseline
string json = @"{""MyDictionary"":{""Key"":""Value""}}";
+ JsonSerializer.Parse<Dictionary<string, object>>(json);
+
Assert.Throws<JsonException>(() => JsonSerializer.Parse<Dictionary<string, string>>(json));
}
+ [Fact, ActiveIssue("JsonElement fails since it is a struct.")]
+ public static void ObjectToJsonElement()
+ {
+ string json = @"{""MyDictionary"":{""Key"":""Value""}}";
+ JsonSerializer.Parse<Dictionary<string, JsonElement>>(json);
+ }
+
[Fact]
public static void HashtableFail()
{
JsonSerializer.Parse<Dictionary<string, string>>(json);
// We don't support non-generic IDictionary
- Assert.Throws<JsonException>(() => JsonSerializer.Parse<Hashtable>(json));
+ Assert.Throws<NotSupportedException>(() => JsonSerializer.Parse<Hashtable>(json));
}
{
Hashtable ht = new Hashtable();
ht.Add("Key", "Value");
- Assert.Throws<JsonException>(() => JsonSerializer.ToString(ht));
+
+ Assert.Throws<NotSupportedException>(() => JsonSerializer.ToString(ht));
}
{
string json = @"{""Key"":""Value""}";
// We don't support non-generic IDictionary
- Assert.Throws<JsonException>(() => JsonSerializer.Parse<IDictionary>(json));
+ Assert.Throws<NotSupportedException>(() => JsonSerializer.Parse<IDictionary>(json));
}
{
IDictionary ht = new Hashtable();
ht.Add("Key", "Value");
- Assert.Throws<JsonException>(() => JsonSerializer.ToString(ht));
+ Assert.Throws<NotSupportedException>(() => JsonSerializer.ToString(ht));
+ }
+ }
+
+ [Fact]
+ public static void ClassWithNoSetter()
+ {
+ string json = @"{""MyDictionary"":{""Key"":""Value""}}";
+ ClassWithDictionaryButNoSetter obj = JsonSerializer.Parse<ClassWithDictionaryButNoSetter>(json);
+ Assert.Equal("Value", obj.MyDictionary["Key"]);
+ }
+
+ [Fact]
+ public static void DictionaryNotSupported()
+ {
+ string json = @"{""MyDictionary"":{""Key"":""Value""}}";
+
+ try
+ {
+ JsonSerializer.Parse<ClassWithNotSupportedDictionary>(json);
+ Assert.True(false, "Expected NotSupportedException to be thrown.");
}
+ catch (NotSupportedException e)
+ {
+ // The exception should contain className.propertyName and the invalid type.
+ Assert.Contains("ClassWithNotSupportedDictionary.MyDictionary", e.Message);
+ Assert.Contains("Dictionary`2[System.Int32,System.Int32]", e.Message);
+ }
+ }
+
+ [Fact]
+ public static void DictionaryNotSupportedButIgnored()
+ {
+ string json = @"{""MyDictionary"":{""Key"":1}}";
+ ClassWithNotSupportedDictionaryButIgnored obj = JsonSerializer.Parse<ClassWithNotSupportedDictionaryButIgnored>(json);
+ Assert.Null(obj.MyDictionary);
+ }
+
+ public class ClassWithDictionaryButNoSetter
+ {
+ public Dictionary<string, string> MyDictionary { get; } = new Dictionary<string, string>();
+ }
+
+ public class ClassWithNotSupportedDictionary
+ {
+ public Dictionary<int, int> MyDictionary { get; set; }
+ }
+
+ public class ClassWithNotSupportedDictionaryButIgnored
+ {
+ [JsonIgnore] public Dictionary<int, int> MyDictionary { get; set; }
}
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+ public static class ExtensionDataTests
+ {
+ [Fact]
+ public static void ExtensionPropertyNotUsed()
+ {
+ string json = @"{""MyNestedClass"":" + SimpleTestClass.s_json + "}";
+ ClassWithExtensionProperty obj = JsonSerializer.Parse<ClassWithExtensionProperty>(json);
+ Assert.Null(obj.MyOverflow);
+ }
+
+ [Fact]
+ public static void ExtensionPropertyRoundTrip()
+ {
+ ClassWithExtensionProperty obj;
+
+ {
+ string json = @"{""MyIntMissing"":2, ""MyInt"":1, ""MyNestedClassMissing"":" + SimpleTestClass.s_json + "}";
+ obj = JsonSerializer.Parse<ClassWithExtensionProperty>(json);
+ Verify();
+ }
+
+ // Round-trip the json.
+ {
+ string json = JsonSerializer.ToString(obj);
+ obj = JsonSerializer.Parse<ClassWithExtensionProperty>(json);
+ Verify();
+ }
+
+ void Verify()
+ {
+ Assert.NotNull(obj.MyOverflow);
+ Assert.NotNull(obj.MyOverflow["MyIntMissing"]);
+ Assert.Equal(1, obj.MyInt);
+ Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
+
+ JsonProperty[] properties = obj.MyOverflow["MyNestedClassMissing"].EnumerateObject().ToArray();
+
+ // Verify a couple properties
+ Assert.Equal(1, properties.Where(prop => prop.Name == "MyInt16").First().Value.GetInt32());
+ Assert.Equal(true, properties.Where(prop => prop.Name == "MyBooleanTrue").First().Value.GetBoolean());
+ }
+ }
+
+ [Fact]
+ public static void ExtensionPropertyAlreadyInstantiated()
+ {
+ Assert.NotNull(new ClassWithExtensionPropertyAlreadyInstantiated().MyOverflow);
+
+ string json = @"{""MyIntMissing"":2}";
+
+ ClassWithExtensionProperty obj = JsonSerializer.Parse<ClassWithExtensionProperty>(json);
+ Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
+ }
+
+ [Fact]
+ public static void ExtensionPropertyAsObject()
+ {
+ string json = @"{""MyIntMissing"":2}";
+
+ ClassWithExtensionPropertyAsObject obj = JsonSerializer.Parse<ClassWithExtensionPropertyAsObject>(json);
+ Assert.IsType<JsonElement>(obj.MyOverflow["MyIntMissing"]);
+ Assert.Equal(2, ((JsonElement)obj.MyOverflow["MyIntMissing"]).GetInt32());
+ }
+
+ [Fact]
+ public static void ExtensionPropertyCamelCasing()
+ {
+ // Currently we apply no naming policy. If we do (such as a ExtensionPropertyNamingPolicy), we'd also have to add functionality to the JsonDocument.
+
+ ClassWithExtensionProperty obj;
+ const string jsonWithProperty = @"{""MyIntMissing"":1}";
+ const string jsonWithPropertyCamelCased = @"{""myIntMissing"":1}";
+
+ {
+ // Baseline Pascal-cased json + no casing option.
+ obj = JsonSerializer.Parse<ClassWithExtensionProperty>(jsonWithProperty);
+ Assert.Equal(1, obj.MyOverflow["MyIntMissing"].GetInt32());
+ string json = JsonSerializer.ToString(obj);
+ Assert.Contains(@"""MyIntMissing"":1", json);
+ }
+
+ {
+ // Pascal-cased json + camel casing option.
+ JsonSerializerOptions options = new JsonSerializerOptions();
+ options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
+ options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+
+ obj = JsonSerializer.Parse<ClassWithExtensionProperty>(jsonWithProperty, options);
+ Assert.Equal(1, obj.MyOverflow["MyIntMissing"].GetInt32());
+ string json = JsonSerializer.ToString(obj);
+ Assert.Contains(@"""MyIntMissing"":1", json);
+ }
+
+ {
+ // Baseline camel-cased json + no casing option.
+ obj = JsonSerializer.Parse<ClassWithExtensionProperty>(jsonWithPropertyCamelCased);
+ Assert.Equal(1, obj.MyOverflow["myIntMissing"].GetInt32());
+ string json = JsonSerializer.ToString(obj);
+ Assert.Contains(@"""myIntMissing"":1", json);
+ }
+
+ {
+ // Baseline camel-cased json + camel casing option.
+ JsonSerializerOptions options = new JsonSerializerOptions();
+ options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
+ options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+
+ obj = JsonSerializer.Parse<ClassWithExtensionProperty>(jsonWithPropertyCamelCased, options);
+ Assert.Equal(1, obj.MyOverflow["myIntMissing"].GetInt32());
+ string json = JsonSerializer.ToString(obj);
+ Assert.Contains(@"""myIntMissing"":1", json);
+ }
+ }
+
+ [Fact]
+ public static void NullValuesIgnored()
+ {
+ const string json = @"{""MyNestedClass"":null}";
+ const string jsonMissing = @"{ ""MyNestedClassMissing"":null}";
+
+ {
+ // Baseline with no missing.
+ ClassWithExtensionProperty obj = JsonSerializer.Parse<ClassWithExtensionProperty>(json);
+ Assert.Null(obj.MyOverflow);
+
+ string outJson = JsonSerializer.ToString(obj);
+ Assert.Contains(@"""MyNestedClass"":null", outJson);
+ }
+
+ {
+ // Baseline with missing.
+ ClassWithExtensionProperty obj = JsonSerializer.Parse<ClassWithExtensionProperty>(jsonMissing);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(JsonValueType.Null, obj.MyOverflow["MyNestedClassMissing"].Type);
+ }
+
+ {
+ JsonSerializerOptions options = new JsonSerializerOptions();
+ options.IgnoreNullValues = true;
+
+ ClassWithExtensionProperty obj = JsonSerializer.Parse<ClassWithExtensionProperty>(jsonMissing, options);
+
+ // Currently we do not ignore nulls in the extension data. The JsonDocument would also need to support this mode
+ // for any lower-level nulls.
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(JsonValueType.Null, obj.MyOverflow["MyNestedClassMissing"].Type);
+ }
+ }
+
+ [Fact]
+ public static void InvalidExtensionPropertyFail()
+ {
+ // Baseline
+ JsonSerializer.Parse<ClassWithExtensionProperty>(@"{}");
+ JsonSerializer.Parse<ClassWithExtensionPropertyAsObject>(@"{}");
+
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Parse<ClassWithInvalidExtensionProperty>(@"{}"));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Parse<ClassWithTwoExtensionPropertys>(@"{}"));
+ }
+
+ [Fact]
+ public static void IgnoredDataShouldNotBeExtensionData()
+ {
+ ClassWithIgnoredData obj = JsonSerializer.Parse<ClassWithIgnoredData>(@"{""MyInt"":1}");
+
+ Assert.Equal(0, obj.MyInt);
+ Assert.Null(obj.MyOverflow);
+ }
+
+ [Fact]
+ public static void InvalidExtensionValue()
+ {
+ // Baseline
+ ClassWithExtensionPropertyAlreadyInstantiated obj = JsonSerializer.Parse<ClassWithExtensionPropertyAlreadyInstantiated>(@"{}");
+ obj.MyOverflow.Add("test", new object());
+
+ try
+ {
+ JsonSerializer.ToString(obj);
+ Assert.True(false, "InvalidOperationException should have thrown.");
+ }
+ catch (InvalidOperationException e)
+ {
+ // Verify the exception contains the property name and invalid type.
+ Assert.Contains("ClassWithExtensionPropertyAlreadyInstantiated.MyOverflow", e.Message);
+ Assert.Contains("System.Object", e.Message);
+ }
+ }
+
+ [Fact]
+ public static void ObjectTree()
+ {
+ string json = @"{""MyIntMissing"":2, ""MyReference"":{""MyIntMissingChild"":3}}";
+
+ ClassWithReference obj = JsonSerializer.Parse<ClassWithReference>(json);
+ Assert.IsType<JsonElement>(obj.MyOverflow["MyIntMissing"]);
+ Assert.Equal(1, obj.MyOverflow.Count);
+ Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32());
+
+ ClassWithExtensionProperty child = obj.MyReference;
+ Assert.IsType<JsonElement>(child.MyOverflow["MyIntMissingChild"]);
+ Assert.IsType<JsonElement>(child.MyOverflow["MyIntMissingChild"]);
+ Assert.Equal(1, child.MyOverflow.Count);
+ Assert.Equal(3, child.MyOverflow["MyIntMissingChild"].GetInt32());
+ }
+
+ public class ClassWithExtensionPropertyAlreadyInstantiated
+ {
+ public ClassWithExtensionPropertyAlreadyInstantiated()
+ {
+ MyOverflow = new Dictionary<string, object>();
+ }
+
+ [JsonExtensionData]
+ public Dictionary<string, object> MyOverflow { get; set; }
+ }
+
+ public class ClassWithExtensionPropertyAsObject
+ {
+ [JsonExtensionData]
+ public Dictionary<string, object> MyOverflow { get; set; }
+ }
+
+ public class ClassWithIgnoredData
+ {
+ [JsonExtensionData]
+ public Dictionary<string, object> MyOverflow { get; set; }
+
+ [JsonIgnore]
+ public int MyInt { get; set; }
+ }
+
+ public class ClassWithInvalidExtensionProperty
+ {
+ [JsonExtensionData]
+ public Dictionary<string, int> MyOverflow { get; set; }
+ }
+
+ public class ClassWithTwoExtensionPropertys
+ {
+ [JsonExtensionData]
+ public Dictionary<string, object> MyOverflow1 { get; set; }
+
+ [JsonExtensionData]
+ public Dictionary<string, object> MyOverflow2 { get; set; }
+ }
+
+ public class ClassWithReference
+ {
+ [JsonExtensionData]
+ public Dictionary<string, JsonElement> MyOverflow { get; set; }
+
+ public ClassWithExtensionProperty MyReference { get; set; }
+ }
+ }
+}
Assert.Contains(Environment.NewLine, json);
}
+ [Fact]
+ public static void ExtensionDataUsesReaderOptions()
+ {
+ // We just verify trailing commas.
+ const string json = @"{""MyIntMissing"":2,}";
+
+ // Verify baseline without options.
+ Assert.Throws<JsonException>(() => JsonSerializer.Parse<ClassWithExtensionProperty>(json));
+
+ // Verify baseline with options.
+ var options = new JsonSerializerOptions();
+ Assert.Throws<JsonException>(() => JsonSerializer.Parse<ClassWithExtensionProperty>(json, options));
+
+ // Set AllowTrailingCommas to true.
+ options = new JsonSerializerOptions();
+ options.AllowTrailingCommas = true;
+ JsonSerializer.Parse<ClassWithExtensionProperty>(json, options);
+ }
+
+ [Fact]
+ public static void ExtensionDataUsesWriterOptions()
+ {
+ // We just verify whitespace.
+
+ ClassWithExtensionProperty obj = JsonSerializer.Parse<ClassWithExtensionProperty>(@"{""MyIntMissing"":2}");
+
+ // Verify baseline without options.
+ string json = JsonSerializer.ToString(obj);
+ Assert.False(HasNewLine());
+
+ // Verify baseline with options.
+ var options = new JsonSerializerOptions();
+ json = JsonSerializer.ToString(obj, options);
+ Assert.False(HasNewLine());
+
+ // Set AllowTrailingCommas to true.
+ options = new JsonSerializerOptions();
+ options.WriteIndented = true;
+ json = JsonSerializer.ToString(obj, options);
+ Assert.True(HasNewLine());
+
+ bool HasNewLine()
+ {
+ int iEnd = json.IndexOf("2", json.IndexOf("MyIntMissing"));
+ return json.Substring(iEnd + 1).StartsWith(Environment.NewLine);
+ }
+ }
+
[Fact]
public static void ReadCommentHandling()
{
Assert.Equal(@"MyString", obj.MyString);
Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore);
Assert.Equal(2, obj.MyStringsWithIgnore.Length);
+ Assert.Equal(1, obj.MyDictionaryWithIgnore["Key"]);
// Verify serialize.
string json = JsonSerializer.ToString(obj);
Assert.Contains(@"""MyString""", json);
Assert.DoesNotContain(@"MyStringWithIgnore", json);
Assert.DoesNotContain(@"MyStringsWithIgnore", json);
+ Assert.DoesNotContain(@"MyDictionaryWithIgnore", json);
// Verify deserialize default.
obj = JsonSerializer.Parse<ClassWithIgnoreAttributeProperty>(@"{}");
Assert.Equal(@"MyString", obj.MyString);
Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore);
Assert.Equal(2, obj.MyStringsWithIgnore.Length);
+ Assert.Equal(1, obj.MyDictionaryWithIgnore["Key"]);
// Verify deserialize ignores the json for MyStringWithIgnore and MyStringsWithIgnore.
obj = JsonSerializer.Parse<ClassWithIgnoreAttributeProperty>(
- @"{""MyString"":""Hello"", ""MyStringWithIgnore"":""IgnoreMe"", ""MyStringsWithIgnore"":[""IgnoreMe""]}");
+ @"{""MyString"":""Hello"", ""MyStringWithIgnore"":""IgnoreMe"", ""MyStringsWithIgnore"":[""IgnoreMe""], ""MyDictionaryWithIgnore"":{""Key"":9}}");
Assert.Contains(@"Hello", obj.MyString);
Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore);
Assert.Equal(2, obj.MyStringsWithIgnore.Length);
+ Assert.Equal(1, obj.MyDictionaryWithIgnore["Key"]);
}
// Todo: add tests with missing object property and missing collection property.
{
public ClassWithIgnoreAttributeProperty()
{
+ MyDictionaryWithIgnore = new Dictionary<string, int> { { "Key", 1 } };
MyString = "MyString";
MyStringWithIgnore = "MyStringWithIgnore";
MyStringsWithIgnore = new string[] { "1", "2" };
}
+ [JsonIgnore]
+ public Dictionary<string, int> MyDictionaryWithIgnore { get; set; }
+
[JsonIgnore]
public string MyStringWithIgnore { get; set; }
public int Aѧ34567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 { get; set; }
}
+ public class ClassWithExtensionProperty
+ {
+ public SimpleTestClass MyNestedClass { get; set; }
+ public int MyInt { get; set; }
+
+ [JsonExtensionData]
+ public IDictionary<string, JsonElement> MyOverflow { get; set; }
+ }
+
public class TestClassWithNestedObjectCommentsInner : ITestClass
{
public SimpleTestClass MyData { get; set; }
<Compile Include="Serialization\CyclicTests.cs" />
<Compile Include="Serialization\DictionaryTests.cs" />
<Compile Include="Serialization\EnumTests.cs" />
+ <Compile Include="Serialization\ExtensionDataTests.cs" />
<Compile Include="Serialization\JsonElementTests.cs" />
<Compile Include="Serialization\ExceptionTests.cs" />
<Compile Include="Serialization\Null.ReadTests.cs" />