Skip to content

Commit

Permalink
Add IndentSize and IndentCharacter properties
Browse files Browse the repository at this point in the history
  • Loading branch information
noqn committed Mar 19, 2022
1 parent aeb7b91 commit e7a73b3
Show file tree
Hide file tree
Showing 33 changed files with 249 additions and 100 deletions.
5 changes: 5 additions & 0 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { }
public bool IgnoreReadOnlyFields { get { throw null; } set { } }
public bool IgnoreReadOnlyProperties { get { throw null; } set { } }
public bool IncludeFields { get { throw null; } set { } }
public char IndentCharacter { get { throw null; } set { } }
public int IndentSize { get { throw null; } set { } }
public int MaxDepth { get { throw null; } set { } }
public System.Text.Json.Serialization.JsonNumberHandling NumberHandling { get { throw null; } set { } }
public bool PropertyNameCaseInsensitive { get { throw null; } set { } }
Expand Down Expand Up @@ -357,7 +359,10 @@ public partial struct JsonWriterOptions
private object _dummy;
private int _dummyPrimitive;
public System.Text.Encodings.Web.JavaScriptEncoder? Encoder { readonly get { throw null; } set { } }
internal byte IndentByte { get { throw null; } private set { } }
public bool Indented { get { throw null; } set { } }
public char IndentCharacter { get { throw null; } set { } }
public int IndentSize { get { throw null; } set { } }
public int MaxDepth { readonly get { throw null; } set { } }
public bool SkipValidation { get { throw null; } set { } }
}
Expand Down
6 changes: 6 additions & 0 deletions src/libraries/System.Text.Json/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -614,4 +614,10 @@
<data name="NoMetadataForTypeCtorParams" xml:space="preserve">
<value>'JsonSerializerContext' '{0}' did not provide constructor parameter metadata for type '{1}'.</value>
</data>
<data name="IndentSizeMustBeGreaterThanOrEqualToZero" xml:space="preserve">
<value>Indent size must greater than or equal to 0.</value>
</data>
<data name="InvalidIndentCharacter" xml:space="preserve">
<value>IndentCharacter must be a valid whitespace character.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ internal static partial class JsonConstants
// Explicitly skipping ReverseSolidus since that is handled separately
public static ReadOnlySpan<byte> EscapableChars => new byte[] { Quote, (byte)'n', (byte)'r', (byte)'t', Slash, (byte)'u', (byte)'b', (byte)'f' };

public const int SpacesPerIndent = 2;
public const int DefaultIndentSize = 2;
public const int RemoveFlagsBitMask = 0x7FFFFFFF;

public const int StackallocByteThreshold = 256;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public sealed partial class JsonSerializerOptions

private int _defaultBufferSize = BufferSizeDefault;
private int _maxDepth;
private int _indentSize;
private bool _allowTrailingCommas;
private bool _haveTypesBeenCreated;
private bool _ignoreNullValues;
Expand All @@ -66,6 +67,7 @@ public sealed partial class JsonSerializerOptions
private bool _includeFields;
private bool _propertyNameCaseInsensitive;
private bool _writeIndented;
private char _indentCharacter;

/// <summary>
/// Constructs a new <see cref="JsonSerializerOptions"/> instance.
Expand Down Expand Up @@ -102,13 +104,15 @@ public JsonSerializerOptions(JsonSerializerOptions options)

_defaultBufferSize = options._defaultBufferSize;
_maxDepth = options._maxDepth;
_indentSize = options._indentSize;
_allowTrailingCommas = options._allowTrailingCommas;
_ignoreNullValues = options._ignoreNullValues;
_ignoreReadOnlyProperties = options._ignoreReadOnlyProperties;
_ignoreReadonlyFields = options._ignoreReadonlyFields;
_includeFields = options._includeFields;
_propertyNameCaseInsensitive = options._propertyNameCaseInsensitive;
_writeIndented = options._writeIndented;
_indentCharacter = options._indentCharacter;

Converters = new ConverterList(this, (ConverterList)options.Converters);
EffectiveMaxDepth = options.EffectiveMaxDepth;
Expand Down Expand Up @@ -441,6 +445,31 @@ public int MaxDepth
}
}

/// <summary>
/// Defines the number of whitespace characters to write per indentation level when serializing with <see cref="WriteIndented"/> set to <see langword="true"/>.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if this property is set after serialization or deserialization has occurred.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the indent size is set to a negative value.
/// </exception>
public int IndentSize
{
get => _indentSize;
set
{
VerifyMutable();

if (value < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException_IndentSizeMustBeGreaterThanOrEqualToZero(nameof(value));
}

_indentSize = value;
}
}

internal int EffectiveMaxDepth { get; private set; } = DefaultMaxDepth;

/// <summary>
Expand Down Expand Up @@ -547,6 +576,31 @@ public bool WriteIndented
}
}

/// <summary>
/// Defines the whitespace character to indent with when <see cref="WriteIndented"/> is set to true, with the default (i.e. '\0') inidicating space (' ').
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if this property is set after serialization or deserialization has occurred.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the indent character is set to a non-whitespace character.
/// </exception>
public char IndentCharacter
{
get => _indentCharacter;
set
{
VerifyMutable();

if (value is not ' ' and not '\t' and not '\0')
{
ThrowHelper.ThrowArgumentException_InvalidIndentCharacter();
}

_indentCharacter = value;
}
}

/// <summary>
/// Configures how object references are handled when reading and writing JSON.
/// </summary>
Expand Down Expand Up @@ -713,6 +767,8 @@ internal JsonWriterOptions GetWriterOptions()
Encoder = Encoder,
Indented = WriteIndented,
MaxDepth = EffectiveMaxDepth,
IndentSize = IndentSize,
IndentCharacter = IndentCharacter,
#if !DEBUG
SkipValidation = true
#endif
Expand Down
12 changes: 12 additions & 0 deletions src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ public static void ThrowArgumentOutOfRangeException_MaxDepthMustBePositive(strin
throw GetArgumentOutOfRangeException(parameterName, SR.MaxDepthMustBePositive);
}

[DoesNotReturn]
public static void ThrowArgumentOutOfRangeException_IndentSizeMustBeGreaterThanOrEqualToZero(string parameterName)
{
throw GetArgumentOutOfRangeException(parameterName, SR.IndentSizeMustBeGreaterThanOrEqualToZero);
}

[DoesNotReturn]
public static void ThrowArgumentException_InvalidIndentCharacter()
{
throw GetArgumentException(SR.InvalidIndentCharacter);
}

private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(string parameterName, string message)
{
return new ArgumentOutOfRangeException(parameterName, message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,23 @@ namespace System.Text.Json
{
internal static partial class JsonWriterHelper
{
public static void WriteIndentation(Span<byte> buffer, int indent)
public static void WriteIndentation(Span<byte> buffer, int indent, byte indentChar)
{
Debug.Assert(indent % JsonConstants.SpacesPerIndent == 0);
Debug.Assert(indentChar is JsonConstants.Tab or JsonConstants.Space);
Debug.Assert(buffer.Length >= indent);

// Based on perf tests, the break-even point where vectorized Fill is faster
// than explicitly writing the space in a loop is 8.
if (indent < 8)
{
int i = 0;
while (i < indent)
for (int i = 0; i < indent; i++)
{
buffer[i++] = JsonConstants.Space;
buffer[i++] = JsonConstants.Space;
buffer[i] = indentChar;
}
}
else
{
buffer.Slice(0, indent).Fill(JsonConstants.Space);
buffer.Slice(0, indent).Fill(indentChar);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public struct JsonWriterOptions

private int _maxDepth;
private int _optionsMask;
private int _indentSize;

/// <summary>
/// The encoder to use when escaping strings, or <see langword="null" /> to use the default encoder.
Expand All @@ -43,6 +44,51 @@ public bool Indented
}
}

internal byte IndentByte { get; private set; }

/// <summary>
/// Defines the whitespace character to indent with when <see cref="Indented"/> is set to true, with the default (i.e. '\0') inidicating space (' ').
/// </summary>
/// <exception cref="ArgumentException">
/// Thrown when the indent character is set to a non-whitespace character.
/// </exception>
public char IndentCharacter
{
get => (char)IndentByte;
set
{
if (value is not ' ' and not '\t' and not '\0')
{
ThrowHelper.ThrowArgumentException_InvalidIndentCharacter();
}

IndentByte = (byte)value;
}
}

/// <summary>
/// Defines the number of whitespace characters to write per indentation level, with the default (i.e. 0) indicating an indent size of 2.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the indent size is set to a negative value.
/// </exception>
public int IndentSize
{
get
{
return _indentSize;
}
set
{
if (value < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException_IndentSizeMustBeGreaterThanOrEqualToZero(nameof(value));
}

_indentSize = value;
}
}

/// <summary>
/// Gets or sets the maximum depth allowed when writing JSON, with the default (i.e. 0) indicating a max depth of 1000.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ private void WriteBase64Minimized(ReadOnlySpan<byte> escapedPropertyName, ReadOn
private void WriteBase64Indented(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> bytes)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * _options.MaxDepth);
Debug.Assert(indent <= _options.IndentSize * _options.MaxDepth);

int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);

Expand Down Expand Up @@ -305,7 +305,7 @@ private void WriteBase64Indented(ReadOnlySpan<char> escapedPropertyName, ReadOnl
WriteNewLine(output);
}

JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent, _options.IndentByte);
BytesPending += indent;

output[BytesPending++] = JsonConstants.Quote;
Expand All @@ -326,7 +326,7 @@ private void WriteBase64Indented(ReadOnlySpan<char> escapedPropertyName, ReadOnl
private void WriteBase64Indented(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> bytes)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * _options.MaxDepth);
Debug.Assert(indent <= _options.IndentSize * _options.MaxDepth);

int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);

Expand Down Expand Up @@ -355,7 +355,7 @@ private void WriteBase64Indented(ReadOnlySpan<byte> escapedPropertyName, ReadOnl
WriteNewLine(output);
}

JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent, _options.IndentByte);
BytesPending += indent;

output[BytesPending++] = JsonConstants.Quote;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, DateTi
private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTime value)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * _options.MaxDepth);
Debug.Assert(indent <= _options.IndentSize * _options.MaxDepth);

Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength);

Expand All @@ -305,7 +305,7 @@ private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTim
WriteNewLine(output);
}

JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent, _options.IndentByte);
BytesPending += indent;

output[BytesPending++] = JsonConstants.Quote;
Expand All @@ -327,7 +327,7 @@ private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTim
private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, DateTime value)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * _options.MaxDepth);
Debug.Assert(indent <= _options.IndentSize * _options.MaxDepth);

Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength);

Expand All @@ -353,7 +353,7 @@ private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, DateTim
WriteNewLine(output);
}

JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent, _options.IndentByte);
BytesPending += indent;

output[BytesPending++] = JsonConstants.Quote;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, DateTi
private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTimeOffset value)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * _options.MaxDepth);
Debug.Assert(indent <= _options.IndentSize * _options.MaxDepth);

Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength);

Expand All @@ -304,7 +304,7 @@ private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTim
WriteNewLine(output);
}

JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent, _options.IndentByte);
BytesPending += indent;

output[BytesPending++] = JsonConstants.Quote;
Expand All @@ -326,7 +326,7 @@ private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTim
private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, DateTimeOffset value)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * _options.MaxDepth);
Debug.Assert(indent <= _options.IndentSize * _options.MaxDepth);

Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength);

Expand All @@ -352,7 +352,7 @@ private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, DateTim
WriteNewLine(output);
}

JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent, _options.IndentByte);
BytesPending += indent;

output[BytesPending++] = JsonConstants.Quote;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, decima
private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, decimal value)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * _options.MaxDepth);
Debug.Assert(indent <= _options.IndentSize * _options.MaxDepth);

Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDecimalLength - 5 - s_newLineLength);

Expand All @@ -298,7 +298,7 @@ private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, decimal
WriteNewLine(output);
}

JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent, _options.IndentByte);
BytesPending += indent;

output[BytesPending++] = JsonConstants.Quote;
Expand All @@ -317,7 +317,7 @@ private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, decimal
private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, decimal value)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * _options.MaxDepth);
Debug.Assert(indent <= _options.IndentSize * _options.MaxDepth);

Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDecimalLength - 5 - s_newLineLength);

Expand All @@ -343,7 +343,7 @@ private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, decimal
WriteNewLine(output);
}

JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent, _options.IndentByte);
BytesPending += indent;

output[BytesPending++] = JsonConstants.Quote;
Expand Down
Loading

0 comments on commit e7a73b3

Please sign in to comment.