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}}}}]}
古いバージョンを更新しましたが、整数やブール値などの引用符で囲まれていない値をサポートするようになりました。
以前のバージョンをリファクタリングし、最終バージョンを取得しました。コードは短く、簡潔です。 1つの拡張メソッドのみが必要です。最も重要なのは、いくつかのバグを修正したことです。
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 ライブラリを使用し、Formatting.Indented列挙型でSerializeObjectを呼び出すこともできます-
var x = JsonConvert.SerializeObject(jsonString, Formatting.Indented);
ドキュメント:オブジェクトのシリアル化
更新-
もう一度試してみました。これが機能していたことはかなり確かです-おそらくそれは後続のバージョンで変更されたか、おそらく私は物事を想像しているだけです。とにかく、以下のコメントによると、期待どおりに機能しません。ただし、これらは実行します(linqpadでテスト済みです)。最初のコメントはコメントから、2番目は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美化機能のコンパクトバージョンを次に示します。
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 を使用するすばらしい回答がたくさんありますが、ToString()
と組み合わせてJObject.Parse
を使用するもう1つがあります。 :
var jObj = Newtonsoft.Json.Linq.JObject.Parse(json);
var formatted = jObj.ToString(Newtonsoft.Json.Formatting.Indented);
独自の関数を記述する主な理由は、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();
}
}
}
}
benjymous が指摘したように、一時オブジェクトで Newtonsoft.Json を使用し、デシリアライズ/シリアライズすることができます。
var obj = JsonConvert.DeserializeObject(jsonString);
var formatted = JsonConvert.SerializeObject(obj, Formatting.Indented);
PrettyPrint()
の\r
および\n
をスキップする必要があります。いくつかの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 = '\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();
}
}
クレジット [リンク切れ]
これにより、各アイテムが新しい行に配置されます
VB.NET
mytext = responseFromServer.Replace("{", vbNewLine + "{")
C#
mytext = responseFromServer.Replace("{", Environment.Newline + "{");
クレジットはすべてフランク・ツァナベティスに帰属します。ただし、これは最も短い直接的な例であり、空の文字列または壊れた元の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;
}
}
これは、私が使用したい受け入れられている答えの変形です。コメントされた部分は、読みやすい形式と考えるものになります(違いを確認するには、隣接するコードをコメント化する必要があります)。
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フォーマッター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);
}
以下の問題を修正します。
出力:
{
"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
}
}
}
}
]
}
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();
}
このバージョンでは、一度に多くを表示できるため、よりコンパクトで読みやすい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形式のバックアップファイルをアセンブルするプロセスの一部としてこれを使用するため、インデントの開始点を指定できます。