Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Open-Source, Secure, Client-Side, Network-Free, JSON Linting

By Ben Nadel on

When I write data to a log aggregation tool, like Loggly, I try to write said data as structured JSON (JavaScript Object Notation) values. This way, the log aggregation tool can parse and index the log payloads using something like ElasticSearch (under the hood). Unfortunately, in order to manage cost and performance, not all values in said payload can get indexed. As such, in my day-to-day work, I often need to manually deserialize log entries when I want to access the non-indexed fields. To do this, I normally just Google for "JSON Linting" and use the first result.

Run this app in my JSON-Linting project on GitHub.

View this code in my JSON-Linting project on GitHub.

The other day, however, I was doing this on a Zoom screen-share when one of my co-workers asked me, "Are you sure that it's safe to be pasting log data into this site?"

To be honest, I had never really thought about it. I had just assumed it was safe - I mean, it's not like the data in the log entry was all that sensitive; but, I had no way of really knowing what might be taking place in the page. This lack of clarity left me feeling unsettled.

For peace-of-mind, I decided to create an open-source, secure, client-side, network-free JSON Linting tool that was hosted on GitHub pages. My JSON Linting tool is little more than a glorified wrapper around JSON.parse() and JSON.stringify(). But, it submits nothing to a server and loads nothing over the network. As such, I - and others - can rest assured that there's no opportunity for a malicious actor to access the pasted content.

To see what I mean, take a look at the v1.0 code yourself:

<!doctype html>
<html lang="en">
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1" />
	<meta name="Keywords" content="linting,json,json linting,parse json,test json" />
	<meta name="Description" content="Providing simple, secure, client-side, network-free JSON linting that ensures no one else is seeing the data you are testing." />

		Secure, Client-Side, Network-Free JSON Linting

	<style type="text/css">

		.description code,
		.description strong {
			background-color: #FFF3A0 ;
			color: #26220A ;
			font-weight: 400 ;

		.intake__input {
			background-color: #FAFDFF ;
			border: 2px solid #3978A1 ;
			box-sizing: border-box ;
			display: block ;
			font-size: 100% ;
			height: 100px ;
			margin-bottom: 10px ;
			padding: 10px 10px 10px 10px ;
			width: 100% ;

		.intake__submit {
			background-color: #4E86AA ;
			border: 2px solid #3978A1 ;
			border-radius: 4px 4px 4px 4px ;
			color: #FFFFFF ;
			display: inline-block ;
			font-size: 100% ;
			padding: 5px 15px 5px 15px ;

		.output--pending {
			display: none ;

		.output--error .output__success,
		.output--success .output__error {
			display: none ;

		.error {
			background-color: #FFF0F7 ;
			border: 2px solid #FFCAE3 ;
			border-radius: 4px 4px 4px 4px ;
			color: #FF0077 ;
			padding: 10px 10px 10px 10px ;

		.success__pre {
			background-color: #F5F2F0 ;
			border-radius: 4px 4px 4px 4px ;
			padding: 10px 10px 10px 10px ;

		.success__code {
			/* Borrowed from PrismJS 1.15.0 */
			 * prism.js default theme for JavaScript, CSS and HTML
			 * Based on dabblet (
			 * @author Lea Verou
			color: black ;
			background: none ;
			text-shadow: 0 1px white ;
			font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace ;
			text-align: left ;
			white-space: pre ;
			word-spacing: normal ;
			word-break: normal ;
			word-wrap: normal ;
			line-height: 1.5 ;

			-moz-tab-size: 4 ;
			-o-tab-size: 4 ;
			tab-size: 4 ;

			-webkit-hyphens: none ;
			-moz-hyphens: none ;
			-ms-hyphens: none ;
			hyphens: none ;


		Secure, Client-Side, Network-Free JSON Linting

	<p class="description">
		The JSON linting on this page is performed completely by <strong>client-side JavaScript
		using <code>JSON.parse()</code></strong> with <strong>no external dependencies</strong>.
		As such, you can rest assured that absolutely <strong>no one but you can see the data</strong>
		that gets pasted into this form.

	<form class="intake">


		<textarea class="intake__input" placeholder="Paste your JSON here..."></textarea>

		<button type="submit" class="intake__submit">
			Parse JSON Input


	<div class="output output--pending">


		<!-- If the JSON could not be parsed, show error. -->
		<div class="output__error error">
			<strong class="error__label">Input could not be parsed:</strong>
			<span class="error__message">The input is not valid JSON.</span>

		<!-- If the JSON could be parsed, show formatted output. -->
		<div class="output__success success">
			<pre class="language-javascript success__pre"><code class="language-javascript success__code"></code></pre>


	<script type="text/javascript">

		var intakeView = {
			form: document.querySelector( ".intake" ),
			input: document.querySelector( ".intake__input" )

		var outputView = {
			output: document.querySelector( ".output" ),
			error: document.querySelector( ".output__error" ),
			success: document.querySelector( ".output__success" )

		var errorView = {
			message: document.querySelector( ".error__message" )

		var successView = {
			code: document.querySelector( ".success__code" )

		intakeView.form.addEventListener( "submit", handleIntakeSubmit, false );

		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //

		// I handle the form submission, moving the input through linting.
		function handleIntakeSubmit( event ) {

			// Since we're handling this client-side, prevent any form behaviors.


			try {

				var parsedInput = JSON.parse( intakeView.input.value );

			} catch ( error ) {

				renderOutputError( error );


			renderOutputSuccess( parsedInput );


		// I render the given linting error.
		function renderOutputError( error ) {

			console.warn( "Input could not be parsed:" );
			console.error( error );

			// Move output to error state.
			outputView.output.classList.add( "output--error" );
			outputView.output.classList.remove( "output--pending", "output--success" );

			errorView.message.innerText = error.message;


		// I render the given linting success.
		function renderOutputSuccess( data ) {

			console.warn( "Successfully parsed input:" );
			console.log( data );

			// Move output to success state.
			outputView.output.classList.add( "output--success" );
			outputView.output.classList.remove( "output--pending", "output--error" );

			successView.code.innerText = JSON.stringify( data, null, "\t" );


		// I reset the output views.
		function resetOutput() {

			// Move output to pending state.
			outputView.output.classList.add( "output--pending" );
			outputView.output.classList.remove( "output--error", "output--success" );

			// Clear error and success views.
			errorView.message.innerText = "";
			successView.code.innerText = "";




As you can see, not even the JavaScript or the CSS is loaded over the network - it's all in-lined in the page. This is probably unnecessary; but, I enjoyed the totality of the isolation.

Anyway, just thought I would share this in case anyone else was interested in using it.

Reader Comments

If you want something a bit more full-featured, I use the "JSON Lite" browser extension.

This lets you drag-and-drop any JSON file directly into the browser, and get easy-to-read views with a theme, formatting, and collapseable subtrees.

If you are just using clipboard data, it might not be as convenient, since I'm not sure if there's any simple way to render the data without it being in a file first.


Sounds pretty solid, I'll check it out. To be honest, I'm not used to thinking in terms of Chrome plug-ins yet. Not sure why -- just not the obvious step for my brain. Right now, I basically just have the anti-virus stuff required by work, and a Cookie Viewer that makes development easier. Other than that, I just use the native Chrome Dev Tools.

I'm slow to get on band-wagons :D