Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Dennis Clark
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Dennis Clark ( @boomerangfish )

Creating An Interactive JSON Explorer Using CSS Grid In Angular 9.0.0-next.14

By Ben Nadel on

At work, I spend a lot of time combing through Logs. Which means, I spend a lot of time looking at JSON (JavaScript Object Notation) payloads. Some of these payloads are rather large, which is why I do things like create Loggly bookmarklets for better Log rendering. Partly as a fun Code Kata and partly because I think I'll actually use this, I created JSON Explorer using CSS Grid and Angular 9.0.0-next.14. This JavaScript application parses JSON into an interactive data structure that, hopefully, makes it easier for me to examine the JSON content in my logs.

Run this demo in my JSON Explorer project on GitHub.

View this code in my JSON Explorer project on GitHub.

The implementation of this Angular app is mostly straightforward. It takes a JSON string (provided by the user), parses said JSON string into a native JavaScript data structure, and then renders that data structure in the browser. It's the rendering of the data structure that's interesting. I'm using a recursive Angular component, <json-node>, that outputs the data as a series of nested CSS Grid layouts.

This JsonNodeComponent has a single input, [value], that renders the passed-in data-structure. For simple values - strings, numbers, booleans, and null - it renders the value and then completes. But, for complex values - Objects and Arrays - it renders the value using a recursive call that renders <json-node> for each one of its entries (ie, object keys and array indices).

To see this in action, let's look at the HTML template for the JsonNodeComponent class. As you look at this code, keep in mind that the rendering of parts of the data tree can be toggled on-and-off, which is why there are many [ngIf] directives:

<div
	class="payload"
	[ngSwitch]="valueType">

	<ng-template ngSwitchCase="Null">
		<div
			(click)="toggle()"
			class="label is-null"
			[class.is-collapsed]="isCollapsed">
			Null
		</div>
		<ng-template [ngIf]="( ! isCollapsed )">
			<div class="value is-null">
				null
			</div>
		</ng-template>
	</ng-template>

	<ng-template ngSwitchCase="String">
		<div
			(click)="toggle()"
			class="label is-string"
			[class.is-collapsed]="isCollapsed">
			String
		</div>
		<ng-template [ngIf]="( ! isCollapsed )">
			<div class="value is-string">
				<a (click)="parseString( $event )">
					{{ value }}
				</a>
			</div>
		</ng-template>
	</ng-template>

	<ng-template ngSwitchCase="Number">
		<div
			(click)="toggle()"
			class="label is-number"
			[class.is-collapsed]="isCollapsed">
			Number
		</div>
		<ng-template [ngIf]="( ! isCollapsed )">
			<div class="value is-number">
				{{ value }}
			</div>
		</ng-template>
	</ng-template>

	<ng-template ngSwitchCase="Boolean">
		<div
			(click)="toggle()"
			class="label is-boolean"
			[class.is-collapsed]="isCollapsed">
			Boolean
		</div>
		<ng-template [ngIf]="( ! isCollapsed )">
			<div class="value is-boolean">
				{{ value }}
			</div>
		</ng-template>
	</ng-template>

	<ng-template ngSwitchCase="Array">
		<div
			(click)="toggle()"
			class="header is-array"
			[class.is-collapsed]="isCollapsed">
			<div class="type">
				Array
			</div>
			<div class="entry-count">
				Entries: {{ entryCount }}
			</div>
		</div>
		<ng-template [ngIf]="( ! isCollapsed )">
			<ng-template ngFor let-subvalue let-index="index" [ngForOf]="value">
				<div
					(click)="toggle( index )"
					class="label is-array"
					[class.is-collapsed]="collapsedEntries[ index ]">
					{{ index }}
				</div>
				<ng-template [ngIf]="( ! collapsedEntries[ index ] )">
					<div class="value is-array">
						<json-node [value]="subvalue"></json-node>
					</div>
				</ng-template>
			</ng-template>
		</ng-template>
	</ng-template>
	
	<ng-template ngSwitchCase="Object">
		<div
			(click)="toggle()"
			class="header is-object"
			[class.is-collapsed]="isCollapsed">
			<div class="type">
				Object
			</div>
			<div class="entry-count">
				Entries: {{ entryCount }}
			</div>
		</div>
		<ng-template [ngIf]="( ! isCollapsed )">
			<ng-template ngFor let-subvalue [ngForOf]="value | keyvalue">
				<div
					(click)="toggle( subvalue.key )"
					class="label is-object"
					[class.is-collapsed]="collapsedEntries[ subvalue.key ]">
					{{ subvalue.key }}
				</div>
				<ng-template [ngIf]="( ! collapsedEntries[ subvalue.key ] )">
					<div class="value is-object">
						<json-node [value]="subvalue.value"></json-node>
					</div>
				</ng-template>
			</ng-template>
		</ng-template>
	</ng-template>

</div>

Notice that the last two cases in the ngSwitch directive, for Array and Object, each iterate over the values entries, making a recursive call to the JsonNodeComponent. This allows the Angular app to render a data structure of arbitrary depth and complexity.

NOTE: This rendering cannot handle circular references. However, since the data being piped into this component is generated from a JSON.parse() call, we know that no circular references will ever exist.

As you can see in the HTML template, each data-point is rendered with a Label and a Value. These two elements are implemented as 2 columns in a CSS Grid layout. CSS Grid makes this super easy, especially considering the fact that I can collapse the Value element based on user interactions. Since CSS Grid doesn't allow elements to float up into different rows (the way CSS Flexbox may in this particular context), I can collapse entire parts of the rendered data structure without altering the layout.

Another thing that I wanted to try and build into this JSON Explorer app was the ability to share a JSON payload with your teammate. To do this, I am attempting to Base64-encode the JSON payload and persist it to the URL. I am not sure how long this URL can get before the browser starts throwing errors. But, at least for smaller JSON payloads, this approach seems to work well.

Altogether, when I run this Angular application using the demo data, I get the following interactive data structure:

JSON Explorer creates an interactive data structure using CSS Grid and Angular 9.

This started as a fun little Angular Code Kata; but, I think this JSON Explorer actually became a tool that I will be using. One thing in particular that I like about this is the ability to parse nested JSON payloads on-the-fly (as you can see in the GIF above) - this will be really helpful when looking at certain Log entries.



Reader Comments

I thought this was a:

cfdump

When I saw the image on Twitter!
But, seriously, this is a great exploration of recursion.

I used to use something called:

mat-tree

To create collapsible tree like structures. It is part of Google Material's API:

https://material.angular.io/components/tree/overview

I used it, primarily, to make nested lists of checkboxes, but you could easily adapt it to make an object explorer or file system structure.

Reply to this Comment

@Charles,

Oh, rest assured that this was heavily influenced by the majesty of cfdump / dump() :D That's still one of the best debugging tools of all time.

I finally tried the Angular Material CDK! I used the drag-n-drop for my Breadboarding Proof-of-Concept. It seemed pretty cool. I really need to try the CDK a bit more.

Reply to this Comment

Material & CDK are super awesome.

I used to think their UI was too clinical, but now I just love its productivity benefits.

You can create super professional interfaces in minutes. And these things just work out of the box. Mind you, with Google's engineering team behind it, it's no wonder this stuff is so robust. And the best thing, is that it is pretty much integrated into Angular.

Yes. The Drag & Drop component is superb, as is the Mat-tree, CDK Stepper and Material Modals!

I even use Material Lite, now, which is Angular Material's Vanilla JS sibling.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.