JSON formatter in C#?

C#JsonFormatting

C# Problem Overview


Looking for a function that will take a string of Json as input and format it with line breaks and indentations. Validation would be a bonus, but isn't necessary, and I don't need to parse it into an object or anything.

Anyone know of such a library?


Sample input:

{"status":"OK", "results":[ {"types":[ "locality", "political"], "formatted_address":"New York, NY, USA", "address_components":[ {"long_name":"New York", "short_name":"New York", "types":[ "locality", "political"]}, {"long_name":"New York", "short_name":"New York", "types":[ "administrative_area_level_2", "political"]}, {"long_name":"New York", "short_name":"NY", "types":[ "administrative_area_level_1", "political"]}, {"long_name":"United States", "short_name":"US", "types":[ "country", "political"]}], "geometry":{"location":{"lat":40.7143528, "lng":-74.0059731}, "location_type":"APPROXIMATE", "viewport":{"southwest":{"lat":40.5788964, "lng":-74.2620919}, "northeast":{"lat":40.8495342, "lng":-73.7498543}}, "bounds":{"southwest":{"lat":40.4773990, "lng":-74.2590900}, "northeast":{"lat":40.9175770, "lng":-73.7002720}}}}]} 

C# Solutions


Solution 1 - C#

You could also use the Newtonsoft.Json library for this and call SerializeObject with the Formatting.Indented enum -

var x = JsonConvert.SerializeObject(jsonString, Formatting.Indented);

Documentation: Serialize an Object


Update -

Just tried it again. Pretty sure this used to work - perhaps it changed in a subsequent version or perhaps i'm just imagining things. Anyway, as per the comments below, it doesn't quite work as expected. These do, however (just tested in linqpad). The first one is from the comments, the 2nd one is an example i found elsewhere in SO -

void Main()
{
	//Example 1
	var t = "{\"x\":57,\"y\":57.0,\"z\":\"Yes\"}";
	var obj = Newtonsoft.Json.JsonConvert.DeserializeObject(t); 
	var f = Newtonsoft.Json.JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.Indented);
	Console.WriteLine(f);

	//Example 2
	JToken jt = JToken.Parse(t);
	string formatted = jt.ToString(Newtonsoft.Json.Formatting.Indented);
	Console.WriteLine(formatted);

	//Example 2 in one line -
	Console.WriteLine(JToken.Parse(t).ToString(Newtonsoft.Json.Formatting.Indented));
}

Solution 2 - C#

I updated the old version, now it should support unquoted values such as integers and booleans.

I refactored the previous version and got the final version: The code is shorter and cleaner. Only require one extension method. The most important: fixed some bugs.

class JsonHelper
{
    private const string INDENT_STRING = "    ";
    public static string FormatJson(string str)
    {
        var indent = 0;
        var quoted = false;
        var sb = new StringBuilder();
        for (var i = 0; i < str.Length; i++)
        {
            var ch = str[i];
            switch (ch)
            {
                case '{':
                case '[':
                    sb.Append(ch);
                    if (!quoted)
                    {
                        sb.AppendLine();
                        Enumerable.Range(0, ++indent).ForEach(item => sb.Append(INDENT_STRING));
                    }
                    break;
                case '}':
                case ']':
                    if (!quoted)
                    {
                        sb.AppendLine();
                        Enumerable.Range(0, --indent).ForEach(item => sb.Append(INDENT_STRING));
                    }
                    sb.Append(ch);
                    break;
                case '"':
                    sb.Append(ch);
                    bool escaped = false;
                    var index = i;
                    while (index > 0 && str[--index] == '\\')
                        escaped = !escaped;
                    if (!escaped)
                        quoted = !quoted;
                    break;
                case ',':
                    sb.Append(ch);
                    if (!quoted)
                    {
                        sb.AppendLine();
                        Enumerable.Range(0, indent).ForEach(item => sb.Append(INDENT_STRING));
                    }
                    break;
                case ':':
                    sb.Append(ch);
                    if (!quoted)
                        sb.Append(" ");
                    break;
                default:
                    sb.Append(ch);
                    break;
            }
        }
        return sb.ToString();
    }
}

static class Extensions
{
    public static void ForEach<T>(this IEnumerable<T> ie, Action<T> action)
    {
        foreach (var i in ie)
        {
            action(i);
        }
    }
}

Solution 3 - C#

Shorter sample for json.net library.

using Newtonsoft.Json;

private static string format_json(string json)
{
	dynamic parsedJson = JsonConvert.DeserializeObject(json);
	return JsonConvert.SerializeObject(parsedJson, Formatting.Indented);
}

PS: You can wrap the formatted json text with

 tag to print as it is on the html page.

Solution 4 - C#

This worked for me using System.Text.Json in .Net Core 3.1

 public string PrettyJson(string unPrettyJson)
 {
     var options = new JsonSerializerOptions(){
         WriteIndented = true
     };

     var jsonElement = JsonSerializer.Deserialize<JsonElement>(unPrettyJson);

     return JsonSerializer.Serialize(jsonElement, options);
 }

Solution 5 - C#

Here's a compact version of a JSON beautifier.

private const string INDENT_STRING = "    ";

static string FormatJson(string json) {
	
	int indentation = 0;
	int quoteCount = 0;
	var result = 
		from ch in json
		let quotes = ch == '"' ? quoteCount++ : quoteCount
		let lineBreak = ch == ',' && quotes % 2 == 0 ? ch + Environment.NewLine +  String.Concat(Enumerable.Repeat(INDENT_STRING, indentation)) : null
		let openChar = ch == '{' || ch == '[' ? ch + Environment.NewLine + String.Concat(Enumerable.Repeat(INDENT_STRING, ++indentation)) : ch.ToString()
		let closeChar = ch == '}' || ch == ']' ? Environment.NewLine + String.Concat(Enumerable.Repeat(INDENT_STRING, --indentation)) + ch : ch.ToString()
		select lineBreak == null	
					? openChar.Length > 1 
						? openChar 
						: closeChar
					: lineBreak;

	return String.Concat(result);
}

Outputs:

 {
    "status":"OK",
     "results":[
         {
            "types":[
                 "locality",
                 "political"
            ],
             "formatted_address":"New York, NY, USA",
             "address_components":[
                 {
                    "long_name":"New York",
                     "short_name":"New York",
                     "types":[
                         "locality",
                         "political"
                    ]
                },
                 {
                    "long_name":"New York",
                     "short_name":"New York",
                     "types":[
                         "administrative_area_level_2",
                         "political"
                    ]
                },
                 {
                    "long_name":"New York",
                     "short_name":"NY",
                     "types":[
                         "administrative_area_level_1",
                         "political"
                    ]
                },
                 {
                    "long_name":"United States",
                     "short_name":"US",
                     "types":[
                         "country",
                         "political"
                    ]
                }
            ],
             "geometry":{
                "location":{
                    "lat":40.7143528,
                     "lng":-74.0059731
                },
                 "location_type":"APPROXIMATE",
                 "viewport":{
                    "southwest":{
                        "lat":40.5788964,
                         "lng":-74.2620919
                    },
                     "northeast":{
                        "lat":40.8495342,
                         "lng":-73.7498543
                    }
                },
                 "bounds":{
                    "southwest":{
                        "lat":40.4773990,
                         "lng":-74.2590900
                    },
                     "northeast":{
                        "lat":40.9175770,
                         "lng":-73.7002720
                    }
                }
            }
        }
    ]
}

Solution 6 - C#

I was very impressed by compact JSON formatter by Vince Panuccio.
Here is an improved version I now use:

public static string FormatJson(string json, string indent = "  ")
{
	var indentation = 0;
	var quoteCount = 0;
	var escapeCount = 0;

	var result =
		from ch in json ?? string.Empty
		let escaped = (ch == '\\' ? escapeCount++ : escapeCount > 0 ? escapeCount-- : escapeCount) > 0
		let quotes = ch == '"' && !escaped ? quoteCount++ : quoteCount
		let unquoted = quotes % 2 == 0
		let colon = ch == ':' && unquoted ? ": " : null
		let nospace = char.IsWhiteSpace(ch) && unquoted ? string.Empty : null
		let lineBreak = ch == ',' && unquoted ? ch + Environment.NewLine + string.Concat(Enumerable.Repeat(indent, indentation)) : null
		let openChar = (ch == '{' || ch == '[') && unquoted ? ch + Environment.NewLine + string.Concat(Enumerable.Repeat(indent, ++indentation)) : ch.ToString()
		let closeChar = (ch == '}' || ch == ']') && unquoted ? Environment.NewLine + string.Concat(Enumerable.Repeat(indent, --indentation)) + ch : ch.ToString()
		select colon ?? nospace ?? lineBreak ?? (
			openChar.Length > 1 ? openChar : closeChar
		);

	return string.Concat(result);
}

It fixes the following issues:

  1. Escape sequences inside strings
  2. Missing spaces after colon
  3. Extra spaces after commas (or elsewhere)
  4. Square and curly braces inside strings
  5. Doesn't fail on null input

Outputs:

{
  "status": "OK",
  "results": [
    {
      "types": [
        "locality",
        "political"
      ],
      "formatted_address": "New York, NY, USA",
      "address_components": [
        {
          "long_name": "New York",
          "short_name": "New York",
          "types": [
            "locality",
            "political"
          ]
        },
        {
          "long_name": "New York",
          "short_name": "New York",
          "types": [
            "administrative_area_level_2",
            "political"
          ]
        },
        {
          "long_name": "New York",
          "short_name": "NY",
          "types": [
            "administrative_area_level_1",
            "political"
          ]
        },
        {
          "long_name": "United States",
          "short_name": "US",
          "types": [
            "country",
            "political"
          ]
        }
      ],
      "geometry": {
        "location": {
          "lat": 40.7143528,
          "lng": -74.0059731
        },
        "location_type": "APPROXIMATE",
        "viewport": {
          "southwest": {
            "lat": 40.5788964,
            "lng": -74.2620919
          },
          "northeast": {
            "lat": 40.8495342,
            "lng": -73.7498543
          }
        },
        "bounds": {
          "southwest": {
            "lat": 40.4773990,
            "lng": -74.2590900
          },
          "northeast": {
            "lat": 40.9175770,
            "lng": -73.7002720
          }
        }
      }
    }
  ]
}

Solution 7 - C#

All credits are to Frank Tzanabetis. However this is the shortest direct example, that also survives in case of empty string or broken original JSON string:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

    ...
    try
    {
        return JToken.Parse(jsonString).ToString(Formatting.Indented);
    }
    catch
    {
        return jsonString;

Solution 8 - C#

Even simpler one that I just wrote:

public class JsonFormatter
{
    public static string Indent = "    ";

    public static string PrettyPrint(string input)
    {
        var output = new StringBuilder(input.Length * 2);
        char? quote = null;
        int depth = 0;

        for(int i=0; i<input.Length; ++i)
        {
            char ch = input[i];

            switch (ch)
            {
                case '{':
                case '[':
                    output.Append(ch);
                    if (!quote.HasValue)
                    {
                        output.AppendLine();
                        output.Append(Indent.Repeat(++depth));
                    }
                    break;
                case '}':
                case ']':
                    if (quote.HasValue)  
                        output.Append(ch);
                    else
                    {
                        output.AppendLine();
                        output.Append(Indent.Repeat(--depth));
                        output.Append(ch);
                    }
                    break;
                case '"':
                case '\'':
                    output.Append(ch);
                    if (quote.HasValue)
                    {
                        if (!output.IsEscaped(i))
                            quote = null;
                    }
                    else quote = ch;
                    break;
                case ',':
                    output.Append(ch);
                    if (!quote.HasValue)
                    {
                        output.AppendLine();
                        output.Append(Indent.Repeat(depth));
                    }
                    break;
                case ':':
                    if (quote.HasValue) output.Append(ch);
                    else output.Append(" : ");
                    break;
                default:
                    if (quote.HasValue || !char.IsWhiteSpace(ch)) 
                        output.Append(ch);
                    break;
            }
        }

        return output.ToString();
    }
}

Necessary extensions:

    public static string Repeat(this string str, int count)
    {
        return new StringBuilder().Insert(0, str, count).ToString();
    }

    public static bool IsEscaped(this string str, int index)
    {
        bool escaped = false;
        while (index > 0 && str[--index] == '\\') escaped = !escaped;
        return escaped;
    }

    public static bool IsEscaped(this StringBuilder str, int index)
    {
        return str.ToString().IsEscaped(index);
    }

Sample output:

{
    "status" : "OK",
    "results" : [
        {
            "types" : [
                "locality",
                "political"
            ],
            "formatted_address" : "New York, NY, USA",
            "address_components" : [
                {
                    "long_name" : "New York",
                    "short_name" : "New York",
                    "types" : [
                        "locality",
                        "political"
                    ]
                },
                {
                    "long_name" : "New York",
                    "short_name" : "New York",
                    "types" : [
                        "administrative_area_level_2",
                        "political"
                    ]
                },
                {
                    "long_name" : "New York",
                    "short_name" : "NY",
                    "types" : [
                        "administrative_area_level_1",
                        "political"
                    ]
                },
                {
                    "long_name" : "United States",
                    "short_name" : "US",
                    "types" : [
                        "country",
                        "political"
                    ]
                }
            ],
            "geometry" : {
                "location" : {
                    "lat" : 40.7143528,
                    "lng" : -74.0059731
                },
                "location_type" : "APPROXIMATE",
                "viewport" : {
                    "southwest" : {
                        "lat" : 40.5788964,
                        "lng" : -74.2620919
                    },
                    "northeast" : {
                        "lat" : 40.8495342,
                        "lng" : -73.7498543
                    }
                },
                "bounds" : {
                    "southwest" : {
                        "lat" : 40.4773990,
                        "lng" : -74.2590900
                    },
                    "northeast" : {
                        "lat" : 40.9175770,
                        "lng" : -73.7002720
                    }
                }
            }
        }
    ]
}

Solution 9 - C#

There are already a bunch of great answers here that use Newtonsoft.JSON, but here's one more that uses JObject.Parse in combination with ToString(), since that hasn't been mentioned yet:

var jObj = Newtonsoft.Json.Linq.JObject.Parse(json);
var formatted = jObj.ToString(Newtonsoft.Json.Formatting.Indented);

Solution 10 - C#

Just use JsonDocument and Utf8JsonWriter. No third-party library required. No target object for deserialization for jsonString required.

using System.IO;
using System.Text;
using System.Text.Json;

// other code ...

public string Prettify(string jsonString)
{
    using var stream = new MemoryStream();
    var document = JsonDocument.Parse(jsonString);
    var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
    document.WriteTo(writer);
    writer.Flush();
    return Encoding.UTF8.GetString(stream.ToArray());
}

Solution 11 - C#

As benjymous pointed out, you can use Newtonsoft.Json with a temporary object and deserialize/serialize.

var obj = JsonConvert.DeserializeObject(jsonString); 
var formatted = JsonConvert.SerializeObject(obj, Formatting.Indented);

Solution 12 - C#

The main reason of writing your own function is that JSON frameworks usually perform parsing of strings into .net types and converting them back to string, which may result in losing original strings. For example 0.0002 becomes 2E-4

I do not post my function (it's pretty same as other here) but here are the test cases

using System.IO;

using Newtonsoft.Json;

using NUnit.Framework;

namespace json_formatter.tests
{
	[TestFixture]
	internal class FormatterTests
	{
		[Test]
		public void CompareWithNewtonsofJson()
		{
			string file = Path.Combine(TestContext.CurrentContext.TestDirectory, "json", "minified.txt");

			string json = File.ReadAllText(file);

			string newton = JsonPrettify(json);
            // Double space are indent symbols which newtonsoft framework uses
			string my = new Formatter("  ").Format(json);

			Assert.AreEqual(newton, my);
		}

		[Test]
		public void EmptyArrayMustNotBeFormatted()
		{
			var input = "{\"na{me\": []}";
			var expected = "{\r\n\t\"na{me\": []\r\n}";

			Assert.AreEqual(expected, new Formatter().Format(input));
		}

		[Test]
		public void EmptyObjectMustNotBeFormatted()
		{
			var input = "{\"na{me\": {}}";
			var expected = "{\r\n\t\"na{me\": {}\r\n}";

			Assert.AreEqual(expected, new Formatter().Format(input));
		}

		[Test]
		public void MustAddLinebreakAfterBraces()
		{
			var input = "{\"name\": \"value\"}";
			var expected = "{\r\n\t\"name\": \"value\"\r\n}";

			Assert.AreEqual(expected, new Formatter().Format(input));
		}

		[Test]
		public void MustFormatNestedObject()
		{
			var input = "{\"na{me\":\"val}ue\", \"name1\": {\"name2\":\"value\"}}";
			var expected = "{\r\n\t\"na{me\": \"val}ue\",\r\n\t\"name1\": {\r\n\t\t\"name2\": \"value\"\r\n\t}\r\n}";

			Assert.AreEqual(expected, new Formatter().Format(input));
		}

		[Test]
		public void MustHandleArray()
		{
			var input = "{\"name\": \"value\", \"name2\":[\"a\", \"b\", \"c\"]}";
			var expected = "{\r\n\t\"name\": \"value\",\r\n\t\"name2\": [\r\n\t\t\"a\",\r\n\t\t\"b\",\r\n\t\t\"c\"\r\n\t]\r\n}";
			Assert.AreEqual(expected, new Formatter().Format(input));
		}

		[Test]
		public void MustHandleArrayOfObject()
		{
			var input = "{\"name\": \"value\", \"name2\":[{\"na{me\":\"val}ue\"}, {\"nam\\\"e2\":\"val\\\\\\\"ue\"}]}";
			var expected =
				"{\r\n\t\"name\": \"value\",\r\n\t\"name2\": [\r\n\t\t{\r\n\t\t\t\"na{me\": \"val}ue\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"nam\\\"e2\": \"val\\\\\\\"ue\"\r\n\t\t}\r\n\t]\r\n}";
			Assert.AreEqual(expected, new Formatter().Format(input));
		}

		[Test]
		public void MustHandleEscapedString()
		{
			var input = "{\"na{me\":\"val}ue\", \"name1\": {\"nam\\\"e2\":\"val\\\\\\\"ue\"}}";
			var expected = "{\r\n\t\"na{me\": \"val}ue\",\r\n\t\"name1\": {\r\n\t\t\"nam\\\"e2\": \"val\\\\\\\"ue\"\r\n\t}\r\n}";
			Assert.AreEqual(expected, new Formatter().Format(input));
		}

		[Test]
		public void MustIgnoreEscapedQuotesInsideString()
		{
            var input = "{\"na{me\\\"\": \"val}ue\"}";
		    var expected = "{\r\n\t\"na{me\\\"\": \"val}ue\"\r\n}";

			Assert.AreEqual(expected, new Formatter().Format(input));
		}

		[TestCase(" ")]
		[TestCase("\"")]
		[TestCase("{")]
		[TestCase("}")]
		[TestCase("[")]
		[TestCase("]")]
		[TestCase(":")]
		[TestCase(",")]
		public void MustIgnoreSpecialSymbolsInsideString(string symbol)
		{
			string input = "{\"na" + symbol + "me\": \"val" + symbol + "ue\"}";
			string expected = "{\r\n\t\"na" + symbol + "me\": \"val" + symbol + "ue\"\r\n}";

			Assert.AreEqual(expected, new Formatter().Format(input));
		}

		[Test]
		public void StringEndsWithEscapedBackslash()
		{
			var input = "{\"na{me\\\\\": \"val}ue\"}";
			var expected = "{\r\n\t\"na{me\\\\\": \"val}ue\"\r\n}";

			Assert.AreEqual(expected, new Formatter().Format(input));
		}

		private static string PrettifyUsingNewtosoft(string json)
		{
			using (var stringReader = new StringReader(json))
			using (var stringWriter = new StringWriter())
			{
				var jsonReader = new JsonTextReader(stringReader);
				var jsonWriter = new JsonTextWriter(stringWriter)
								{
									Formatting = Formatting.Indented
								};
				jsonWriter.WriteToken(jsonReader);
				return stringWriter.ToString();
			}
		}
	}
}

Solution 13 - C#

Fixed it... somewhat.

public class JsonFormatter
{
    #region class members
    const string Space = " ";
    const int DefaultIndent = 0;
    const string Indent = Space + Space + Space + Space;
    static readonly string NewLine = Environment.NewLine;
    #endregion

    private enum JsonContextType
    {
        Object, Array
    }

    static void BuildIndents(int indents, StringBuilder output)
    {
        indents += DefaultIndent;
        for (; indents > 0; indents--)
            output.Append(Indent);
    }
   

    bool inDoubleString = false;
    bool inSingleString = false;
    bool inVariableAssignment = false;
    char prevChar = '\0';

    Stack<JsonContextType> context = new Stack<JsonContextType>();

    bool InString()
    {
        return inDoubleString || inSingleString;
    }

    public string PrettyPrint(string input)
    {
        var output = new StringBuilder(input.Length * 2);
        char c;

        for (int i = 0; i < input.Length; i++)
        {
            c = input[i];

            switch (c)
            {
                case '{':
                    if (!InString())
                    {
                        if (inVariableAssignment || (context.Count > 0 && context.Peek() != JsonContextType.Array))
                        {
                            output.Append(NewLine);
                            BuildIndents(context.Count, output);
                        }
                        output.Append(c);
                        context.Push(JsonContextType.Object);
                        output.Append(NewLine);
                        BuildIndents(context.Count, output);
                    }
                    else
                        output.Append(c);

                    break;

                case '}':
                    if (!InString())
                    {
                        output.Append(NewLine);
                        context.Pop();
                        BuildIndents(context.Count, output);
                        output.Append(c);
                    }
                    else
                        output.Append(c);

                    break;

                case '[':
                    output.Append(c);

                    if (!InString())
                        context.Push(JsonContextType.Array);

                    break;

                case ']':
                    if (!InString())
                    {
                        output.Append(c);
                        context.Pop();
                    }
                    else
                        output.Append(c);

                    break;

                case '=':
                    output.Append(c);
                    break;

                case ',':
                    output.Append(c);

                    if (!InString() && context.Peek() != JsonContextType.Array)
                    {
                        BuildIndents(context.Count, output);
                        output.Append(NewLine);
                        BuildIndents(context.Count, output);
                        inVariableAssignment = false;
                    }

                    break;

                case '\'':
                    if (!inDoubleString && prevChar != '\\')
                        inSingleString = !inSingleString;

                    output.Append(c);
                    break;

                case ':':
                    if (!InString())
                    {
                        inVariableAssignment = true;
                        output.Append(Space);
                        output.Append(c);
                        output.Append(Space);
                    }
                    else
                        output.Append(c);

                    break;

                case '"':
                    if (!inSingleString && prevChar != '\\')
                        inDoubleString = !inDoubleString;

                    output.Append(c);
                    break;
                case ' ':
                    if (InString())
                        output.Append(c);
                    break;

                default:
                    output.Append(c);
                    break;
            }
            prevChar = c;
        }

        return output.ToString();
    }
}

credit [dead link]

Solution 14 - C#

You need to skip \r and \n in PrettyPrint(). The output looks funny of there are some crlf's already present (or the source was already formatted).

Solution 15 - C#

This will put each item on a new line

VB.NET

mytext = responseFromServer.Replace("{", vbNewLine + "{")

C#

mytext = responseFromServer.Replace("{", Environment.NewLine + "{");

Solution 16 - C#

Using a simple Method Extension:

using Newtonsoft.Json;

namespace Example
{
    public static class StringExtensions
    {
        public static string BeautifyJson(this string str)
        {
            var obj = JsonConvert.DeserializeObject(str);
            string json = JsonConvert.SerializeObject(obj, Formatting.Indented);
            return json;
        }
    }
}

and using it like:

string jsonFormatted = json.BeautifyJson();

Solution 17 - C#

This is a variant of the accepted answer that I like to use. The commented parts result in what I consider a more readable format (you would need to comment out the adjacent code to see the difference):

public class JsonHelper
{
    private const int INDENT_SIZE = 4;

    public static string FormatJson(string str)
    {
        str = (str ?? "").Replace("{}", @"\{\}").Replace("[]", @"\[\]");

        var inserts = new List<int[]>();
        bool quoted = false, escape = false;
        int depth = 0/*-1*/;

        for (int i = 0, N = str.Length; i < N; i++)
        {
            var chr = str[i];

            if (!escape && !quoted)
                switch (chr)
                {
                    case '{':
                    case '[':
                        inserts.Add(new[] { i, +1, 0, INDENT_SIZE * ++depth });
                        //int n = (i == 0 || "{[,".Contains(str[i - 1])) ? 0 : -1;
                        //inserts.Add(new[] { i, n, INDENT_SIZE * ++depth * -n, INDENT_SIZE - 1 });
                        break;
                    case ',':
                        inserts.Add(new[] { i, +1, 0, INDENT_SIZE * depth });
                        //inserts.Add(new[] { i, -1, INDENT_SIZE * depth, INDENT_SIZE - 1 });
                        break;
                    case '}':
                    case ']':
                        inserts.Add(new[] { i, -1, INDENT_SIZE * --depth, 0 });
                        //inserts.Add(new[] { i, -1, INDENT_SIZE * depth--, 0 });
                        break;
                    case ':':
                        inserts.Add(new[] { i, 0, 1, 1 });
                        break;
                }

            quoted = (chr == '"') ? !quoted : quoted;
            escape = (chr == '\\') ? !escape : false;
        }

        if (inserts.Count > 0)
        {
            var sb = new System.Text.StringBuilder(str.Length * 2);

            int lastIndex = 0;
            foreach (var insert in inserts)
            {
                int index = insert[0], before = insert[2], after = insert[3];
                bool nlBefore = (insert[1] == -1), nlAfter = (insert[1] == +1);

                sb.Append(str.Substring(lastIndex, index - lastIndex));

                if (nlBefore) sb.AppendLine();
                if (before > 0) sb.Append(new String(' ', before));

                sb.Append(str[index]);

                if (nlAfter) sb.AppendLine();
                if (after > 0) sb.Append(new String(' ', after));

                lastIndex = index + 1;
            }

            str = sb.ToString();
        }

        return str.Replace(@"\{\}", "{}").Replace(@"\[\]", "[]");
    }
}

Solution 18 - C#

Example

    public static string JsonFormatter(string json)
    {
        StringBuilder builder = new StringBuilder();
        
        bool quotes = false;

        bool ignore = false;

        int offset = 0;

        int position = 0;

        if (string.IsNullOrEmpty(json))
        {
            return string.Empty;
        }

        json = json.Replace(Environment.NewLine, "").Replace("\t", "");

        foreach (char character in json)
        {
            switch (character)
            {
                case '"':
                    if (!ignore)
                    {
                        quotes = !quotes;
                    }
                    break;
                case '\'':
                    if (quotes)
                    {
                        ignore = !ignore;
                    }
                    break;
            }

            if (quotes)
            {
                builder.Append(character);
            }
            else
            {
                switch (character)
                {
                    case '{':
                    case '[':
                        builder.Append(character);
                        builder.Append(Environment.NewLine);
                        builder.Append(new string(' ', ++offset * 4));
                        break;
                    case '}':
                    case ']':
                        builder.Append(Environment.NewLine);
                        builder.Append(new string(' ', --offset * 4));
                        builder.Append(character);
                        break;
                    case ',':
                        builder.Append(character);
                        builder.Append(Environment.NewLine);
                        builder.Append(new string(' ', offset * 4));
                        break;
                    case ':':
                        builder.Append(character);
                        builder.Append(' ');
                        break;
                    default:
                        if (character != ' ')
                        {
                            builder.Append(character);
                        }
                        break;
                }

                position++;
            }
        }

        return builder.ToString().Trim();
    }

Solution 19 - C#

This version produces JSON that is more compact and in my opinion more readable since you can see more at one time. It does this by formatting the deepest layer inline or like a compact array structure.

The code has no dependencies but is more complex.

{ 
  "name":"Seller", 
  "schema":"dbo",
  "CaptionFields":["Caption","Id"],
  "fields":[ 
    {"name":"Id","type":"Integer","length":"10","autoincrement":true,"nullable":false}, 
    {"name":"FirstName","type":"Text","length":"50","autoincrement":false,"nullable":false}, 
    {"name":"LastName","type":"Text","length":"50","autoincrement":false,"nullable":false}, 
    {"name":"LotName","type":"Text","length":"50","autoincrement":false,"nullable":true}, 
    {"name":"LotDetailsURL","type":"Text","length":"255","autoincrement":false,"nullable":true} 
  ]
}

The code follows

private class IndentJsonInfo
{
    public IndentJsonInfo(string prefix, char openingTag)
    {
        Prefix = prefix;
        OpeningTag = openingTag;
        Data = new List<string>();
    }
    public string Prefix;
    public char OpeningTag;
    public bool isOutputStarted;
    public List<string> Data;
}
internal static string IndentJSON(string jsonString, int startIndent = 0, int indentSpaces = 2)
{
    if (String.IsNullOrEmpty(jsonString))
        return jsonString;

    try
    {
        var jsonCache = new List<IndentJsonInfo>();
        IndentJsonInfo currentItem = null;

        var sbResult = new StringBuilder();

        int curIndex = 0;
        bool inQuotedText = false;

        var chunk = new StringBuilder();

        var saveChunk = new Action(() =>
        {
            if (chunk.Length == 0)
                return;
            if (currentItem == null)
                throw new Exception("Invalid JSON: No container.");
            currentItem.Data.Add(chunk.ToString());
            chunk = new StringBuilder();
        });

        while (curIndex < jsonString.Length)
        {
            var cChar = jsonString[curIndex];
            if (inQuotedText)
            {
                // Get the rest of quoted text.
                chunk.Append(cChar);

                // Determine if the quote is escaped.
                bool isEscaped = false;
                var excapeIndex = curIndex;
                while (excapeIndex > 0 && jsonString[--excapeIndex] == '\\') isEscaped = !isEscaped;

                if (cChar == '"' && !isEscaped)
                    inQuotedText = false;
            }
            else if (Char.IsWhiteSpace(cChar))
            {
                // Ignore all whitespace outside of quotes.
            }
            else
            {
                // Outside of Quotes.
                switch (cChar)
                {
                    case '"':
                        chunk.Append(cChar);
                        inQuotedText = true;
                        break;
                    case ',':
                        chunk.Append(cChar);
                        saveChunk();
                        break;
                    case '{':
                    case '[':
                        currentItem = new IndentJsonInfo(chunk.ToString(), cChar);
                        jsonCache.Add(currentItem);
                        chunk = new StringBuilder();
                        break;
                    case '}':
                    case ']':
                        saveChunk();
                        for (int i = 0; i < jsonCache.Count; i++)
                        {
                            var item = jsonCache[i];
                            var isLast = i == jsonCache.Count - 1;
                            if (!isLast)
                            {
                                if (!item.isOutputStarted)
                                {
                                    sbResult.AppendLine(
                                        "".PadLeft((startIndent + i) * indentSpaces) +
                                        item.Prefix + item.OpeningTag);
                                    item.isOutputStarted = true;
                                }
                                var newIndentString = "".PadLeft((startIndent + i + 1) * indentSpaces);
                                foreach (var listItem in item.Data)
                                {
                                    sbResult.AppendLine(newIndentString + listItem);
                                }
                                item.Data = new List<string>();
                            }
                            else // If Last
                            {
                                if (!(
                                    (item.OpeningTag == '{' && cChar == '}') ||
                                    (item.OpeningTag == '[' && cChar == ']')
                                   ))
                                {
                                    throw new Exception("Invalid JSON: Container Mismatch, Open '" + item.OpeningTag + "', Close '" + cChar + "'.");
                                }

                                string closing = null;
                                if (item.isOutputStarted)
                                {
                                    var newIndentString = "".PadLeft((startIndent + i + 1) * indentSpaces);
                                    foreach (var listItem in item.Data)
                                    {
                                        sbResult.AppendLine(newIndentString + listItem);
                                    }
                                    closing = cChar.ToString();
                                }
                                else
                                {
                                    closing =
                                        item.Prefix + item.OpeningTag +
                                        String.Join("", currentItem.Data.ToArray()) +
                                        cChar;
                                }

                                jsonCache.RemoveAt(i);
                                currentItem = (jsonCache.Count > 0) ? jsonCache[jsonCache.Count - 1] : null;
                                chunk.Append(closing);
                            }
                        }
                        break;
                    default:
                        chunk.Append(cChar);
                        break;
                }
            }
            curIndex++;
        }

        if (inQuotedText)
            throw new Exception("Invalid JSON: Incomplete Quote");
        else if (jsonCache.Count != 0)
            throw new Exception("Invalid JSON: Incomplete Structure");
        else
        {
            if (chunk.Length > 0)
                sbResult.AppendLine("".PadLeft(startIndent * indentSpaces) + chunk);
            var result = sbResult.ToString();
            return result;
        }
    }
    catch (Exception ex)
    {
        throw;  // Comment out to return unformatted text if the format failed.
        // Invalid JSON, skip the formatting.
        return jsonString;
    }
}

The function allows you to specify a starting point for the indentation because I use this as part of a process that assembles very large JSON formatted backup files.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmpenView Question on Stackoverflow
Solution 1 - C#Frank TzanabetisView Answer on Stackoverflow
Solution 2 - C#Peter LongView Answer on Stackoverflow
Solution 3 - C#dvdmnView Answer on Stackoverflow
Solution 4 - C#Gurdeep Singh SidhuView Answer on Stackoverflow
Solution 5 - C#RazorView Answer on Stackoverflow
Solution 6 - C#yallieView Answer on Stackoverflow
Solution 7 - C#epoxView Answer on Stackoverflow
Solution 8 - C#mpenView Answer on Stackoverflow
Solution 9 - C#Nate CookView Answer on Stackoverflow
Solution 10 - C#Roger.KView Answer on Stackoverflow
Solution 11 - C#Twisted Zipper LabsView Answer on Stackoverflow
Solution 12 - C#Max VenediktovView Answer on Stackoverflow
Solution 13 - C#mpenView Answer on Stackoverflow
Solution 14 - C#wvd_vegtView Answer on Stackoverflow
Solution 15 - C#AndrewView Answer on Stackoverflow
Solution 16 - C#JanView Answer on Stackoverflow
Solution 17 - C#J Bryan PriceView Answer on Stackoverflow
Solution 18 - C#Сергей АфоничевView Answer on Stackoverflow
Solution 19 - C#AnthonyVOView Answer on Stackoverflow