public partial struct JsonReaderOptions
{
private int _dummyPrimitive;
+ public bool AllowTrailingCommas { get { throw null; } set { } }
public System.Text.Json.JsonCommentHandling CommentHandling { get { throw null; } set { } }
public int MaxDepth { get { throw null; } set { } }
- public bool AllowTrailingCommas { get { throw null; } set { } }
}
public partial struct JsonReaderState
{
}
namespace System.Text.Json.Serialization
{
+ public abstract partial class JsonAttribute : System.Attribute
+ {
+ protected JsonAttribute() { }
+ }
+ [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false)]
+ public sealed partial class JsonIgnoreAttribute : System.Text.Json.Serialization.JsonAttribute
+ {
+ public JsonIgnoreAttribute() { }
+ }
public static partial class JsonSerializer
{
public static object Parse(System.ReadOnlySpan<byte> utf8Json, System.Type returnType, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
public sealed partial class JsonSerializerOptions
{
public JsonSerializerOptions() { }
+ public bool AllowTrailingCommas { get { throw null; } set { } }
public int DefaultBufferSize { get { throw null; } set { } }
- public bool IgnoreNullPropertyValueOnRead { get { throw null; } set { } }
- public bool IgnoreNullPropertyValueOnWrite { get { throw null; } set { } }
- public System.Text.Json.JsonReaderOptions ReaderOptions { get { throw null; } set { } }
- public System.Text.Json.JsonWriterOptions WriterOptions { get { throw null; } set { } }
+ public bool IgnoreNullValues { get { throw null; } set { } }
+ public bool IgnoreReadOnlyProperties { get { throw null; } set { } }
+ public int MaxDepth { get { throw null; } set { } }
+ public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } }
+ public bool WriteIndented { get { throw null; } set { } }
}
}
<data name="TrailingCommaNotAllowedBeforeObjectEnd" xml:space="preserve">
<value>The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options.</value>
</data>
+ <data name="SerializerOptionsImmutable" xml:space="preserve">
+ <value>Serializer options cannot be changed once serialization or deserialization has occurred.</value>
+ </data>
</root>
\ No newline at end of file
<Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt16.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt32.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt64.cs" />
+ <Compile Include="System\Text\Json\Serialization\JsonAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.cs" />
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.AddProperty.cs" />
<Compile Include="System\Text\Json\Serialization\JsonEnumerableConverter.cs" />
+ <Compile Include="System\Text\Json\Serialization\JsonIgnoreAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyInfo.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyInfoCommon.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyInfoNotNullable.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyInfoNullable.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleArray.cs" />
+ <Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Helpers.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Stream.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleValue.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleObject.cs" />
/// <summary>
/// Defines how the <see cref="Utf8JsonReader"/> should handle comments when reading through the JSON.
- /// By default the reader will throw a <exception cref="JsonReaderException"/> if it encounters a comment.
+ /// By default <exception cref="JsonReaderException"/> is thrown if a comment is encountered.
/// </summary>
public JsonCommentHandling CommentHandling { get; set; }
/// <summary>
/// Defines whether an extra comma at the end of a list of JSON values in an object or array
- /// are allowed (and ignored) within the JSON payload being read.
- /// By default, it's set to false, and the reader will throw a <exception cref="JsonReaderException"/> if it encounters a trailing comma.
+ /// is allowed (and ignored) within the JSON payload being read.
+ /// By default, it's set to false, and <exception cref="JsonReaderException"/> is thrown if a trailing comma is encountered.
/// </summary>
public bool AllowTrailingCommas { get; set; }
}
--- /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>
+ /// The base class of serialization attributes.
+ /// </summary>
+ public abstract class JsonAttribute : Attribute { }
+}
--- /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>
+ /// Prevents a property from being serialized or deserialized.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
+ public sealed class JsonIgnoreAttribute : JsonAttribute
+ {
+ public JsonIgnoreAttribute() { }
+ }
+}
// See the LICENSE file in the project root for more information.
using System.Collections;
+using System.Diagnostics;
using System.Reflection;
using System.Text.Json.Serialization.Converters;
using System.Text.Json.Serialization.Policies;
{
internal abstract class JsonPropertyInfo
{
+ // Cache the array and enumerable converters so they don't get created for every enumerable property.
+ private static readonly JsonEnumerableConverter s_jsonArrayConverter = new DefaultArrayConverter();
+ private static readonly JsonEnumerableConverter s_jsonEnumerableConverter = new DefaultEnumerableConverter();
+
internal ClassType ClassType;
internal byte[] _name = default;
internal bool HasGetter { get; set; }
internal bool HasSetter { get; set; }
+ internal bool ShouldSerialize { get; private set; }
+ internal bool ShouldDeserialize { get; private set; }
+
+ internal bool IgnoreNullValues { get; private set; }
- public ReadOnlySpan<byte> EscapedName => _escapedName;
public ReadOnlySpan<byte> Name => _name;
// todo: to minimize hashtable lookups, cache JsonClassInfo:
ClassType = JsonClassInfo.GetClassType(runtimePropertyType);
if (elementType != null)
{
+ Debug.Assert(ClassType == ClassType.Enumerable);
ElementClassInfo = options.GetOrAddClass(elementType);
}
internal virtual void GetPolicies(JsonSerializerOptions options)
{
- if (RuntimePropertyType.IsArray)
+ DetermineSerializationCapabilities(options);
+ IgnoreNullValues = options.IgnoreNullValues;
+ }
+
+ private void DetermineSerializationCapabilities(JsonSerializerOptions options)
+ {
+ bool hasIgnoreAttribute = (GetAttribute<JsonIgnoreAttribute>() != null);
+
+ if (hasIgnoreAttribute)
{
- EnumerableConverter = new DefaultArrayConverter();
+ // We don't serialize or deserialize.
+ return;
}
- else if (typeof(IEnumerable).IsAssignableFrom(RuntimePropertyType))
+
+ if (ClassType != ClassType.Enumerable)
{
- Type elementType = JsonClassInfo.GetElementType(RuntimePropertyType);
+ // We serialize if there is a getter + no [Ignore] attribute + not ignoring readonly properties.
+ ShouldSerialize = HasGetter && (HasSetter || !options.IgnoreReadOnlyProperties);
- if (RuntimePropertyType.IsAssignableFrom(typeof(JsonEnumerableT<>).MakeGenericType(elementType)))
+ // We deserialize if there is a setter + no [Ignore] attribute.
+ ShouldDeserialize = HasSetter;
+ }
+ else
+ {
+ if (HasGetter)
{
- EnumerableConverter = new DefaultEnumerableConverter();
+ if (HasSetter)
+ {
+ ShouldDeserialize = true;
+ }
+ else if (RuntimePropertyType.IsAssignableFrom(typeof(IList)))
+ {
+ ShouldDeserialize = true;
+ }
+ //else
+ //{
+ // // todo: future feature that allows non-List types (e.g. from System.Collections.Immutable) to have converters.
+ //}
+ }
+ //else if (HasSetter)
+ //{
+ // // todo: Special case where there is no getter but a setter (and an EnumerableConverter)
+ //}
+
+ if (ShouldDeserialize)
+ {
+ ShouldSerialize = HasGetter;
+
+ if (RuntimePropertyType.IsArray)
+ {
+ EnumerableConverter = s_jsonArrayConverter;
+ }
+ else if (typeof(IEnumerable).IsAssignableFrom(RuntimePropertyType))
+ {
+ Type elementType = JsonClassInfo.GetElementType(RuntimePropertyType);
+
+ if (RuntimePropertyType.IsAssignableFrom(typeof(JsonEnumerableT<>).MakeGenericType(elementType)))
+ {
+ EnumerableConverter = s_jsonEnumerableConverter;
+ }
+ }
+ }
+ else
+ {
+ ShouldSerialize = HasGetter && !options.IgnoreReadOnlyProperties;
}
}
}
internal abstract object GetValueAsObject(object obj, JsonSerializerOptions options);
- internal bool IgnoreNullPropertyValueOnRead(JsonSerializerOptions options)
- {
- return options.IgnoreNullPropertyValueOnRead;
- }
-
- internal bool IgnoreNullPropertyValueOnWrite(JsonSerializerOptions options)
+ internal TAttribute GetAttribute<TAttribute>() where TAttribute : Attribute
{
- return options.IgnoreNullPropertyValueOnWrite;
+ return (TAttribute)PropertyInfo?.GetCustomAttribute(typeof(TAttribute), inherit: false);
}
internal abstract void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader);
Debug.Assert(Set != null);
TDeclaredProperty typedValue = (TDeclaredProperty)value;
- if (typedValue != null || !IgnoreNullPropertyValueOnWrite(options))
+ if (typedValue != null || !IgnoreNullValues)
{
Set((TClass)obj, (TDeclaredProperty)value);
}
JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
propertyInfo.ReadEnumerable(tokenType, options, ref state, ref reader);
}
- else if (HasSetter)
+ else if (ShouldDeserialize)
{
if (ValueConverter != null)
{
}
else
{
- if (value != null || !IgnoreNullPropertyValueOnRead(options))
- {
- Set((TClass)state.Current.ReturnValue, value);
- }
+ // Null values were already handled.
+ Debug.Assert(value != null);
+
+ Set((TClass)state.Current.ReturnValue, value);
}
return;
JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
propertyInfo.WriteEnumerable(options, ref current, ref writer);
}
- else if (HasGetter)
+ else if (ShouldSerialize)
{
TRuntimeProperty value;
if (_isPropertyPolicy)
{
writer.WriteNullValue();
}
- else if (!IgnoreNullPropertyValueOnWrite(options))
+ else if (!IgnoreNullValues)
{
writer.WriteNull(_escapedName);
}
JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
propertyInfo.ReadEnumerable(tokenType, options, ref state, ref reader);
}
- else if (HasSetter)
+ else if (ShouldDeserialize)
{
if (ValueConverter != null)
{
JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
propertyInfo.WriteEnumerable(options, ref current, ref writer);
}
- else if (HasGetter)
+ else if (ShouldSerialize)
{
TProperty? value;
if (_isPropertyPolicy)
{
writer.WriteNullValue();
}
- else if (!IgnoreNullPropertyValueOnWrite(options))
+ else if (!IgnoreNullValues)
{
writer.WriteNull(_escapedName);
}
ref Utf8JsonReader reader,
ref ReadStack state)
{
- if (state.Current.Skip())
+ JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
+
+ bool skip = jsonPropertyInfo != null && !jsonPropertyInfo.ShouldDeserialize;
+ if (skip || state.Current.Skip())
{
// The array is not being applied to the object.
state.Push();
return;
}
- JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo;
if (jsonPropertyInfo == null || state.Current.JsonClassInfo.ClassType == ClassType.Unknown)
{
jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options);
state.Current.EnumerableCreated = true;
}
+ jsonPropertyInfo = state.Current.JsonPropertyInfo;
+
// If current property is already set (from a constructor, for example) leave as-is
- if (state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue, options) == null)
+ if (jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue, options) == null)
{
// Create the enumerable.
object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state, options);
return true;
}
- if (!propertyInfo.IgnoreNullPropertyValueOnRead(options))
+ if (!propertyInfo.IgnoreNullValues)
{
state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null, options);
}
--- /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
+{
+ public static partial class JsonSerializer
+ {
+ private static object ReadCore(
+ Type returnType,
+ JsonSerializerOptions options,
+ ref Utf8JsonReader reader)
+ {
+ options ??= JsonSerializerOptions.s_defaultOptions;
+
+ ReadStack state = default;
+ state.Current.Initialize(returnType, options);
+
+ ReadCore(options, ref reader, ref state);
+
+ return state.Current.ReturnValue;
+ }
+ }
+}
private static object ParseCore(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerOptions options)
{
- if (options == null)
- options = s_defaultSettings;
+ options ??= JsonSerializerOptions.s_defaultOptions;
- var readerState = new JsonReaderState(options: options.ReaderOptions);
+ var readerState = new JsonReaderState(options.GetReaderOptions());
var reader = new Utf8JsonReader(utf8Json, isFinalBlock: true, readerState);
object result = ReadCore(returnType, options, ref reader);
JsonSerializerOptions options = null,
CancellationToken cancellationToken = default)
{
- options ??= s_defaultSettings;
+ options ??= JsonSerializerOptions.s_defaultOptions;
ReadStack state = default;
state.Current.Initialize(returnType, options);
- var readerState = new JsonReaderState(options.ReaderOptions);
+ var readerState = new JsonReaderState(options.GetReaderOptions());
// todo: switch to ArrayBuffer implementation to handle and simplify the allocs?
byte[] buffer = ArrayPool<byte>.Shared.Rent(options.DefaultBufferSize);
private static object ParseCore(string json, Type returnType, JsonSerializerOptions options = null)
{
- if (options == null)
- options = s_defaultSettings;
+ options ??= JsonSerializerOptions.s_defaultOptions;
// todo: use an array pool here for smaller requests to avoid the alloc?
byte[] jsonBytes = JsonReaderHelper.s_utf8Encoding.GetBytes(json);
- var readerState = new JsonReaderState(options: options.ReaderOptions);
+ var readerState = new JsonReaderState(options.GetReaderOptions());
var reader = new Utf8JsonReader(jsonBytes, isFinalBlock: true, readerState);
object result = ReadCore(returnType, options, ref reader);
public static partial class JsonSerializer
{
internal static readonly JsonPropertyInfo s_missingProperty = new JsonPropertyInfoNotNullable<object, object, object>();
- private static readonly JsonSerializerOptions s_defaultSettings = new JsonSerializerOptions();
-
- private static object ReadCore(
- Type returnType,
- JsonSerializerOptions options,
- ref Utf8JsonReader reader)
- {
- if (options == null)
- options = s_defaultSettings;
-
- ReadStack state = default;
- state.Current.Initialize(returnType, options);
-
- ReadCore(options, ref reader, ref state);
-
- return state.Current.ReturnValue;
- }
// todo: for readability, refactor this method to split by ClassType(Enumerable, Object, or Value) like Write()
private static void ReadCore(
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)
{
}
else
{
- if (!jsonPropertyInfo.IgnoreNullPropertyValueOnWrite(options))
+ if (!jsonPropertyInfo.IgnoreNullValues)
{
writer.WriteNull(jsonPropertyInfo._escapedName);
}
private static byte[] WriteCoreBytes(object value, Type type, JsonSerializerOptions options)
{
- if (options == null)
- options = s_defaultSettings;
+ options ??= JsonSerializerOptions.s_defaultOptions;
byte[] result;
private static string WriteCoreString(object value, Type type, JsonSerializerOptions options)
{
- if (options == null)
- options = s_defaultSettings;
-
+ options ??= JsonSerializerOptions.s_defaultOptions;
string result;
using (var output = new ArrayBufferWriter<byte>(options.DefaultBufferSize))
{
Debug.Assert(type != null || value == null);
- var writerState = new JsonWriterState(options.WriterOptions);
+ var writerState = new JsonWriterState(options.GetWriterOptions());
var writer = new Utf8JsonWriter(output, writerState);
if (value == null)
private static async Task WriteAsyncCore(object value, Type type, Stream utf8Json, JsonSerializerOptions options, CancellationToken cancellationToken)
{
- if (options == null)
- options = s_defaultSettings;
+ options ??= JsonSerializerOptions.s_defaultOptions;
- var writerState = new JsonWriterState(options.WriterOptions);
+ var writerState = new JsonWriterState(options.GetWriterOptions());
using (var bufferWriter = new ArrayBufferWriter<byte>(options.DefaultBufferSize))
{
// See the LICENSE file in the project root for more information.
using System.Collections.Concurrent;
+using System.Diagnostics;
namespace System.Text.Json.Serialization
{
{
internal const int BufferSizeDefault = 16 * 1024;
+ internal static readonly JsonSerializerOptions s_defaultOptions = new JsonSerializerOptions();
+
+ private readonly ConcurrentDictionary<Type, JsonClassInfo> _classes = new ConcurrentDictionary<Type, JsonClassInfo>();
private ClassMaterializer _classMaterializerStrategy;
+ private JsonCommentHandling _readCommentHandling;
private int _defaultBufferSize = BufferSizeDefault;
-
- private static readonly ConcurrentDictionary<Type, JsonClassInfo> s_classes = new ConcurrentDictionary<Type, JsonClassInfo>();
+ private int _maxDepth;
+ private bool _allowTrailingCommas;
+ private bool _haveTypesBeenCreated;
+ private bool _ignoreNullValues;
+ private bool _ignoreReadOnlyProperties;
+ private bool _writeIndented;
/// <summary>
/// Constructs a new <see cref="JsonSerializerOptions"/> instance.
/// </summary>
public JsonSerializerOptions() { }
- internal JsonClassInfo GetOrAddClass(Type classType)
+ /// <summary>
+ /// Defines whether an extra comma at the end of a list of JSON values in an object or array
+ /// is allowed (and ignored) within the JSON payload being read.
+ /// By default, it's set to false, and <exception cref="JsonReaderException"/> is thrown if a trailing comma is encountered.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown if this property is set after serialization or deserialization has occurred.
+ /// </exception>
+ public bool AllowTrailingCommas
{
- JsonClassInfo result;
-
- if (!s_classes.TryGetValue(classType, out result))
+ get
{
- result = s_classes.GetOrAdd(classType, new JsonClassInfo(classType, this));
+ return _allowTrailingCommas;
+ }
+ set
+ {
+ VerifyMutable();
+ _allowTrailingCommas = value;
}
-
- return result;
}
/// <summary>
- /// Options to control the <see cref="Utf8JsonReader"/>.
- /// </summary>
- public JsonReaderOptions ReaderOptions { get; set; }
-
- /// <summary>
- /// Options to control the <see cref="Utf8JsonWriter"/>.
- /// </summary>
- public JsonWriterOptions WriterOptions { get; set; }
-
- /// <summary>
/// The default buffer size in bytes used when creating temporary buffers.
/// </summary>
/// <remarks>The default size is 16K.</remarks>
/// <exception cref="System.ArgumentException">Thrown when the buffer size is less than 1.</exception>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown if this property is set after serialization or deserialization has occurred.
+ /// </exception>
public int DefaultBufferSize
{
get
}
set
{
+ VerifyMutable();
+
if (value < 1)
{
throw new ArgumentException(SR.SerializationInvalidBufferSize);
}
/// <summary>
- /// Determines whether null values of properties are ignored or whether they are written to the JSON.
+ /// Determines whether null values are ignored during serialization and deserialization.
+ /// The default value is false.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown if this property is set after serialization or deserialization has occurred.
+ /// </exception>
+ public bool IgnoreNullValues
+ {
+ get
+ {
+ return _ignoreNullValues;
+ }
+ set
+ {
+ VerifyMutable();
+ _ignoreNullValues = value;
+ }
+ }
+
+ /// <summary>
+ /// Determines whether read-only properties are ignored during serialization and deserialization.
+ /// A property is read-only if it contains a public getter but not a public setter.
+ /// The default value is false.
/// </summary>
- public bool IgnoreNullPropertyValueOnWrite { get; set; }
+ /// <exception cref="InvalidOperationException">
+ /// Thrown if this property is set after serialization or deserialization has occurred.
+ /// </exception>
+ public bool IgnoreReadOnlyProperties
+ {
+ get
+ {
+ return _ignoreReadOnlyProperties;
+ }
+ set
+ {
+ VerifyMutable();
+ _ignoreReadOnlyProperties = value;
+ }
+ }
/// <summary>
- /// Determines whether null values in the JSON are ignored or whether they are set on properties.
+ /// Gets or sets the maximum depth allowed when reading or writing JSON, with the default (i.e. 0) indicating a max depth of 64.
+ /// Reading past this depth will throw a <exception cref="JsonReaderException"/>.
/// </summary>
- public bool IgnoreNullPropertyValueOnRead { get; set; }
+ /// <exception cref="InvalidOperationException">
+ /// Thrown if this property is set after serialization or deserialization has occurred.
+ /// </exception>
+ public int MaxDepth
+ {
+ get
+ {
+ return _maxDepth;
+ }
+ set
+ {
+ VerifyMutable();
+ _maxDepth = value;
+ }
+ }
+
+ /// <summary>
+ /// Defines how the comments are handled during deserialization.
+ /// By default <exception cref="JsonReaderException"/> is thrown if a comment is encountered.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown if this property is set after serialization or deserialization has occurred.
+ /// </exception>
+ public JsonCommentHandling ReadCommentHandling
+ {
+ get
+ {
+ return _readCommentHandling;
+ }
+ set
+ {
+ VerifyMutable();
+ _readCommentHandling = value;
+ }
+ }
+
+ /// <summary>
+ /// Defines whether JSON should pretty print which includes:
+ /// indenting nested JSON tokens, adding new lines, and adding white space between property names and values.
+ /// By default, the JSON is written without any extra white space.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown if this property is set after serialization or deserialization has occurred.
+ /// </exception>
+ public bool WriteIndented
+ {
+ get
+ {
+ return _writeIndented;
+ }
+ set
+ {
+ VerifyMutable();
+ _writeIndented = value;
+ }
+ }
internal ClassMaterializer ClassMaterializerStrategy
{
return _classMaterializerStrategy;
}
}
+
+ internal JsonClassInfo GetOrAddClass(Type classType)
+ {
+ _haveTypesBeenCreated = true;
+
+ // todo: for performance, consider obtaining the type from s_defaultOptions and then cloning.
+ if (!_classes.TryGetValue(classType, out JsonClassInfo result))
+ {
+ result = _classes.GetOrAdd(classType, new JsonClassInfo(classType, this));
+ }
+
+ return result;
+ }
+
+ internal JsonReaderOptions GetReaderOptions()
+ {
+ return new JsonReaderOptions
+ {
+ AllowTrailingCommas = AllowTrailingCommas,
+ CommentHandling = ReadCommentHandling,
+ MaxDepth = MaxDepth
+ };
+ }
+
+ internal JsonWriterOptions GetWriterOptions()
+ {
+ return new JsonWriterOptions
+ {
+ Indented = WriteIndented
+ };
+ }
+
+ private void VerifyMutable()
+ {
+ // The default options are hidden and thus should be immutable.
+ Debug.Assert(this != s_defaultOptions);
+
+ if (_haveTypesBeenCreated)
+ {
+ ThrowHelper.ThrowInvalidOperationException_SerializerOptionsImmutable();
+ }
+ }
}
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowJsonReaderException_DeserializeUnableToConvertValue(Type propertyType, in ReadStack state)
- {
- throw new JsonReaderException(SR.Format(SR.DeserializeUnableToConvertValue, state.PropertyPath, propertyType), 0 , 0);
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowJsonReaderException_DeserializeCannotBeNull(in Utf8JsonReader reader, in ReadStack state)
{
throw new JsonReaderException(SR.Format(SR.DeserializeCannotBeNull, state.PropertyPath), reader.CurrentState);
{
throw new ObjectDisposedException(name);
}
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowInvalidOperationException_SerializerOptionsImmutable()
+ {
+ throw new InvalidOperationException(SR.SerializerOptionsImmutable);
+ }
}
}
// 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 static partial class ArrayTests
{
[Fact]
+ public static void ReadEmpty()
+ {
+ SimpleTestClass[] arr = JsonSerializer.Parse<SimpleTestClass[]>("[]");
+ Assert.Equal(0, arr.Length);
+
+ List<SimpleTestClass> list = JsonSerializer.Parse<List<SimpleTestClass>>("[]");
+ Assert.Equal(0, list.Count);
+ }
+
+ [Fact]
public static void ReadClassWithStringArray()
{
TestClassWithStringArray obj = JsonSerializer.Parse<TestClassWithStringArray>(TestClassWithStringArray.s_data);
// 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 static partial class ArrayTests
{
[Fact]
+ public static void WriteEmpty()
+ {
+ string json = JsonSerializer.ToString(new SimpleTestClass[] { });
+ Assert.Equal("[]", json);
+
+ json = JsonSerializer.ToString(new List<SimpleTestClass>());
+ Assert.Equal("[]", json);
+ }
+
+ [Fact]
public static void WriteClassWithStringArray()
{
string json;
}
[Fact]
- public static void DefaultReadValue()
+ public static void DefaultIgnoreNullValuesOnRead()
{
- TestClassWithNullButInitialized obj = JsonSerializer.Parse<TestClassWithNullButInitialized>(TestClassWithNullButInitialized.s_json);
+ TestClassWithInitializedProperties obj = JsonSerializer.Parse<TestClassWithInitializedProperties>(TestClassWithInitializedProperties.s_null_json);
Assert.Equal(null, obj.MyString);
Assert.Equal(null, obj.MyInt);
}
[Fact]
- public static void OverrideReadOnOption()
+ public static void EnableIgnoreNullValuesOnRead()
{
var options = new JsonSerializerOptions();
- options.IgnoreNullPropertyValueOnRead = true;
+ options.IgnoreNullValues = true;
- TestClassWithNullButInitialized obj = JsonSerializer.Parse<TestClassWithNullButInitialized>(TestClassWithNullButInitialized.s_json, options);
+ TestClassWithInitializedProperties obj = JsonSerializer.Parse<TestClassWithInitializedProperties>(TestClassWithInitializedProperties.s_null_json, options);
Assert.Equal("Hello", obj.MyString);
Assert.Equal(1, obj.MyInt);
}
public static partial class NullTests
{
[Fact]
- public static void DefaultWriteOptions()
+ public static void DefaultIgnoreNullValuesOnWrite()
{
- var input = new TestClassWithNull();
- string json = JsonSerializer.ToString(input);
- Assert.Equal(@"{""MyString"":null}", json);
+ var obj = new TestClassWithInitializedProperties();
+ obj.MyString = null;
+ obj.MyInt = null;
+
+ string json = JsonSerializer.ToString(obj);
+ Assert.Contains(@"""MyString"":null", json);
+ Assert.Contains(@"""MyInt"":null", json);
}
[Fact]
- public static void OverrideWriteOnOption()
+ public static void EnableIgnoreNullValuesOnWrite()
{
JsonSerializerOptions options = new JsonSerializerOptions();
- options.IgnoreNullPropertyValueOnWrite = true;
+ options.IgnoreNullValues = true;
+
+ var obj = new TestClassWithInitializedProperties();
+ obj.MyString = null;
+ obj.MyInt = null;
- var input = new TestClassWithNull();
- string json = JsonSerializer.ToString(input, options);
+ string json = JsonSerializer.ToString(obj, options);
Assert.Equal(@"{}", json);
}
}
[Fact]
+ public static void ReadEmpty()
+ {
+ SimpleTestClass obj = JsonSerializer.Parse<SimpleTestClass>("{}");
+ Assert.NotNull(obj);
+ }
+
+ [Fact]
public static void EmptyClassWithRandomData()
{
JsonSerializer.Parse<EmptyClass>(SimpleTestClass.s_json);
public static partial class OptionsTests
{
[Fact]
+ public static void SetOptionsFail()
+ {
+ var options = new JsonSerializerOptions();
+
+ JsonSerializer.Parse<int>("1", options);
+
+ // Verify defaults and ensure getters do not throw.
+ Assert.False(options.AllowTrailingCommas);
+ Assert.Equal(16 * 1024, options.DefaultBufferSize);
+ Assert.False(options.IgnoreNullValues);
+ Assert.Equal(0, options.MaxDepth);
+ Assert.Equal(JsonCommentHandling.Disallow, options.ReadCommentHandling);
+ Assert.False(options.WriteIndented);
+
+ // Setters should throw
+ Assert.Throws<InvalidOperationException>(() => options.AllowTrailingCommas = options.AllowTrailingCommas);
+ Assert.Throws<InvalidOperationException>(() => options.DefaultBufferSize = options.DefaultBufferSize);
+ Assert.Throws<InvalidOperationException>(() => options.IgnoreNullValues = options.IgnoreNullValues);
+ Assert.Throws<InvalidOperationException>(() => options.MaxDepth = options.MaxDepth);
+ Assert.Throws<InvalidOperationException>(() => options.ReadCommentHandling = options.ReadCommentHandling);
+ Assert.Throws<InvalidOperationException>(() => options.WriteIndented = options.WriteIndented);
+ }
+
+ [Fact]
public static void DefaultBufferSizeFail()
{
Assert.Throws<ArgumentException>(() => new JsonSerializerOptions().DefaultBufferSize = 0);
options.DefaultBufferSize = 1;
Assert.Equal(1, options.DefaultBufferSize);
}
+
+ [Fact]
+ public static void AllowTrailingCommas()
+ {
+ Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<int[]>("[1,]"));
+
+ var options = new JsonSerializerOptions();
+ options.AllowTrailingCommas = true;
+
+ int[] value = JsonSerializer.Parse<int[]>("[1,]", options);
+ Assert.Equal(1, value[0]);
+ }
+
+ [Fact]
+ public static void WriteIndented()
+ {
+ var obj = new BasicCompany();
+ obj.Initialize();
+
+ // Verify default value.
+ string json = JsonSerializer.ToString(obj);
+ Assert.DoesNotContain(Environment.NewLine, json);
+
+ // Verify default value on options.
+ var options = new JsonSerializerOptions();
+ json = JsonSerializer.ToString(obj, options);
+ Assert.DoesNotContain(Environment.NewLine, json);
+
+ // Change the value on options.
+ options = new JsonSerializerOptions();
+ options.WriteIndented = true;
+ json = JsonSerializer.ToString(obj, options);
+ Assert.Contains(Environment.NewLine, json);
+ }
+
+ [Fact]
+ public static void ReadCommentHandling()
+ {
+ Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<object>("/* commment */"));
+
+ var options = new JsonSerializerOptions();
+
+ Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<object>("/* commment */", options));
+
+ options = new JsonSerializerOptions();
+ options.ReadCommentHandling = JsonCommentHandling.Allow;
+
+ JsonSerializer.Parse<object>("/* commment */", options);
+ }
+
+ [Fact]
+ public static void MaxDepthRead()
+ {
+ JsonSerializer.Parse<BasicCompany>(BasicCompany.s_data);
+
+ var options = new JsonSerializerOptions();
+
+ JsonSerializer.Parse<BasicCompany>(BasicCompany.s_data, options);
+
+ options = new JsonSerializerOptions();
+ options.MaxDepth = 1;
+
+ Assert.Throws<JsonReaderException>(() => JsonSerializer.Parse<BasicCompany>(BasicCompany.s_data, options));
+ }
}
}
Assert.Contains(@"""NullableInt"":null", json);
JsonSerializerOptions options = new JsonSerializerOptions();
- options.IgnoreNullPropertyValueOnWrite = true;
+ options.IgnoreNullValues = true;
json = JsonSerializer.ToString(obj, options);
Assert.DoesNotContain(@"""NullableInt"":null", json);
}
// 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
var obj = new ClassWithNoSetter();
string json = JsonSerializer.ToString(obj);
- Assert.Equal(@"{""MyString"":""DefaultValue""}", json);
+ Assert.Contains(@"""MyString"":""DefaultValue""", json);
+ Assert.Contains(@"""MyInts"":[1,2]", json);
- ClassWithNoSetter objCopy = JsonSerializer.Parse<ClassWithNoSetter>(json);
- Assert.Equal("DefaultValue", objCopy.MyString);
+ obj = JsonSerializer.Parse<ClassWithNoSetter>(@"{""MyString"":""IgnoreMe"",""MyInts"":[0]}");
+ Assert.Equal("DefaultValue", obj.MyString);
+ Assert.Equal(2, obj.MyInts.Length);
+ }
+
+ [Fact]
+ public static void IgnoreReadOnlyProperties()
+ {
+ var options = new JsonSerializerOptions();
+ options.IgnoreReadOnlyProperties = true;
+
+ var obj = new ClassWithNoSetter();
+
+ string json = JsonSerializer.ToString(obj, options);
+ Assert.Equal(@"{}", json);
}
[Fact]
public static void NoGetter()
{
- var objNoSetter = new ClassWithNoSetter();
+ ClassWithNoGetter objWithNoGetter = JsonSerializer.Parse<ClassWithNoGetter>(
+ @"{""MyString"":""Hello"",""MyIntArray"":[0],""MyIntList"":[0]}");
- string json = JsonSerializer.ToString(objNoSetter);
- Assert.Equal(@"{""MyString"":""DefaultValue""}", json);
+ Assert.Equal("Hello", objWithNoGetter.GetMyString());
- ClassWithNoGetter objNoGetter = JsonSerializer.Parse<ClassWithNoGetter>(json);
- Assert.Equal("DefaultValue", objNoGetter.GetMyString());
+ // Currently we don't support setters without getters.
+ Assert.Equal(0, objWithNoGetter.GetMyIntArray().Length);
+ Assert.Equal(0, objWithNoGetter.GetMyIntList().Count);
}
[Fact]
Assert.Null(objCopy.GetMyString());
}
+ [Fact]
+ public static void JsonIgnoreAttribute()
+ {
+ // Verify default state.
+ var obj = new ClassWithIgnoreAttributeProperty();
+ Assert.Equal(@"MyString", obj.MyString);
+ Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore);
+ Assert.Equal(2, obj.MyStringsWithIgnore.Length);
+
+ // Verify serialize.
+ string json = JsonSerializer.ToString(obj);
+ Assert.Contains(@"""MyString""", json);
+ Assert.DoesNotContain(@"MyStringWithIgnore", json);
+ Assert.DoesNotContain(@"MyStringsWithIgnore", json);
+
+ // Verify deserialize default.
+ obj = JsonSerializer.Parse<ClassWithIgnoreAttributeProperty>(@"{}");
+ Assert.Equal(@"MyString", obj.MyString);
+ Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore);
+ Assert.Equal(2, obj.MyStringsWithIgnore.Length);
+
+ // Verify deserialize ignores the json for MyStringWithIgnore and MyStringsWithIgnore.
+ obj = JsonSerializer.Parse<ClassWithIgnoreAttributeProperty>(
+ @"{""MyString"":""Hello"", ""MyStringWithIgnore"":""IgnoreMe"", ""MyStringsWithIgnore"":[""IgnoreMe""]}");
+ Assert.Contains(@"Hello", obj.MyString);
+ Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore);
+ Assert.Equal(2, obj.MyStringsWithIgnore.Length);
+ }
+
// Todo: add tests with missing object property and missing collection property.
+ public class ClassWithPrivateSetterAndGetter
+ {
+ private string MyString { get; set; }
+
+ public string GetMyString()
+ {
+ return MyString;
+ }
+
+ public void SetMyString(string value)
+ {
+ MyString = value;
+ }
+ }
+
public class ClassWithNoSetter
{
public ClassWithNoSetter()
{
MyString = "DefaultValue";
+ MyInts = new int[] { 1, 2 };
}
public string MyString { get; }
+ public int[] MyInts { get; }
}
public class ClassWithNoGetter
{
string _myString = "";
+ int[] _myIntArray = new int[] { };
+ List<int> _myIntList = new List<int> { };
public string MyString
{
}
}
+ public int[] MyIntArray
+ {
+ set
+ {
+ _myIntArray = value;
+ }
+ }
+
+ public List<int> MyList
+ {
+ set
+ {
+ _myIntList = value;
+ }
+ }
+
public string GetMyString()
{
return _myString;
}
- }
- public class ClassWithPrivateSetterAndGetter
- {
- private string MyString { get; set; }
+ public int[] GetMyIntArray()
+ {
+ return _myIntArray;
+ }
- public string GetMyString()
+ public List<int> GetMyIntList()
{
- return MyString;
+ return _myIntList;
}
+ }
- public void SetMyString(string value)
+ public class ClassWithIgnoreAttributeProperty
+ {
+ public ClassWithIgnoreAttributeProperty()
{
- MyString = value;
+ MyString = "MyString";
+ MyStringWithIgnore = "MyStringWithIgnore";
+ MyStringsWithIgnore = new string[] { "1", "2" };
}
+
+ [JsonIgnore]
+ public string MyStringWithIgnore { get; set; }
+
+ public string MyString { get; set; }
+
+ [JsonIgnore]
+ public string[] MyStringsWithIgnore { get; set; }
}
}
}
}
}
- public class TestClassWithNullButInitialized
+ public class TestClassWithInitializedProperties
{
public string MyString { get; set; } = "Hello";
public int? MyInt { get; set; } = 1;
- public static readonly string s_json =
+ public static readonly string s_null_json =
@"{" +
@"""MyString"" : null," +
@"""MyInt"" : null" +
@"}";
- public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
+ public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_null_json);
}
public class TestClassWithNestedObjectInner : ITestClass
}
[Fact]
+ public static void ReadEmptyObjectArray()
+ {
+ SimpleTestClass[] data = JsonSerializer.Parse<SimpleTestClass[]>("[{}]");
+ Assert.Equal(1, data.Length);
+ Assert.NotNull(data[0]);
+ }
+
+ [Fact]
public static void ReadPrimitiveJaggedArray()
{
int[][] i = JsonSerializer.Parse<int[][]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
}
[Fact]
+ public static void WriteEmptyObjectArray()
+ {
+ object[] arr = new object[]{new object()};
+
+ string json = JsonSerializer.ToString(arr);
+ Assert.Equal("[{}]", json);
+ }
+
+ [Fact]
public static void WritePrimitiveJaggedArray()
{
var input = new int[2][];