Fundamentals 13 min read

How to Build a Custom JSON Parser in JavaScript from Scratch

This article walks through creating a JSON parser in JavaScript, covering the JSON grammar, railroad and BNF diagrams, step‑by‑step implementation of object, array and value parsing, robust error handling, and suggestions for meaningful error messages.

WecTeam
WecTeam
WecTeam
How to Build a Custom JSON Parser in JavaScript from Scratch

In a recent Cassidoo weekly newsletter a challenge was posed: write a function that accepts a valid JSON string and converts it into an object, using any language and data structure.

The article explains that JSON is a language with its own formal syntax, which can be referenced from the official specification.

Understanding the Grammar

The JSON specification provides two visual representations: a railroad diagram and a McKeeman form (a variant of Backus‑Naur Form). Both describe the same grammar and are interchangeable for building a parser.

Implementing the Parser

We start with a skeleton function fakeParseJSON(str) and an index i that tracks the current character. The first concrete implementation is parseObject, which handles JSON objects by consuming the opening brace, whitespace, key‑value pairs, commas, and the closing brace.

function fakeParseJSON(str){
  let i = 0;
  function parseObject(){
    if(str[i]==='{'){
      i++;
      skipWhitespace();
      const result = {};
      let initial = true;
      while(str[i]!=='}'){
        if(!initial){
          eatComma();
          skipWhitespace();
        }
        const key = parseString();
        skipWhitespace();
        eatColon();
        const value = parseValue();
        result[key] = value;
        initial = false;
      }
      i++; // move past '}'
      return result;
    }
  }
  // ... other helper functions
}

Helper functions eatComma and eatColon verify the expected characters and throw descriptive errors when they are missing.

function eatComma(){
  if(str[i]!==',') throw new Error('Expected ",".');
  i++;
}
function eatColon(){
  if(str[i]!==':') throw new Error('Expected ":".');
  i++;
}

After objects, the article adds an parseArray routine that mirrors the object logic but works with square brackets and pushes parsed values into an array.

function parseArray(){
  if(str[i]==='['){
    i++;
    skipWhitespace();
    const result = [];
    let initial = true;
    while(str[i]!==']'){
      if(!initial){
        eatComma();
        skipWhitespace();
      }
      const value = parseValue();
      result.push(value);
      initial = false;
    }
    i++; // move past ']'
    return result;
  }
}

The parseValue function attempts each possible JSON value type in order—string, number, object, array, and the literals true, false, null —using the nullish coalescing operator ( ??) to fall back when a parser returns undefined.

function parseValue(){
  skipWhitespace();
  const value =
    parseString() ??
    parseNumber() ??
    parseObject() ??
    parseArray() ??
    parseKeyword('true', true) ??
    parseKeyword('false', false) ??
    parseKeyword('null', null);
  skipWhitespace();
  return value;
}

The parseKeyword helper checks the upcoming characters against a keyword and advances the index if they match.

function parseKeyword(name, value){
  if(str.slice(i, i+name.length)===name){
    i += name.length;
    return value;
  }
}

Handling Unexpected Input

Robust parsers must detect and report errors such as unexpected tokens or premature end‑of‑input. The article shows how to guard loop conditions with i < str.length and to emit clear error messages with position information.

if(i>=str.length) throw new Error('Unexpected end of input');
if(str[i]!==expected) throw new Error(`Unexpected token "${str[i]}" at position ${i}`);

Beyond raw messages, the guide suggests assigning error codes (e.g., JSON_ERROR_001) and printing a code snippet with a caret pointing to the offending character, helping developers locate the problem quickly.

function printCodeSnippet(message){
  const from = Math.max(0, i-10);
  const trimmed = from>0;
  const padding = (trimmed?3:0)+(i-from);
  const snippet = [
    (trimmed?'...':'')+str.slice(from,i+1),
    ' '.repeat(padding)+'^',
    ' '.repeat(padding)+message
  ].join('
');
  console.log(snippet);
}

When possible, the parser can also suggest corrective actions, such as inserting a missing colon or closing quote, and provide links to further documentation.

Conclusion

Building a JSON parser starts with a solid understanding of the grammar, whether expressed as a railroad diagram or BNF. Implementing each production rule—objects, arrays, values—step by step yields a functional parser, and adding detailed error handling makes it developer‑friendly.

With the basics mastered, developers can extend the parser to handle more complex scenarios, integrate it into larger projects, or study how mature parsers like Babel and Svelte are built.

JavaScriptJSONError HandlingtutorialSyntaxParser
WecTeam
Written by

WecTeam

WecTeam (维C团) is the front‑end technology team of JD.com’s Jingxi business unit, focusing on front‑end engineering, web performance optimization, mini‑program and app development, serverless, multi‑platform reuse, and visual building.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.