问题描述
编辑:我昨天在.Net runtime repo上提出了一个问题,这个问题由";layomia";关闭,消息是:";添加这样的扩展点会导致较低级别的阅读器和编写器的性能成本降低,并且没有在性能和功能/好处之间提供良好的平衡。System.Text.Json路线图中没有提供此类配置。
设置JsonSerializerOptions.WriteIndented=True时,在编写json时缩进如下所示...
{
"TILESET": "tilesets/HOW_TO_GET_TILESET_NAME_?",
"TILES": {
"TILE_1": {
"NAME": "auto_tile_18",
"TEXTURE_BOUNDS": [
304,
16,
16,
16
],
"SCREEN_BOUNDS": [
485,
159,
64,
64
]
}
}
}
有没有办法将自动缩进更改为这样...
{
"TILESET": "tilesets/HOW_TO_GET_TILESET_NAME_?",
"TILES":
{
"TILE_1":
{
"NAME": "auto_tile_18",
"TEXTURE_BOUNDS": [304, 16, 16,16],
"SCREEN_BOUNDS": [485, 159, 64, 64]
}
}
}
推荐答案
目前System.Text.Json
不可能(从.NET 5开始)。让我们考虑一下这些可能性:
没有控制缩进的方法JsonSerializerOptions
除了Boolean属性WriteIndented
:获取或设置一个值,该值定义JSON是否应使用美观打印。
Utf8JsonWriter
没有修改或控制缩进的方法,因为Options
是一个只获取struct
值的属性。在.Net Core 3.1中,如果我为
TEXTURE_BOUNDS
和SCREEN_BOUNDS
列表创建customJsonConverter<T>
,并在序列化过程中尝试设置options.WriteIndented = false;
,则会引发System.InvalidOperationException:一旦发生序列化或反序列化就无法更改序列化程序选项异常。具体地说,如果我创建以下转换器:
class CollectionFormattingConverter<TCollection, TItem> : JsonConverter<TCollection> where TCollection : class, ICollection<TItem>, new() { public override TCollection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => JsonSerializer.Deserialize<CollectionSurrogate<TCollection, TItem>>(ref reader, options)?.BaseCollection; public override void Write(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options) { var old = options.WriteIndented; try { options.WriteIndented = false; JsonSerializer.Serialize(writer, new CollectionSurrogate<TCollection, TItem>(value), options); } finally { options.WriteIndented = old; } } } public class CollectionSurrogate<TCollection, TItem> : ICollection<TItem> where TCollection : ICollection<TItem>, new() { public TCollection BaseCollection { get; } public CollectionSurrogate() { this.BaseCollection = new TCollection(); } public CollectionSurrogate(TCollection baseCollection) { this.BaseCollection = baseCollection ?? throw new ArgumentNullException(); } public void Add(TItem item) => BaseCollection.Add(item); public void Clear() => BaseCollection.Clear(); public bool Contains(TItem item) => BaseCollection.Contains(item); public void CopyTo(TItem[] array, int arrayIndex) => BaseCollection.CopyTo(array, arrayIndex); public int Count => BaseCollection.Count; public bool IsReadOnly => BaseCollection.IsReadOnly; public bool Remove(TItem item) => BaseCollection.Remove(item); public IEnumerator<TItem> GetEnumerator() => BaseCollection.GetEnumerator(); System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => ((IEnumerable)BaseCollection).GetEnumerator(); }
和以下数据模型:
public partial class Root { [JsonPropertyName("TILESET")] public string Tileset { get; set; } [JsonPropertyName("TILES")] public Tiles Tiles { get; set; } } public partial class Tiles { [JsonPropertyName("TILE_1")] public Tile1 Tile1 { get; set; } } public partial class Tile1 { [JsonPropertyName("NAME")] public string Name { get; set; } [JsonPropertyName("TEXTURE_BOUNDS")] [JsonConverter(typeof(CollectionFormattingConverter<List<long>, long>))] public List<long> TextureBounds { get; set; } [JsonPropertyName("SCREEN_BOUNDS")] [JsonConverter(typeof(CollectionFormattingConverter<List<long>, long>))] public List<long> ScreenBounds { get; set; } }
然后序列化
Root
会引发以下异常:Failed with unhandled exception: System.InvalidOperationException: Serializer options cannot be changed once serialization or deserialization has occurred. at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_SerializerOptionsImmutable() at System.Text.Json.JsonSerializerOptions.set_WriteIndented(Boolean value) at CollectionFormattingConverter`2.Write(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options) at System.Text.Json.JsonPropertyInfoNotNullable`4.OnWrite(WriteStackFrame& current, Utf8JsonWriter writer) at System.Text.Json.JsonPropertyInfo.Write(WriteStack& state, Utf8JsonWriter writer) at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.JsonSerializer.WriteCore(Utf8JsonWriter writer, Object value, Type type, JsonSerializerOptions options) at System.Text.Json.JsonSerializer.WriteCore(PooledByteBufferWriter output, Object value, Type type, JsonSerializerOptions options) at System.Text.Json.JsonSerializer.WriteCoreString(Object value, Type type, JsonSerializerOptions options) at System.Text.Json.JsonSerializer.Serialize[TValue](TValue value, JsonSerializerOptions options)
演示小提琴#1here。
在.Net Core 3.1中,如果我创建自定义
JsonConverter<T>
以创建预格式化的JsonDocument
,然后将其写出,则文档将在写入时重新格式化。例如,如果我创建以下转换器:
class CollectionFormattingConverter<TCollection, TItem> : JsonConverter<TCollection> where TCollection : class, ICollection<TItem>, new() { public override TCollection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => JsonSerializer.Deserialize<CollectionSurrogate<TCollection, TItem>>(ref reader, options)?.BaseCollection; public override void Write(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options) { var copy = options.Clone(); copy.WriteIndented = false; using var doc = JsonExtensions.JsonDocumentFromObject(new CollectionSurrogate<TCollection, TItem>(value), copy); Debug.WriteLine("Preformatted JsonDocument: {0}", doc.RootElement); doc.WriteTo(writer); } } public static partial class JsonExtensions { public static JsonSerializerOptions Clone(this JsonSerializerOptions options) { if (options == null) return new JsonSerializerOptions(); //In .Net 5 a copy constructor will be introduced for JsonSerializerOptions. Use the following in that version. //return new JsonSerializerOptions(options); //In the meantime copy manually. var clone = new JsonSerializerOptions { AllowTrailingCommas = options.AllowTrailingCommas, DefaultBufferSize = options.DefaultBufferSize, DictionaryKeyPolicy = options.DictionaryKeyPolicy, Encoder = options.Encoder, IgnoreNullValues = options.IgnoreNullValues, IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties, MaxDepth = options.MaxDepth, PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive, PropertyNamingPolicy = options.PropertyNamingPolicy, ReadCommentHandling= options.ReadCommentHandling, WriteIndented = options.WriteIndented, }; foreach (var converter in options.Converters) clone.Converters.Add(converter); return clone; } // Copied from this answer https://stackoverflow.com/a/62998253/3744182 // To https://stackoverflow.com/questions/62996999/convert-object-to-system-text-json-jsonelement // By https://stackoverflow.com/users/3744182/dbc public static JsonDocument JsonDocumentFromObject<TValue>(TValue value, JsonSerializerOptions options = default) => JsonDocumentFromObject(value, typeof(TValue), options); public static JsonDocument JsonDocumentFromObject(object value, Type type, JsonSerializerOptions options = default) { var bytes = JsonSerializer.SerializeToUtf8Bytes(value, options); return JsonDocument.Parse(bytes); } }
尽管中间
JsonDocument doc
是在没有缩进的情况下序列化的,但仍会生成完全缩进的JSON:{ "TILESET": "tilesets/HOW_TO_GET_TILESET_NAME_?", "TILES": { "TILE_1": { "NAME": "auto_tile_18", "TEXTURE_BOUNDS": [ 304, 16, 16, 16 ], "SCREEN_BOUNDS": [ 485, 159, 64, 64 ] } } }
演示小提琴#2here。
最后,在.Net Core 3.1中,如果我创建一个自定义
JsonConverter<T>
来克隆传入的JsonSerializerOptions
,修改副本上的WriteIndented
,然后使用复制的设置递归序列化--WriteIndented
的修改值将被忽略。演示小提琴#3here。
显然,
JsonConverter
架构将在.Net 5中得到广泛增强,因此您可以在此选项发布时重新测试它。
您可能希望打开请求此功能的issue,因为有多个关于如何使用Json.NET执行此操作的常见问题(其中可以使用转换器完成):
- How to apply indenting serialization only to some properties?
- Newtonsoft inline formatting for subelement while serializing
- Creating JSON without array indentation
这篇关于在System.Text.Json中,是否可以指定定制的缩进规则?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!