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

本文关键字:应用 缩进 序列化 属性 何仅 | 更新日期: 2024-07-27 03:17:18

我想以可读的方式将.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
{
    /// <summary>
    /// 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.
    /// </summary>
    /// <remarks>
    /// 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 (e.g. 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.
    /// </remarks>
    public class ArrayNoFormattingConverter : JsonConverter
    {
        # region Static Fields    
        static HashSet<Type> _primitiveTypeSet = 
            new HashSet<Type> 
            { 
                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
        /// <summary>
        /// Determines whether this instance can convert the specified object type.
        /// </summary>
        /// <param name="objectType">Type of the object.</param>
        /// <returns>
        ///     <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
        /// </returns>
        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);
        }
        /// <summary>
        /// Gets a value indicating whether this <see cref="JsonConverter"/> can read JSON.
        /// </summary>
        /// <value>Always returns <c>false</c>.</value>
        public override bool CanRead
        {
            get { return false; }
        }
        #endregion
        #region Public Methods
        /// <summary>
        /// Reads the JSON representation of the object. (Not implemented on this converter).
        /// </summary>
        /// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
        /// <param name="objectType">Type of the object.</param>
        /// <param name="existingValue">The existing value of object being read.</param>
        /// <param name="serializer">The calling serializer.</param>
        /// <returns>The object value.</returns>
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Writes the JSON representation of the object.
        /// </summary>
        /// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
        /// <param name="value">The value.</param>
        /// <param name="serializer">The calling serializer.</param>
        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
    }
}