JSON formatter в C#?
Ищу функцию, которая будет принимать string Json в качестве входных данных и форматировать его с разрывами строк и отступов. Проверка была бы бонусом, но не нужна, и мне не нужно разбирать ее на объект или что-то еще.
кто-нибудь знает такой библиотеки?
пример ввода:
{"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}}}}]}
15 ответов:
я обновил старую версию, теперь она должна поддерживать некотируемые значения, такие как целые числа и логические значения.
я переработал предыдущую версию и получил окончательную версию: Код короче и чище. Требуется только один метод расширения. Самое главное: исправлены некоторые ошибки.
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); } } }
вы также можете использовать Newtonsoft.Json библиотека для этого и вызовите 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)); }
более короткий образец для 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 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); }выходы:
{ "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 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 } } } } ] }
здесь уже есть куча отличных ответов, которые используют Newtonsoft.JSON, а вот еще один, который использует
JObject.Parseв сочетании сToString(), так как это еще не было упомянуто:var jObj = Newtonsoft.Json.Linq.JObject.Parse(json); var formatted = jObj.ToString(Newtonsoft.Json.Formatting.Indented);
вы должны пропустить
\rи\ninPrettyPrint(). Вывод выглядит забавно, что некоторые crlf уже присутствуют (или источник уже был отформатирован).
исправил... отчасти.
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 = ''; 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(); } }
кредит[мертвой ссылке]
Это вариант принятого ответа, который мне нравится использовать. Комментируемые части приводят к тому, что я считаю более читаемым форматом (вам нужно будет закомментировать соседний код, чтобы увидеть разницу):
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(@"\[\]", "[]"); } }
Как benjymous указал, вы можете использовать Newtonsoft.Json С временным объектом и десериализации/сериализации.
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 обычно выполняют разбор строк в типы .net и преобразование их обратно в строку, что может привести к потере исходных строк. Например 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(); } } } }
все кредиты принадлежат Фрэнку Цанабетису. Однако это самый короткий прямой пример, который также сохраняется в случае пустой строки или сломанной исходной строки JSON:
using Newtonsoft.Json; using Newtonsoft.Json.Linq; ... private static string Format(string jsonString) { try { return JToken.Parse(jsonString).ToString(Formatting.Indented); } catch { return jsonString; } }
J Брайан Прайс, хороший пример, но есть недостатки
{\"response\":[123, 456, {\"name\":\"John\"}, {\"count\":3}]}после форматирования
{ "response" : [ 123, 456, { "name" : "John" }, { "count" : 3 } ] }неправильного уклона :(
пример
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(); }
Comments