Skip to content

Commit 37ed0ee

Browse files
Allow specifying IndentCharacter and IndentSize when writing JSON (dotnet#95292)
* Add IndentText json option * Add IndentText for json source generator * Add tests * IndentText must be non-nullable * Improve performance * Add extra tests * Cleanup * Apply suggestions from code review Co-authored-by: Eirik Tsarpalis <[email protected]> * Fixes following code review * Fixes following code review #2 * Add tests for invalid characters * Handle RawIndent length * Move all to RawIndentation * Update documentation * Additional fixes from code review * Move to the new API * Extra fixes and enhancements * Fixes from code review * Avoid introducing extra fields in JsonWriterOptions * Fix OOM error * Use bitwise logic for IndentedOrNotSkipValidation * Cache indentation options in Utf8JsonWriter * Add missing test around indentation options * New fixes from code review * Update src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs * Add test to check default values of the JsonWriterOptions properties * Fix comment --------- Co-authored-by: Eirik Tsarpalis <[email protected]>
1 parent 0823c5c commit 37ed0ee

File tree

45 files changed

+1087
-1131
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1087
-1131
lines changed

src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerConfigureOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public void EnsureConsoleLoggerOptions_ConfigureOptions_SupportsAllProperties()
2525
Assert.Equal(3, typeof(ConsoleFormatterOptions).GetProperties(flags).Length);
2626
Assert.Equal(5, typeof(SimpleConsoleFormatterOptions).GetProperties(flags).Length);
2727
Assert.Equal(4, typeof(JsonConsoleFormatterOptions).GetProperties(flags).Length);
28-
Assert.Equal(4, typeof(JsonWriterOptions).GetProperties(flags).Length);
28+
Assert.Equal(6, typeof(JsonWriterOptions).GetProperties(flags).Length);
2929
}
3030

3131
[Theory]

src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@ private static void VerifyHasOnlySimpleProperties(Type type)
597597
// or else NativeAOT would break
598598
Assert.True(prop.PropertyType == typeof(string) ||
599599
prop.PropertyType == typeof(bool) ||
600+
prop.PropertyType == typeof(char) ||
600601
prop.PropertyType == typeof(int) ||
601602
prop.PropertyType.IsEnum, $"ConsoleOptions property '{type.Name}.{prop.Name}' must be a simple type in order for NativeAOT to work");
602603
}

src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,16 @@ public JsonSourceGenerationOptionsAttribute(JsonSerializerDefaults defaults)
125125
/// </summary>
126126
public bool WriteIndented { get; set; }
127127

128+
/// <summary>
129+
/// Specifies the default value of <see cref="JsonSerializerOptions.IndentCharacter"/> when set.
130+
/// </summary>
131+
public char IndentCharacter { get; set; }
132+
133+
/// <summary>
134+
/// Specifies the default value of <see cref="JsonSerializerOptions.IndentCharacter"/> when set.
135+
/// </summary>
136+
public int IndentSize { get; set; }
137+
128138
/// <summary>
129139
/// Specifies the default source generation mode for type declarations that don't set a <see cref="JsonSerializableAttribute.GenerationMode"/>.
130140
/// </summary>

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,12 @@ private static void GetLogicForDefaultSerializerOptionsInit(SourceGenerationOpti
11681168
if (optionsSpec.WriteIndented is bool writeIndented)
11691169
writer.WriteLine($"WriteIndented = {FormatBool(writeIndented)},");
11701170

1171+
if (optionsSpec.IndentCharacter is char indentCharacter)
1172+
writer.WriteLine($"IndentCharacter = {FormatIndentChar(indentCharacter)},");
1173+
1174+
if (optionsSpec.IndentSize is int indentSize)
1175+
writer.WriteLine($"IndentSize = {indentSize},");
1176+
11711177
writer.Indentation--;
11721178
writer.WriteLine("};");
11731179

@@ -1344,6 +1350,7 @@ private static string FormatJsonSerializerDefaults(JsonSerializerDefaults defaul
13441350

13451351
private static string FormatBool(bool value) => value ? "true" : "false";
13461352
private static string FormatStringLiteral(string? value) => value is null ? "null" : $"\"{value}\"";
1353+
private static string FormatIndentChar(char value) => value is '\t' ? "'\\t'" : $"'{value}'";
13471354

13481355
/// <summary>
13491356
/// Method used to generate JsonTypeInfo given options instance

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN
280280
JsonUnmappedMemberHandling? unmappedMemberHandling = null;
281281
bool? useStringEnumConverter = null;
282282
bool? writeIndented = null;
283+
char? indentCharacter = null;
284+
int? indentSize = null;
283285

284286
if (attributeData.ConstructorArguments.Length > 0)
285287
{
@@ -373,6 +375,14 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN
373375
writeIndented = (bool)namedArg.Value.Value!;
374376
break;
375377

378+
case nameof(JsonSourceGenerationOptionsAttribute.IndentCharacter):
379+
indentCharacter = (char)namedArg.Value.Value!;
380+
break;
381+
382+
case nameof(JsonSourceGenerationOptionsAttribute.IndentSize):
383+
indentSize = (int)namedArg.Value.Value!;
384+
break;
385+
376386
case nameof(JsonSourceGenerationOptionsAttribute.GenerationMode):
377387
generationMode = (JsonSourceGenerationMode)namedArg.Value.Value!;
378388
break;
@@ -404,6 +414,8 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN
404414
UnmappedMemberHandling = unmappedMemberHandling,
405415
UseStringEnumConverter = useStringEnumConverter,
406416
WriteIndented = writeIndented,
417+
IndentCharacter = indentCharacter,
418+
IndentSize = indentSize,
407419
};
408420
}
409421

src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ public sealed record SourceGenerationOptionsSpec
5252

5353
public required bool? WriteIndented { get; init; }
5454

55+
public required char? IndentCharacter { get; init; }
56+
57+
public required int? IndentSize { get; init; }
58+
5559
public JsonKnownNamingPolicy? GetEffectivePropertyNamingPolicy()
5660
=> PropertyNamingPolicy ?? (Defaults is JsonSerializerDefaults.Web ? JsonKnownNamingPolicy.CamelCase : null);
5761
}

src/libraries/System.Text.Json/ref/System.Text.Json.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,8 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { }
395395
public System.Text.Json.Serialization.JsonUnknownTypeHandling UnknownTypeHandling { get { throw null; } set { } }
396396
public System.Text.Json.Serialization.JsonUnmappedMemberHandling UnmappedMemberHandling { get { throw null; } set { } }
397397
public bool WriteIndented { get { throw null; } set { } }
398+
public char IndentCharacter { get { throw null; } set { } }
399+
public int IndentSize { get { throw null; } set { } }
398400
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
399401
[System.ObsoleteAttribute("JsonSerializerOptions.AddContext is obsolete. To register a JsonSerializerContext, use either the TypeInfoResolver or TypeInfoResolverChain properties.", DiagnosticId="SYSLIB0049", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
400402
public void AddContext<TContext>() where TContext : System.Text.Json.Serialization.JsonSerializerContext, new() { }
@@ -440,6 +442,8 @@ public partial struct JsonWriterOptions
440442
private int _dummyPrimitive;
441443
public System.Text.Encodings.Web.JavaScriptEncoder? Encoder { readonly get { throw null; } set { } }
442444
public bool Indented { get { throw null; } set { } }
445+
public char IndentCharacter { get { throw null; } set { } }
446+
public int IndentSize { get { throw null; } set { } }
443447
public int MaxDepth { readonly get { throw null; } set { } }
444448
public bool SkipValidation { get { throw null; } set { } }
445449
}
@@ -1075,6 +1079,8 @@ public JsonSourceGenerationOptionsAttribute(System.Text.Json.JsonSerializerDefau
10751079
public System.Text.Json.Serialization.JsonUnmappedMemberHandling UnmappedMemberHandling { get { throw null; } set { } }
10761080
public bool UseStringEnumConverter { get { throw null; } set { } }
10771081
public bool WriteIndented { get { throw null; } set { } }
1082+
public char IndentCharacter { get { throw null; } set { } }
1083+
public int IndentSize { get { throw null; } set { } }
10781084
}
10791085
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JsonStringEnumConverter cannot be statically analyzed and requires runtime code generation. Applications should use the generic JsonStringEnumConverter<TEnum> instead.")]
10801086
public partial class JsonStringEnumConverter : System.Text.Json.Serialization.JsonConverterFactory

src/libraries/System.Text.Json/src/Resources/Strings.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,4 +708,10 @@
708708
<data name="FormatHalf" xml:space="preserve">
709709
<value>Either the JSON value is not in a supported format, or is out of bounds for a Half.</value>
710710
</data>
711+
<data name="InvalidIndentCharacter" xml:space="preserve">
712+
<value>Supported indentation characters are space and horizontal tab.</value>
713+
</data>
714+
<data name="InvalidIndentSize" xml:space="preserve">
715+
<value>Indentation size must be between {0} and {1}.</value>
716+
</data>
711717
</root>

src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ internal static partial class JsonConstants
4747
// Explicitly skipping ReverseSolidus since that is handled separately
4848
public static ReadOnlySpan<byte> EscapableChars => "\"nrt/ubf"u8;
4949

50-
public const int SpacesPerIndent = 2;
5150
public const int RemoveFlagsBitMask = 0x7FFFFFFF;
5251

5352
// In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped.
@@ -110,5 +109,13 @@ internal static partial class JsonConstants
110109
// The maximum number of parameters a constructor can have where it can be considered
111110
// for a path on deserialization where we don't box the constructor arguments.
112111
public const int UnboxedParameterCountThreshold = 4;
112+
113+
// Two space characters is the default indentation.
114+
public const char DefaultIndentCharacter = ' ';
115+
public const char TabIndentCharacter = '\t';
116+
public const int DefaultIndentSize = 2;
117+
public const int MinimumIndentSize = 0;
118+
public const int MaximumIndentSize = 127; // If this value is changed, the impact on the options masking used in the JsonWriterOptions struct must be checked carefully.
119+
113120
}
114121
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,8 @@ public bool Equals(JsonSerializerOptions? left, JsonSerializerOptions? right)
511511
left._includeFields == right._includeFields &&
512512
left._propertyNameCaseInsensitive == right._propertyNameCaseInsensitive &&
513513
left._writeIndented == right._writeIndented &&
514+
left._indentCharacter == right._indentCharacter &&
515+
left._indentSize == right._indentSize &&
514516
left._typeInfoResolver == right._typeInfoResolver &&
515517
CompareLists(left._converters, right._converters);
516518

@@ -565,6 +567,8 @@ public int GetHashCode(JsonSerializerOptions options)
565567
AddHashCode(ref hc, options._includeFields);
566568
AddHashCode(ref hc, options._propertyNameCaseInsensitive);
567569
AddHashCode(ref hc, options._writeIndented);
570+
AddHashCode(ref hc, options._indentCharacter);
571+
AddHashCode(ref hc, options._indentSize);
568572
AddHashCode(ref hc, options._typeInfoResolver);
569573
AddListHashCode(ref hc, options._converters);
570574

0 commit comments

Comments
 (0)