개요
C#에서 JSON 파일을 파싱할 때 보통 각 계층을 클래스로 만들어 HAS-A 관계로 포함시키는 것이다. 개발자 입장에서 JSON 파일을 열어본 후, 해당 구조를 일일이 파악해서 클래스로 만드는 것이 여간 귀찮은 게 아니다. 그래서 JSON 파일을 넣으면 자동으로 계층을 보기 좋게 출력 해주는 프로그램을 만들어 봤다. 추후엔 이 로직을 바탕으로 자동으로 클래스를 생성해주는 프로그램을 만들 수 있지 않을까 라는 생각도 해본다.
본문
사용법 (exe 파일 활용)
JsonParser.msi 파일을 다운받은 뒤, 해당 프로그램을 설치하고 JsonParse.exe 파일을 실행시키면 다음과 같은 화면이 뜰 것이다. 여기서 Select JSON File을 선택하자.
다음과 같이 파일 탐색기가 뜰 것인데, 파싱을 하고 싶은 json 파일을 선택하자.
그러면 다음과 같이 깊이 별로 어떤 정보가 있는지, 그리고 계층도가 어떻게 이루어져있는지 출력해준다.
사용법 (코드 활용)
더보기
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Windows.Forms;
namespace JsonFileParser
{
public partial class Form1 : Form
{
class JsonHierarchyAnalyzer
{
public static void AnalyzeJsonHierarchy(string jsonFilePath, TextBox outputTextBox)
{
string jsonContent = File.ReadAllText(jsonFilePath);
using (JsonDocument jsonDocument = JsonDocument.Parse(jsonContent))
{
Dictionary<int, HashSet<string>> hierarchy = new Dictionary<int, HashSet<string>>();
TreeNode rootNode = new TreeNode("root");
int maxDepth = 0;
AnalyzeElement(jsonDocument.RootElement, 0, hierarchy, rootNode, ref maxDepth);
List<int> emptyDepths = new List<int>();
foreach (var depth in hierarchy)
{
if (depth.Value.Count == 0)
{
emptyDepths.Add(depth.Key);
}
}
foreach (var emptyDepth in emptyDepths)
{
hierarchy.Remove(emptyDepth);
}
outputTextBox.AppendText($"Total Depth: {maxDepth}{Environment.NewLine}");
PrintHierarchyByDepth(hierarchy, outputTextBox);
outputTextBox.AppendText(new string('-', 50) + Environment.NewLine);
PrintHierarchyTree(rootNode, outputTextBox);
}
}
private static void AnalyzeElement(JsonElement element, int currentDepth, Dictionary<int, HashSet<string>> hierarchy, TreeNode currentNode, ref int maxDepth)
{
if (!hierarchy.ContainsKey(currentDepth))
{
hierarchy[currentDepth] = new HashSet<string>();
}
switch (element.ValueKind)
{
case JsonValueKind.Object:
foreach (JsonProperty property in element.EnumerateObject())
{
hierarchy[currentDepth].Add(property.Name);
TreeNode childNode = currentNode.AddChild(property.Name);
AnalyzeElement(property.Value, currentDepth + 1, hierarchy, childNode, ref maxDepth);
}
break;
case JsonValueKind.Array:
foreach (JsonElement item in element.EnumerateArray())
{
AnalyzeElement(item, currentDepth, hierarchy, currentNode, ref maxDepth);
}
break;
}
if (currentDepth > maxDepth)
{
maxDepth = currentDepth;
}
}
private static void PrintHierarchyByDepth(Dictionary<int, HashSet<string>> hierarchy, TextBox outputTextBox)
{
foreach (var depth in hierarchy)
{
outputTextBox.AppendText($"Depth {depth.Key}:{Environment.NewLine}");
foreach (var key in depth.Value)
{
outputTextBox.AppendText($" {key}{Environment.NewLine}");
}
}
}
private static void PrintHierarchyTree(TreeNode node, TextBox outputTextBox, string indent = "", bool isLast = true)
{
outputTextBox.AppendText(indent + (isLast ? "└── " : "├── ") + node.Name + Environment.NewLine);
indent += isLast ? " " : "│ ";
for (int i = 0; i < node.Children.Count; i++)
{
PrintHierarchyTree(node.Children[i], outputTextBox, indent, i == node.Children.Count - 1);
}
}
}
class TreeNode
{
public string Name { get; }
public List<TreeNode> Children { get; }
public TreeNode(string name)
{
Name = name;
Children = new List<TreeNode>();
}
public TreeNode AddChild(string name)
{
foreach (var child in Children)
{
if (child.Name == name)
{
return child;
}
}
var newChild = new TreeNode(name);
Children.Add(newChild);
return newChild;
}
}
private TextBox outputTextBox;
private Button selectFileButton;
public Form1()
{
InitializeComponent();
InitializeCustomComponents();
}
private void InitializeCustomComponents()
{
this.outputTextBox = new TextBox
{
Multiline = true,
ScrollBars = ScrollBars.Vertical,
Dock = DockStyle.Fill,
WordWrap = true, // 줄 바꿈 활성화
};
this.selectFileButton = new Button
{
Text = "Select JSON File",
Dock = DockStyle.Top
};
this.selectFileButton.Click += new EventHandler(this.SelectFileButton_Click);
this.Controls.Add(this.outputTextBox);
this.Controls.Add(this.selectFileButton);
}
private void SelectFileButton_Click(object sender, EventArgs e)
{
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
string jsonFilePath = openFileDialog.FileName;
outputTextBox.Clear();
JsonHierarchyAnalyzer.AnalyzeJsonHierarchy(jsonFilePath, outputTextBox);
}
}
}
}
}
혹시 실행 파일이 찝찝하면 아래에 접은 글로 코드를 보고 자신의 입맛에 맞게 수정한 뒤 사용하면 좋을 것 같다. 필자의 경우 JSON 요소를 재귀적으로 순회하며 현재 깊이를 추적하고, 각 깊이에 있는 키를 hierarchy 딕셔너리에 저장하게 하였다. 그리고 해당 딕셔너리에 저장된 각 깊이와 해당 깊이의 키들을 출력하고, 트리 구조를 구축하게 로직을 구성하였다. 이후의 사용법은 위와 동일하니 JSON 파일의 구조를 파악할 때 참고하면 되겠다.
반응형