How to apply indenting serialization only to some properties?
One possibility would be to write a custom Json converter for the specific types you need special handling and switch the formatting for them:
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));
}
}
This will output:
{
"Array": ["element 1","element 2","element 3"],
"Object": {"Property1":"value1","Property2":"value2"}
}
I also used a converter for this (as per Darin Dimitrov's answer), but instead of calling WriteRawValue() I use the serializer for each element; which ensures any custom converters that apply to the element type will be used.
Note however that this converter is only operating on Arrays of a handful of primitive types, it's not using the Newtonsoft.Json logic for determining what should be serialized as an array and what a primitive type is, basically because that code is internal and I wanted to avoid maintaining a copy of it.
Overall I get the feeling that converters aren't intended to be used for formatting tasks such as this, but I think they're the only option in the current API. Ideally the API would offer a few more formatting options or perhaps better support for custom formatting within the converter 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
}
}