Advanced JSON serialization formatting options - c#

I have some data in form of objects, inside a list object. Now I want to serialize this data to JSON. For this I'm (currently) using JSON.NET. My problem is that with
JsonConvert.SerializeObject(list, ...)
I seem to only have the option to either indent the whole thing, aka every key/value, or not indent at all.
{"Variable1":1,"Variable2":"2"},{"Variable1":1,"Variable2":"2"},...
or
{
"Variable1": 1,
"Variable2": "2"
},
{
"Variable1": 1,
"Variable2": "2"
},
...
I'd like to get this:
{ "Variable1": 1, "Variable2": "2" },
{ "Variable1": 1, "Variable2": "2" },
but without having to explicitly writing every key/value myself (JsonTextWriter or manually). I just want to pass the list and get the above. Is this somehow possible? At the moment I'm serializing every object separate, by going through the list, and running some replaces, regex replaces, and similar, to get the desired output, depending on the input list. Is there an easier way to do this, without writing your own serialization method?

hello i found something that my help you to start your special formatter
i translated it from javascript to c# from here: https://github.com/umbrae/jsonlintdotcom/blob/master/c/js/jsl.format.js
it add whitespaces to json string without them.
i think you can modify this formatter to your own needs.
private static string PrettyPrinter_repeat(string s, int count) {
string ns = "";
for(int i = 0; i < count; i++) ns += s;
return ns;
}
public static string PrettyPrinter(string json)
{
int i = 0,
il = 0;
string tab = " ",
newJson = "";
int indentLevel = 0;
bool inString = false;
char currentChar = default(char);
for (i = 0, il = json.Length; i < il; i += 1)
{
currentChar = json[i];
switch (currentChar) {
case '{':
case '[':
if (!inString) {
newJson += currentChar + "\n" + PrettyPrinter_repeat(tab, indentLevel + 1);
indentLevel += 1;
} else {
newJson += currentChar;
}
break;
case '}':
case ']':
if (!inString) {
indentLevel -= 1;
newJson += "\n" + PrettyPrinter_repeat(tab, indentLevel) + currentChar;
} else {
newJson += currentChar;
}
break;
case ',':
if (!inString) {
newJson += ",\n" + PrettyPrinter_repeat(tab, indentLevel);
} else {
newJson += currentChar;
}
break;
case ':':
if (!inString) {
newJson += ": ";
} else {
newJson += currentChar;
}
break;
case ' ':
case '\n':
case '\t':
if (inString) {
newJson += currentChar;
}
break;
case '"':
if (i > 0 && json[i - 1] != '\\') {
inString = !inString;
}
newJson += currentChar;
break;
default:
newJson += currentChar;
break;
}
}
return newJson;
}

Related

Trying to detect which html tags are affecting an element in a string

I am trying to write a markup editor in c#, part of is is to detect which tags are affecting where the caret currently is.
Examples:
<b>Tes|ting</b><u><i>Testing</i>Testing</u> (caret '|' at index 6)
Answer: [b]
<b>Testing<u><i>Test|ing</i>Tes</b>ting</u> (caret '|' at index 20)
Answer: [b, u, i]
Here is the code I have currently, inside "GetTags()" I split the string into 2 queues representing what tags are in front and behind where the caret is. I played around with ways to pop the elements from both queues to try to solve the problem but I keep getting wrong results. I think im on the right track creating 2 data structures but I don't know if Queue<> is what I need.
public class Tag
{
public enum TagType
{
bold,
italic,
underline,
}
public TagType type;
public bool opening;
public Tag(TagType type, bool opening)
{
this.type = type;
this.opening = opening;
}
public static Tag GetTagFromString(string str)
{
switch (str)
{
case "<b>": return new Tag(TagType.bold, true);
case "</b>": return new Tag(TagType.bold, false);
case "<i>": return new Tag(TagType.italic, true);
case "</i>": return new Tag(TagType.italic, false);
case "<u>": return new Tag(TagType.underline, true);
case "</u>": return new Tag(TagType.underline, false);
}
return null;
}
public static List<TagType> GetTags(string str, int index)
{
Queue<Tag> backQueue = new Queue<Tag>();
Queue<Tag> forwardQueue = new Queue<Tag>();
// populate the back queue
int i = index;
while (true)
{
int lastOpening = str.LastIndexOf('<', i);
int lastClosing = str.LastIndexOf('>', i);
if (lastOpening != -1 && lastClosing != -1)
{
string tagStr = str.Substring(lastOpening, lastClosing - lastOpening + 1);
Tag tag = GetTagFromString(tagStr);
backQueue.Enqueue(tag);
i = lastOpening - 1;
if (i < 0)
break;
}
else
break;
}
// populate the front stack
i = index;
while (true)
{
int nextOpening = str.IndexOf('<', i);
int nextClosing = str.IndexOf('>', i);
if (nextOpening != -1 && nextClosing != -1)
{
string tagStr = str.Substring(nextOpening, nextClosing - nextOpening + 1);
Tag tag = GetTagFromString(tagStr);
forwardQueue.Enqueue(tag);
i = nextClosing + 1;
}
else
break;
}
List<TagType> tags = new List<TagType>();
// populate 'tags' list with the tags affecting the index here
return tags;
}
public override string ToString()
{
string str = "<";
if (!opening)
str += "/";
switch (type)
{
case TagType.bold:
str += "b";
break;
case TagType.italic:
str += "i";
break;
case TagType.underline:
str += "u";
break;
}
str += ">";
return str;
}
}
Would love any input on how I could solve this and would greatly appreciate any issues anyone has with the code that I've provided.

How to prevent text changing between special symbols

This code i used to text change as i need.
private void textBox1_TextChanged(object sender, EventArgs e)
{
string A = textBox1.Text.Trim();
A = A.Replace("A", "C");
A = A.Replace("F", "H");
A = A.Replace("C", "W");
A = A.Replace("B", "G");
textBox2.Text = (A);
}
Now i need to stop text changing after,
if i type '|' symbol in tetxbox1, Again i need to
start text changing if i type '|' symbol again,such as happened thing in this image.
So how can i prevent text changing between these two symbols only ||
You're replace code won't work how you have it, as it will just keep changing the characters for the same string(you change A to C, and later down you change C to W, so your final first character would be W and not C like you want).
Below is an overly complicated method(i also added a method that runs through each character of the string doing the replace) but it should work, and you can change as needed. Good luck
private void textBox1_TextChanged(object sender, EventArgs e)
{
string A = textBox1.Text.Trim();
string[] Aarry = A.Split('|');
string cleanedString = "";
for (int i = 0; i < Aarry.Length; i++)
{
if (i % 2 == 0)
cleanedString += FixText(Aarry[i]) + " ";
else
cleanedString += Aarry[i] + " ";
}
textBox2.Text = cleanedString ;
The method below will go through each character doing the replace
public string FixText(string A)
{
string newText = "";
for (int i = 0; i < A.Length; i++)
{
switch (A.Substring(i, 1))
{
case "A":
newText += A.Substring(i, 1).Replace("A", "C");
break;
case "F":
newText += A.Substring(i, 1).Replace("F", "H");
break;
case "C":
newText += A.Substring(i, 1).Replace("C", "W");
break;
case "B":
newText += A.Substring(i, 1).Replace("B", "G");
break;
default:
break;
}
}
return newText;
}
To handle the >500 lines of replacement type you have, you could setup a dictionary using method below:
public Dictionary<string, string> ReturnReplacementDictionary()
{
Dictionary<string, string> dictLibrary = new Dictionary<string,string)()
{
{"A","C"},
{"F","H"},
{"C","W"},
{"B","G"}
};
return dictLibrary;
}
In the above you would just continue adding in all your other replacement values.
Then you would call use that method below instead of the switch case(If you don't add a character/replacement to the dictionary method you can see it will just set the replacement character to blank):
public string FixTextUsingDictionary(string A)
{
Dictionary<string, string> replaceDict = ReturnReplacementDictionary();
string newText = "";
for (int i = 0; i < A.Length; i++)
{
string replacementLetter="";
if (replaceDict.TryGetValue(A.Substring(i, 1), out replacementLetter))
{
newText += replacementLetter;
}
// Added so that if the char is not in the dictionary the output will just have the original char
else { newText += A.Substring(i, 1); }
}
return newText;
}
Good luck
If the text is entered manually not pasted from clipboard, my solution will be:
int counter = 0;
private string replaceSpecial(string A)
{
if (A.Equals("A")) return "C";
else if (A.Equals("F")) return "H";
else if (A.Equals("C")) return "W";
else if (A.Equals("B")) return "G";
else if (A.Equals("|")) return "";
else return A;
}
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar.Equals('|'))
{
counter++;
}
if (counter == 0 || counter % 2 == 0)
textBox2.Text += replaceSpecial(e.KeyChar.ToString());
else
textBox2.Text += e.KeyChar.ToString().Replace("|", "");
}
considering that the only entered character is "|".
good luck
void StringReplace(string initialString)
{
bool insideSpecialCharacter = false;
string[] Pattern = { "A-C", "C-W", "F-H", "B-G" };
string specialCharacter = "|";
char[] characters = initialString.ToCharArray();
char?[] Newcharacters = new char?[characters.Length];
for (int i = 0; i < characters.Length; i++)
{
if (!characters[i].ToString().Equals(specialCharacter))
{
if (insideSpecialCharacter)
{
Newcharacters[i] = characters[i];
}
else
{
CheckPattern(Pattern, characters, Newcharacters, i);
}
}
else
{
insideSpecialCharacter = (insideSpecialCharacter) ? false : true;
}
}
txtSecond.Text = string.Concat(Newcharacters).Trim();
}
//-------Checks the Pattern Array and Replaces the Characters-----------
private static void CheckPattern(string[] Pattern, char[] characters, char?[] Newcharacters, int i)
{
for (int j = 0; j < Pattern.Length; j++)
{
string[] replaceValue = Pattern[j].Split('-');
if (characters[i].ToString() == replaceValue[0])
{
Newcharacters[i] = Convert.ToChar(characters[i].ToString().Replace(characters[i].ToString(), replaceValue[1]));
break;
}
else
{
Newcharacters[i] = characters[i];
}
}
}

i want make math operation with one button C#

i want make math operation with one button and textbox , when type on textbox operation like 25+5+-10+7 and press on button show me result
i am trying do this but i can not.
in this code i make for each char in textbox and i want store 25 in firstvalue parameter and + in op parameter and 5 in secondvalue parameter
and make operation 25+5=30 and store it in firstvalue parameter and store +- in op , 10 in secondvalue and make operation ,store it in firstvalue ....and so on until length of string .
but i do not know where and how store second value
private void button1_Click(object sender, EventArgs e)
{
string allValue = textBox1.Text;
int firstValue=0;
int secondValue=0;
string first = string.Empty;
string op = string.Empty;
foreach (char item in allValue)
{
if (char.IsNumber(item))
{
first += item;
}
else if(item=='+' || item=='-' || item=='*' || item=='/')
{
firstValue = Int32.Parse(first);
op += item;
first = "";
switch (op)
{
case "+":
firstValue = firstValue + secondValue;
break;
case "-":
firstValue = firstValue - secondValue;
break;
case "+-":
firstValue = firstValue +- secondValue;
break;
case "*":
firstValue = firstValue * secondValue;
break;
case "/":
firstValue = firstValue / secondValue;
break;
}
}
}
MessageBox.Show(firstValue.ToString());
}
If you want to parse the string from beginning to end and do the calculations during the parsing, then you can't do the calculation between two values until you have both operands. That means that you have to wait until you find the second operator (or the end of the string) until you can do the calculation for the first operator.
When you parse the number, put that in the secondValue variable. If it's the first value then just move it to firstValue and continue to get another value, otherwise do the calculation with the two values that you have according to the previous operator.
To include the last calculation you can loop one step more than the last character in the string. That allows your loop to run one more time after the last value, and you can do that last calculation. It requires a few more checks though, so that you don't actually try to read from that position outside the string.
To handle negative values you should include that in the value, not using a +- operator. Otherwise you would also need --, *- and /- operators.
Example (not tested):
private void button1_Click(object sender, EventArgs e) {
string allValue = textBox1.Text;
int firstValue = 0;
int secondValue = 0;
string second = string.Empty;
string op = string.Empty;
for (int i = 0; i <= allValue.Length; i++) {
if (i < allValue.Length && (Char.IsNumber(allValue[i]) || (char == '-' && second.Length == 0))) {
second += allValue[i];
} else if (i == allValue.Length || "+-*/".indexOf(allValue[i]) != -1) {
secondValue = Int32.Parse(second);
if (op.Length > 0) {
switch (op) {
case "+": firstValue += secondValue; break;
case "-": firstValue -= secondValue; break;
case "*": firstValue *= secondValue; break;
case "/": firstValue /= secondValue; break;
}
} else {
firstValue = secondValue;
}
if (i < allValue.Length) {
op = allValue[i].ToString();
}
second = "";
} else {
// illegal formula
}
}
MessageBox.Show(firstValue.ToString());
}
Note: This calculates strictly from left to right and does not handle priority, e.g. 1+2*3 is evaluated as (1+2)*3, not the 1+(2*3) that is normally used.
You can use the NCalc library. You would use it like this:
NCalc.Expression e = new NCalc.Expression(textBox1.Text);
double value = e.Evaluate();
// Next, do something with your evaluated value.
Would this work for you? As mentioned in earlier answers this does not pay regard to the order of operations and there is a lot of other error checking that should likely be done.
using System.Text.RegularExpressions;
private void button1_Click(object sender, EventArgs e)
{
MatchCollection values = Regex.Matches(textBox1.Text, "[0-9]+");
MatchCollection operations = Regex.Matches(textBox1.Text, #"[\/\*\+-]");
if (values.Count == operations.Count + 1)
{
int total = int.Parse(values[0].Value);
if (operations.Count > 0)
{
for (int index = 1; index < values.Count; index++)
{
int value = int.Parse(values[index].Value);
switch (operations[index - 1].Value)
{
case "+":
total += value;
break;
case "-":
total -= value;
break;
case "/":
total /= value;
break;
case "*":
total *= value;
break;
}
}
}
MessageBox.Show(total.ToString());
}
}
Doing all the things from scratch is not easy. In my simplified answer I'll presume your operators are only +-*/ (including their precedence).
Reference: How to evaluate an infix expression in just one scan using stacks?
First you should parse the input string... My way would be
public enum TokenType
{
Operator,
Operand
}
public class Token
{
public string Value { get; set; }
public TokenType Type { get; set; }
public override string ToString()
{
return Type + " " + Value;
}
}
IEnumerable<Token> Parse(string input)
{
return Regex.Matches(input, #"(?<value>\d+)|(?<type>[\+\-\*\/\(\)])").Cast<Match>()
.Select(m => m.Groups["value"].Success
? new Token() { Value = m.Groups["value"].Value, Type = TokenType.Operand }
: new Token() { Value = m.Groups["type"].Value, Type = TokenType.Operator });
}
This Parse method will return all the operators and operands in your expression.
The next step would be to evaluate the expression using a stack
Token Exec(Stack<Token> operatorStack, Stack<Token> operandStack)
{
var op = operatorStack.Pop();
var t1 = operandStack.Pop();
var t2 = operandStack.Pop();
switch (op.Value)
{
case "+": return new Token() { Value = (int.Parse(t1.Value) + int.Parse(t2.Value)).ToString(), Type = TokenType.Operand };
case "-": return new Token() { Value = (int.Parse(t2.Value) - int.Parse(t1.Value)).ToString(), Type = TokenType.Operand };
case "*": return new Token() { Value = (int.Parse(t1.Value) * int.Parse(t2.Value)).ToString(), Type = TokenType.Operand };
case "/": return new Token() { Value = (int.Parse(t2.Value) / int.Parse(t1.Value)).ToString(), Type = TokenType.Operand };
}
return null;
}
int Eval(string input)
{
var tokens = Parse(input);
var operatorStack = new Stack<Token>();
var operandStack = new Stack<Token>();
var precedence = new Dictionary<string, int>() { { "+", 0 }, { "-", 0 }, { "*", 1 }, { "/", 1 } };
foreach (var token in tokens)
{
if (token.Type == TokenType.Operand) operandStack.Push(token);
if (token.Type == TokenType.Operator)
{
while (operatorStack.Count > 0 && precedence[operatorStack.Peek().Value] >= precedence[token.Value])
{
operandStack.Push(Exec(operatorStack, operandStack));
}
operatorStack.Push(token);
}
}
while(operatorStack.Count>0)
{
operandStack.Push(Exec(operatorStack, operandStack));
}
return int.Parse(operandStack.Pop().Value);
}
You can test your code now.
int r1 = Eval("25 - 5");
int r2 = Eval("25 - 5*2");
int r3 = Eval("25 - 5 + 3*2");
int r4 = Eval("25/5 + 7*3");
int r5 = Eval("25/5 + 7*3 + 2");
int r6 = Eval("25/5 + 7*3 * 2");

A number is inserted, and I have to make a message box pop up with the word versions of the numbers

How do I fix this program? A number is inserted, and I have to make a message box pop up with the word versions of the numbers. For example, if "301" is typed in, the message box has to say "Three Zero One". Here's what I have:
string display = "";
for (int i = 0; i < txtNumber.Text.Length; i++)
{
if (txtNumber.Text[i] == 0)
{
display += "Zero ";
}
if (txtNumber.Text[i] == 1)
{
display += "One ";
}
if (txtNumber.Text[i] == 2)
{
display += "Two ";
}
if (txtNumber.Text[i] == 3)
{
display += "Three ";
}
if (txtNumber.Text[i] == 4)
{
display += "Four ";
}
if (txtNumber.Text[i] == 5)
{
display += "Five ";
}
if (txtNumber.Text[i] == 6)
{
display += "Six ";
}
if (txtNumber.Text[i] == 7)
{
display += "Seven ";
}
if (txtNumber.Text[i] == 8)
{
display += "Eight ";
}
if (txtNumber.Text[i] == 9)
{
display += "Nine ";
}
}
MessageBox.Show(display);
There are no errors showing up on an error list, but whenever I run it and type in a number and hit enter, the message box pops up with nothing in it. Anything will help. Thank you very much!
Text[i] is a character that is being converted automatically to int. And since for example '0' code is not 0 - nothing is shown. Change all your if to char-to-char comparison:
if (txtNumber.Text[i] == '0')
Also you might want to consider using switch construction.
var numToText = new Dictionary<int, string>() { {'0', "Zero "}, ...};
foreach (var char in txtMessage.Text)
{
display += numToText[char];
}
This is probly also a linq one-liner
The problem is that you are comparing characters with integers, which you cannot do.
However, you can do like this:
foreach (char c in txtNumber.Text)
{
switch (c)
{
case '0': display += "Zero ";
break;
case '1': display += "One ";
break;
case '2': display += "Two ";
break;
case '3': display += "Three ";
break;
case '4': display += "Four ";
break;
case '5': display += "Five ";
break;
case '6': display += "Six ";
break;
case '7': display += "Seven ";
break;
case '8': display += "Eight ";
break;
case '9': display += "Nine ";
break;
default: display += "NaN ";
break;
}
}
Here we loop through all the characters in the string and compares each character with a specified case in the switch statement and if the character is neither 0-9 it dispays "NaN" (Not a Number).
The .Text property is of type string. Indexing into an item of type string returns values of type char. Your code is performing an incorrect implied cast the way that it's written now. For example, casting a 0 char to an int yields 48 and casting 0 int to a char yields an ASCII NUL. Try either single-quoting your digits to make them character literals (i.e. '0') or parsing the indexed item (i.e., int.Parse(txtNumber.Text[i])) to avoid an implied cast with an unexpected result.
Should be as easy as
public static string DigitsToWords( string digits )
{
if ( !digits.All( char.IsDigit ) ) throw new ArgumentOutOfRangeException("digits");
return string.Join( " " , digits.Select( d => words[d-'0'] ) ) ;
}
private static readonly string [] words = { "zero" , "one" , "two" , "three" , "four" , "five" , "six" , "seven" , "eight" , "nine" , } ;
You are comparing char with int in your txtNumber.Text[i] == 0 line for example.
And since I'm an humble programmer, I wrote your example with based switch stament on LINQPad like;
void Main()
{
string s = Console.ReadLine();
string result = "";
foreach (var i in s)
{
switch (i)
{
case '1':
result += "One ";
break;
case '2':
result += "Two ";
break;
case '3':
result += "Three ";
break;
case '4':
result += "Four ";
break;
case '5':
result += "Five ";
break;
case '6':
result += "Six ";
break;
case '7':
result += "Seven ";
break;
case '8':
result += "Eight ";
break;
case '9':
result += "Nine ";
break;
case '0':
result += "Zero ";
break;
}
}
result = result.Trim();
result.Dump();
}
For 301 as an input, output will be Three Zero One

JSON formatter in C#?

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}}}}]}
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));
}
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);
}
}
}
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.
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);
}
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
}
}
}
}
]
}
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:
Escape sequences inside strings
Missing spaces after colon
Extra spaces after commas (or elsewhere)
Square and curly braces inside strings
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
}
}
}
}
]
}
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);
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;
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
}
}
}
}
]
}
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());
}
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);
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();
}
}
}
}
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).
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]
This will put each item on a new line
VB.NET
mytext = responseFromServer.Replace("{", vbNewLine + "{")
C#
mytext = responseFromServer.Replace("{", Environment.NewLine + "{");
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(#"\[\]", "[]");
}
}
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();
}
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.

Categories

Resources