C # 中的 JSON 格式化程序?

寻找一个将以 Json 的 string作为输入并使用换行符和缩进格式化它的函数。验证是一个额外的好处,但不是必需的,我也不需要将它解析成对象或其他任何东西。

有人知道这个图书馆吗?


输入样本:

{"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}}}}]}
193778 次浏览

修好了... 一点点。

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 ]

还有一个更简单的,我刚写的:

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();
}
}

必要的延期:

    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);
}

输出样本:

{
"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
}
}
}
}
]
}

我更新了旧版本,现在它应该支持非引号值,如整数和布尔值。

我重构了前一个版本,得到了最终版本: 代码更短更清晰。只需要一个扩展方法。最重要的是: 修复一些错误。

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);
}
}
}

你需要跳过 PrettyPrint()中的 \r\n。输出看起来很有趣,有一些 crlf 的已经存在(或源已经格式化)。

您还可以使用 牛顿软件,杰森库进行此操作,并使用 Formatting.Indented enum-调用 SerializeObject

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

文件: 串行化一个对象


更新 -

再试一次。我很确定这曾经是有用的-也许它在后来的版本中改变了,或者也许我只是在想象。无论如何,根据下面的评论,它并不像预期的那样工作。但是,这些可以(只是在 linqpad 中测试)。第一个是从评论,第二个是一个例子,我发现在其他地方的 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));
}

这是我喜欢使用的公认答案的一个变体。注释部分的结果是我认为更具可读性的格式(您需要注释掉相邻的代码才能看出区别) :

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(@"\[\]", "[]");
}
}

Json.net 库的简短示例。

using Newtonsoft.Json;


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

PS: 您可以用标记包装格式化的 json 文本,以便像在 html 页面上那样打印它。

下面是一个简洁版的 JSON 美化器。

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);
}

产出:

 {
"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
}
}
}
}
]
}

例子

    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();
}

正如 Benjymous指出的,可以将 牛顿软件,杰森与临时对象一起使用,并进行反序列化/序列化。

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

这将把每个项目放在一个新的行

VB.NET

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

C #

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

编写自己的函数的主要原因是 JSON 框架通常将字符串解析为。并将它们转换回字符串,这可能导致丢失原始字符串。例如0.0002变成2E-4

我没有发布我的函数(它和这里的其他函数非常相似) ,但是这里有一些测试用例

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();
}
}
}
}

所有的荣誉都归于 Frank Tzanabetis。然而,这是最短的直接示例,它也适用于空字符串或中断的原始 JSON 字符串:

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


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

这里已经有很多使用 牛顿软件 JSON的好答案,但这里还有一个使用 JObject.ParseToString()结合使用的答案,因为还没有提到:

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

这个版本生成的 JSON 更加紧凑,在我看来更具可读性,因为您可以一次看到更多内容。它通过内联格式化最深层或类似于紧凑数组结构来实现这一点。

代码没有依赖项,但是更复杂。

{
"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}
]
}

代码如下

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;
}
}

该函数允许您指定缩进的起始点,因为我使用它作为组装非常大的 JSON 格式备份文件的过程的一部分。

我非常印象深刻的紧凑型 JSON 格式化程序Vince Panuccio
下面是我现在使用的一个改进版本:

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);
}

它解决了以下问题:

  1. 字符串中的转义序列
  2. 冒号后缺少空格
  3. 逗号后的额外空格(或其他地方)
  4. 字符串中的方括号和花括号
  5. 空输入不会失败

产出:

{
"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
}
}
}
}
]
}

只要使用 JsonDocumentUtf8JsonWriter.不需要第三方库。不需要 jsonString反序列化的目标对象。

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());
}

这对我来说在.Net Core 3.1中使用 System.Text.Json 是有效的

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


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


return JsonSerializer.Serialize(jsonElement, options);
}

使用一个简单的方法扩展:

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;
}
}
}

并且像这样使用它:

string jsonFormatted = json.BeautifyJson();