Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to apply indenting serialization only to some properties?

I want to serialize .NET objects to JSON in a human-readable way, but I would like to have more control about whether an object's properties or array's elements end up on a line of their own.

Currently I'm using JSON.NET's JsonConvert.SerializeObject(object, Formatting, JsonSerializerSettings) method for serialization, but it seems I can only apply the Formatting.Indented (all elements on individual lines) or Formatting.None (everything on a single line without any whitespace) formatting rules globally for the entire object. Is there a way to globally use indenting by default, but turn it off for certain classes or properties, e.g. using attributes or other parameters?

To help you understand the problem, here are some output examples. Using Formatting.None:

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

Using Formatting.Indented:

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

What I would like to see:

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

(I realize my question may be slightly related to this one, but the comments there totally miss the point and don't actually provide a valid answer.)

like image 574
Wormbo Avatar asked Feb 22 '15 08:02

Wormbo


People also ask

What is the name of the attribute that excludes a class property from serialization?

Transient is the Java way to exclude from serialization, then our field will also be filtered out by Serializable's serialization, and by every library tool or framework managing our objects.

What is Jsonconvert SerializeObject C#?

SerializeObject Method (Object, Type, JsonSerializerSettings) Serializes the specified object to a JSON string using a type, formatting and JsonSerializerSettings. Namespace: Newtonsoft.Json.

Can JSON serialize a list?

Json.NET has excellent support for serializing and deserializing collections of objects. To serialize a collection - a generic list, array, dictionary, or your own custom collection - simply call the serializer with the object you want to get JSON for.


2 Answers

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"}
}
like image 70
Darin Dimitrov Avatar answered Oct 12 '22 23:10

Darin Dimitrov


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
    }
}
like image 32
redcalx Avatar answered Oct 12 '22 23:10

redcalx