如何仅对某些属性应用缩进序列化?

我希望以人类可读的方式将.NET对象序列化为JSON,但我希望能够更好地控制对象的属性或数组的元素是否最终在自己的行上。

目前我正在使用JSON.NET的JsonConvert.SerializeObject(object, Formatting, JsonSerializerSettings)方法进行序列化,但似乎我只能应用Formatting.Indented (单个行上的所有元素)或Formatting.None (一行上的所有内容)没有任何空格)整个对象的全局格式规则。 有没有办法在默认情况下全局使用缩进,但是为某些类或属性关闭它,例如使用属性或其他参数?

为了帮助您理解该问题,以下是一些输出示例。 使用Formatting.None

 {"array":["element 1","element 2","element 3"],"object":{"property1":"value1","property2":"value2"}} 

使用Formatting.Indented

 { "array": [ "element 1", "element 2", "element 3" ], "object": { "property1": "value1", "property2":"value2" } } 

我想看到的:

 { "array": ["element 1","element 2","element 3"], "object": {"property1":"value1","property2":"value2"} } 

(我意识到我的问题可能与这个问题有点相关,但那里的评论完全忽略了这一点,并没有真正提供有效答案。)

一种可能性是为您需要特殊处理的特定类型编写自定义Json转换器并切换它们的格式:

 class Program { static void Main() { var root = new Root { Array = new[] { "element 1", "element 2", "element 3" }, Object = new Obj { Property1 = "value1", Property2 = "value2", }, }; var settings = new JsonSerializerSettings { Formatting = Formatting.Indented, }; settings.Converters.Add(new MyConverter()); string json = JsonConvert.SerializeObject(root, settings); Console.WriteLine(json); } } public class Root { public string[] Array { get; set; } public Obj Object { get; set; } } public class Obj { public string Property1 { get; set; } public string Property2 { get; set; } } class MyConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(string[]) || objectType == typeof(Obj); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { writer.WriteRawValue(JsonConvert.SerializeObject(value, Formatting.None)); } } 

这将输出:

 { "Array": ["element 1","element 2","element 3"], "Object": {"Property1":"value1","Property2":"value2"} } 

我也使用了转换器(根据Darin Dimitrov的回答),但是我没有调用WriteRawValue(),而是使用每个元素的序列化器; 这将确保使用适用于元素类型的任何自定义转换器。

但请注意,此转换器仅在少数基本类型的数组上运行,它不使用Newtonsoft.Json逻辑来确定应该序列化为数组的内容以及基本类型是什么,主要是因为该代码是内部的,我想要避免保留它的副本。

总的来说,我觉得转换器并不打算用于格式化这样的任务,但我认为它们是当前API中唯一的选项。 理想情况下,API会提供更多格式化选项,或者可能更好地支持转换器API中的自定义格式。

 using System; using System.Collections.Generic; using Newtonsoft.Json; namespace JsonProto { ///  /// A JsonConverter that modifies formatting of arrays, such that the array elements are serialised to a single line instead of one element per line /// preceded by indentation whitespace. /// This converter handles writing JSON only; CanRead returns false. ///  ///  /// This converter/formatter applies to arrays only and not other collection types. Ideally we would use the existing logic within Newtonsoft.Json for /// identifying collections of items, as this handles a number of special cases (eg string implements IEnumerable over the string characters). In order /// to avoid duplicating in lots of logic, instead this converter handles only Arrays of a handful of selected primitive types. ///  public class ArrayNoFormattingConverter : JsonConverter { # region Static Fields static HashSet _primitiveTypeSet = new HashSet { typeof(char), typeof(char?), typeof(bool), typeof(bool?), typeof(sbyte), typeof(sbyte?), typeof(short), typeof(short?), typeof(ushort), typeof(ushort?), typeof(int), typeof(int?), typeof(byte), typeof(byte?), typeof(uint), typeof(uint?), typeof(long), typeof(long?), typeof(ulong), typeof(ulong?), typeof(float), typeof(float?), typeof(double), typeof(double?), typeof(decimal), typeof(decimal?), typeof(string), typeof(DateTime), typeof(DateTime?), }; #endregion #region Properties ///  /// Determines whether this instance can convert the specified object type. ///  /// Type of the object. ///  /// true if this instance can convert the specified object type; otherwise, false. ///  public override bool CanConvert(Type objectType) { // Note. Ideally this would match the test for JsonContractType.Array in DefaultContractResolver.CreateContract(), // but that code is all internal to Newtonsoft.Json. // Here we elect to take over conversion for Arrays only. if(!objectType.IsArray) { return false; } // Fast/efficient way of testing for multiple possible primitive types. Type elemType = objectType.GetElementType(); return _primitiveTypeSet.Contains(elemType); } ///  /// Gets a value indicating whether this  can read JSON. ///  /// Always returns false. public override bool CanRead { get { return false; } } #endregion #region Public Methods ///  /// Reads the JSON representation of the object. (Not implemented on this converter). ///  /// The  to read from. /// Type of the object. /// The existing value of object being read. /// The calling serializer. /// The object value. public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); } ///  /// Writes the JSON representation of the object. ///  /// The  to write to. /// The value. /// The calling serializer. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { Formatting formatting = writer.Formatting; writer.WriteStartArray(); try { writer.Formatting = Formatting.None; foreach(object childValue in ((System.Collections.IEnumerable)value)) { serializer.Serialize(writer, childValue); } } finally { writer.WriteEndArray(); writer.Formatting = formatting; } } #endregion } }