KKMIN

Visual JSON: Seeing complex data simply

It's been a while since I made a post; I've been pre-occupied with a new internship at the start of 2023 that has left me with little time for blogging and tinkering things of my own. However, this opportunity cost is quite worth it seeing how many new things I have learnt at my workplace within the span of just two months: a new language, new concepts, working with a team in an agile environment, as well as having mentors to guide my progress has been nothing short of a great growth opportunity.

Even so, I still try to keep myself engaged, thinking of the next interesting thing I could make, and Visual JSON was a simple tool that resulted from both this mindset as well as programming challenges I faced while working.

Context

As one of my first few programming tasks, I was delegated to replacing a particular set of APIs with a new set of APIs provided by our service vendor; the gist of the challenge I faced was that the API would return a humongous JSON response that was extremely difficult to read by itself, and I had to analyze its structure to sift out the data we needed. Granted it wasn't impossible, but when the data fills up several pages, your eyes start to glaze over and you tend to forget what level you are at when looking at deeply-nested values. So I thought of a possibly easier way to analyze such a huge JSON object.

JSON as a tree structure

The JSON data format, at its core, is trying to represent a set of hierarchical relationships between keys and values; nested objects can be considered as children of parent keys or objects, as denoted by indentations and colons. The JSON format can be easily represented via text form, and that looks like a side-ways tree. But since reading this side-ways tree gets irritating fast with big objects, I decided to render the data as a top-down tree while maintaining the hierarchical relationship between objects.

Tech stack

For this simple project, I decided to use React with Typescript as the main tools for rendering the visuals, with zustand as a simple data management library for sharing the JSON file across components. I did not plan for it to be a full-fledged website, so I did not use any full-stack React frameworks like Nextjs.

Interface and structure

The component structures were simple; on the main app, there would be the FileInput component, which is responsible for getting a file input (upload) from the file system, and a Visualizer component which holds the generated tree. The tree itself only consists of one component, the Node component which can recursively render itself to represent the JSON layers. The file that is uploaded in FileInput is shared across all components via zustand.

Breadth-First Traversal

Since the JSON object is already in a tree structure by its nature, we can recursively traverse the object via Breadth-First Search, rendering nodes level-by-level until the last item is reached, upon which we terminate.

This is important for preserving the structure of the JSON data, as objects/values on the same indentation have to be on the same horizontal level visually. The implementation is fairly trivial; at each traversal into a node, we can keep all the subnodes in a list and map them to render all of them in the same level at once:

Node.tsx

let level = [];
for (const key in jsonObj) {
if (jsonObj.hasOwnProperty(key)) {
let element = jsonObj[key as keyof JSON]!;
level.push({ key, element });
}
}
return <div className="Level">{level.map((item) => ...render the nodes here)}</div>

Which can be also refactored as:

Node.tsx

let level = Object.entries(jsonObj) // This returns an array of [key, value] of an object's enumerable string-keyed property pairs.
return <div className="Level">{level.map(([key, value]) => ...render the nodes here)}</div>

Tree Generation

Generating the whole tree just means we can recursively perform the BFS on each node and their children, who will then also generate levels for their own children:

Node.tsx

interface NodeProps {
jsonObj: JSON | null;
}
export default function Node(props: NodeProps) {
const {jsonObj} = props
let level = Object.entries(jsonObj)
return <div className="Level">{level.map(([key, value]) => (
<div>
<div className="Node">{key}</div>
<Node jsonObj={value}/>
</div>))}
</div>

Note that the above code does not consider or differentiate things like JSON arrays, or nameless objects in arrays.

Arrays and Key-less items

To differentiate between Objects and Arrays, I added square bracket "[ ]" tags on keys whose values were arrays, with the array elements under the key. Objects which have no corresponding key (e.g. they are inside a list) are represented by a "{ _ }" parent node, and arrays with no keys (e.g. array inside an array) were similarly represented by a "[ _ ]" parent node.

Result

Consider the following JSON data:


{
"sample_object":{
"sample_key":"sample_value"
},
"nested_list":[
"nested_value",
{
"deeper_key":"deeper_value",
"another_key":"another_value"
}
],
"nested_object":{
"nested_key":"nested_value",
"nested_key_2":{
"nested_key_2_1":"nested_value_2_1",
"nested_key_2_2":"nested_value_2_2",
"boolean_key":true,
"nested_object_2":{
"nested_key_3":"nested_value_3",
"nested_list_2":[
"nested_value_1",
"nested_value_2",
"nested_value_3",
[
"nested_value_4",
"nested_value_5",
"nested_value_6"
],
{
"nested_key_4":"nested_value_4",
"nested_key_5":"nested_value_5"
}
]
}
}
},
"null_key":null
}

Which will then be displayed as:

Example result of Visual JSON

Click the image to view it fully-sized.

With this, we have a clear and immediate bird's eye view of the entire JSON structure without having to scroll through a full page of JSON while trying to keep track of the nesting. If we want to know how to access a particular value at a leaf, we can simply backtrack the tree-traversal to see all its parents and values that need to be accessed.

Closing Thoughts

This was a fairly simple project that required a few days' work at most, but its impact was very noticeable for my particular task at that moment, which involved analyzing huge JSON objects and finding out what property to use. Other future things to implement could include quality of life features such as collapsible objects or adding color-coding to different data types, etc.

I also noticed that my React skills were a little dusty after several months of not working with it frequently, so this project helped to bring them back up to speed as well. Definitely a reminder that we ought to be continously learning and improving, and tinkering when the inspiration strikes to keep our toolshed sharp.

← Back to home

Comments