Friday, January 13, 2012

C# JSon Pretty-Print (A simple C# JSon Presentation Formatter)

When working on a recent project, I ran across a need to have the user paste into a textbox (in a WPF application) a snippet of  unformatted  JSon data. The data was then used with a template to render content for them to view. The hard part... the unformatted JSon data was almost impossible to read (let alone do anything with).

A quick search around on Google, and I couldn't find what I thought was pretty basic function in C#. I mean, someone, somewhere else in the world needed to be able to do this. The Javascript serializer would work if I had an object to start with, but in my case, there wasn't an object just a string. So I wrote a basic formatter for presentation of JSon data. It's not all that impressive, but it works, and creates presentable data that the user can view and work with.

I also added a quick method as well that would convert an object to a string, and then apply the same formatting algorithm in-case you have a need to present both and want to have the same presentation.

So instead of this:



You get this:












The Code:
        public class JSonPresentationFormatter
        {
            public string Format(object obj)
            {
                var serializer = new JavaScriptSerializer();
                var text = serializer.Serialize(obj);

                return Format(text);
            }

            public string Format(string text)
            {
                if (string.IsNullOrEmpty(text)) return string.Empty;
                text = text.Replace(System.Environment.NewLine, string.Empty).Replace("\t", string.Empty);

                var offset = 0;
                var output = new StringBuilder();
                Action tabs = (sb, pos) => { for (var i = 0; i < pos; i++) { sb.Append("\t"); } };
                Func> previousNotEmpty = (s, i) =>
                {
                    if (string.IsNullOrEmpty(s) || i <= 0) return null;

                    Nullable prev = null;

                    while (i > 0 && prev == null)
                    {
                        prev = s[i - 1];
                        if (prev.ToString() == " ") prev = null;
                        i--;
                    }

                    return prev;
                };
                Func> nextNotEmpty = (s, i) =>
                {
                    if (string.IsNullOrEmpty(s) || i >= (s.Length - 1)) return null;

                    Nullable next = null;
                    i++;

                    while (i < (s.Length - 1) && next == null)
                    {
                        next = s[i++];
                        if (next.ToString() == " ") next = null;
                    }

                    return next;
                };

                for (var i = 0; i < text.Length; i++)
                {
                    var chr = text[i];

                    if (chr.ToString() == "{")
                    {
                        offset++;
                        output.Append(chr);
                        output.Append(System.Environment.NewLine);
                        tabs(output, offset);
                    }
                    else if (chr.ToString() == "}")
                    {
                        offset--;
                        output.Append(System.Environment.NewLine);
                        tabs(output, offset);
                        output.Append(chr);

                    }
                    else if (chr.ToString() == ",")
                    {
                        output.Append(chr);
                        output.Append(System.Environment.NewLine);
                        tabs(output, offset);
                    }
                    else if (chr.ToString() == "[")
                    {
                        output.Append(chr);

                        var next = nextNotEmpty(text, i);

                        if (next != null && next.ToString() != "]")
                        {
                            offset++;
                            output.Append(System.Environment.NewLine);
                            tabs(output, offset);
                        }
                    }
                    else if (chr.ToString() == "]")
                    {
                        var prev = previousNotEmpty(text, i);

                        if (prev != null && prev.ToString() != "[")
                        {
                            offset--;
                            output.Append(System.Environment.NewLine);
                            tabs(output, offset);
                        }

                        output.Append(chr);
                    }
                    else
                        output.Append(chr);
                }

                return output.ToString().Trim();
            }
        }
You can download a copy of the source here: JSonPresentationFormatter
Example:
            //From object
            var employees = new[]
            {
                 new {Name= "John Vansteen",ID="1", Age="35"}
                ,new {Name = "Joe Yankin", ID = "2", Age = "44" }
                ,new {Name = "Larry Yon", ID = "3", Age = "33" }
            };
            Console.Write(new JSonPresentationFormatter().Format(employees));

            //From string
            var jSonAsString = "{\"glossary\": {\"title\": \"example glossary\",\"GlossDiv\": {\"title\": \"S\",\"GlossList\": {\"GlossEntry\": {\"ID\": \"SGML\",\"SortAs\": \"SGML\",\"GlossTerm\": \"Standard Generalized Markup Language\",\"Acronym\": \"SGML\",\"Abbrev\": \"ISO 8879:1986\",\"GlossDef\": {\"para\": \"A meta-markup language, used to create markup languages such as DocBook.\",\"GlossSeeAlso\": [\"GML\", \"XML\"]},\"GlossSee\": \"markup\"}}}}}";
            Console.Write(new JSonPresentationFormatter().Format(jSonAsString));
Key Words:
JSon C# Presentation Formatter

References:
IFormatProvider Interface

Source:
JSonPresentationFormatter

9 comments:

fleurdelys said...

Hi, can you please check your code.
there are syntax errors.

Func> previousNotEmpty = (s, i) =>
{...};
does not exist

Unknown said...
This comment has been removed by the author.
Unknown said...

@fleurdelys - Thanks for the comment. Blogger is messing up the formatting. previousNotEmpty and nextNotEmpty are both anonymous functions, and are within the Format(string text) method.

It should be:
Func> previousNotEmpty
Func> nextNotEmpty

You can download the .cs file from here:
http://dl.dropbox.com/u/52219470/Source/JSonPresentationFormatter.cs

If I can help any more, please do not hesitate to ask.

Unknown said...

@fleurdelys. Sorry.. it messed up the syntax in the comment as well! Just take a look at the file download. It has what your looking for.

JSonPresentationFormatter

Fred Peters said...

Thanks for this routine, I've been looking for months. It works great for me.

Unknown said...

Thanks, works nicely. I just changed the class to a static one.

Corey Alix said...

Thanks for this! Here's a small enhancement to detect quoted elements:

if (chr == '"' && !ignoreQuote) inQuote = !inQuote;
if (chr == '\'' && inQuote) ignoreQuote = !ignoreQuote;
if (inQuote) {
output.Append(chr);
}
else if (chr.ToString() == "{")

Corey Alix said...

I put your code on a public gist with minor modification:
https://gist.github.com/ca0v/5de495eccdc2874fecbd#file-jsonpresentationformatter-cs

José Manuel Vega Monroy said...

I translated your code into Java due to I needed. I post, maybe could be useful for anyone more.

Thanks for your code.


import java.io.IOException;
import java.io.Writer;

public final class PrettyJsonSerializer {

private PrettyJsonSerializer() {

}

public static String serializePrettyHtml(String text, Writer writer)
throws IOException {

if (text == null || text.isEmpty()) {
return null;
}

text = text.replaceAll("\n", "").replaceAll("\t", "");

int offset = 0;
StringBuilder output = new StringBuilder();

for (int i = 0; i < text.length(); i++) {
char chr = text.charAt(i);

if (chr == '{') {
offset++;
output.append(chr);
output.append("\n");
addTabs(output, offset);
} else if (chr == '}') {
offset--;
output.append("\n");
addTabs(output, offset);
output.append(chr);

} else if (chr == ',') {
output.append(chr);
output.append("\n");
addTabs(output, offset);
} else if (chr == '[') {
output.append(chr);

Character next = getNextCharNotEmpty(text, i);

if (next != null && !next.toString().equals("]")) {
offset++;
output.append("\n");
addTabs(output, offset);
}
} else if (chr == ']') {
Character prev = getPreviousCharNotEmpty(text, i);

if (prev != null && !prev.toString().equals("[")) {
offset--;
output.append("\n");
addTabs(output, offset);
}

output.append(chr);
} else
output.append(chr);
}

return output.toString().trim();
}

/**
* Use this method to add tabs till given position
*
*/
private static void addTabs(StringBuilder sb, int pos) {
for (int i = 0; i < pos; i++) {
sb.append("\t");
}
}

/**
* Use this method to get previous character not empty
*
* @param s
* @param i
* @return
*/
private static Character getPreviousCharNotEmpty(String s, int i) {
if (s == null || s.isEmpty() || i <= 0)
return null;

Character prev = null;

while (i > 0 && prev == null) {
prev = s.charAt(i - 1);
if (prev == ' ')
prev = null;
i--;
}

return prev;
}

/**
* Use this method to get next character not empty
*
* @param s
* @param i
* @return
*/
private static Character getNextCharNotEmpty(String s, int i) {
if (s == null || s.isEmpty() || i >= (s.length() - 1))
return null;

Character next = null;
i++;

while (i < (s.length() - 1) && next == null) {
next = s.charAt(i++);
if (next == ' ')
next = null;
}

return next;
};

}