Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

🧩 Project: Build Your Own JSON Parser (Step 1)

Imagine JSON like Lego blocks that computers use to send and understand data. In this project, we’re learning how to read those blocks and say: “Yep, this is built correctly!” or “Nope, something’s wrong!”

🪜 Step-by-Step Guide for Step 1

🛠 Step 0: Setup

“Before we play the game, we need to set up the board.”

1.	Create folders like this:
jsonparser/
├── main.go
├── lexer/
│ └── lexer.go
├── parser/
│ └── parser.go
├── tests/
│ └── step1/
│ ├── valid.json
│ └── invalid.json
2.	Put this in tests/step1/valid.json

{}

3.	Put this in tests/step1/invalid.json

{

🧠 Step 1: Understand What We’re Building

“We’re building a tool that checks if a JSON is okay or broken.”

We want to: • Read a file. • Break it into pieces (like Lego blocks). • Check if those pieces make a real object ({}). • Say “Valid JSON” ✅ or “Invalid JSON” ❌.

🧪 The Parts of Our Tool

1️⃣ main.go — the commander

It reads the file, sends it to the lexer, then the parser, and prints the result.

package main

import (
    "fmt"
    "os"

    "jsonparser/lexer"
    "jsonparser/parser"
)

func main() {
    if len(os.Args) < 2 {
        fmt.Println("Usage: ./jsonparser <path-to-json-file>")
        os.Exit(1)
    }

    filePath := os.Args[1]
    data, err := os.ReadFile(filePath)
    if err != nil {
    	fmt.Printf("Failed to read file: %v\n", err)
    	os.Exit(1)
    }

    tokens := lexer.Tokenize(string(data))
    if parser.Parse(tokens) {
    	fmt.Println("Valid JSON")
    	os.Exit(0)
    } else {
    	fmt.Println("Invalid JSON")
    	os.Exit(1)
    }
}

2️⃣ lexer/lexer.go — the scanner

It breaks the string like {} into tokens (chunks), like:

[LEFT_BRACE, RIGHT_BRACE]

package lexer

import "strings"

type TokenType string

const (
    LEFT_BRACE TokenType = "{"
    RIGHT_BRACE TokenType = "}"
    ILLEGAL TokenType = "ILLEGAL"
    EOF TokenType = "EOF"
)

type Token struct {
    Type TokenType
    Literal string
}

func Tokenize(input string) []Token {
    var tokens []Token
    input = strings.TrimSpace(input)

    for _, ch := range input {
    	switch ch {
    	case '{':
    		tokens = append(tokens, Token{Type: LEFT_BRACE, Literal: "{"})
    	case '}':
    		tokens = append(tokens, Token{Type: RIGHT_BRACE, Literal: "}"})
    	default:
    		// not a valid character in step 1
    		tokens = append(tokens, Token{Type: ILLEGAL, Literal: string(ch)})
    	}
    }

    tokens = append(tokens, Token{Type: EOF, Literal: ""})
    return tokens
}

3️⃣ parser/parser.go — the judge

It looks at the tokens and decides if it’s correct.

package parser

import "jsonparser/lexer"

func Parse(tokens []lexer.Token) bool {
    // Step 1: Only valid thing is [LEFT_BRACE, RIGHT_BRACE, EOF]
    if len(tokens) != 3 {
        return false
    }

    return tokens[0].Type == lexer.LEFT_BRACE &&
    	tokens[1].Type == lexer.RIGHT_BRACE &&
    	tokens[2].Type == lexer.EOF

}

▶️ Run It!

Go into your terminal and run this:

go run main.go tests/step1/valid.json # ✅ Should print: Valid JSON
go run main.go tests/step1/invalid.json # ❌ Should print: Invalid JSON

🎓 What Did We Learn? • ✅ JSON is just a way to store data, like a toy box. • ✅ Lexers break it into tokens (like sorting toys). • ✅ Parsers check if the toys are arranged correctly. • ✅ We only accept {} right now. • ❌ Anything else is “broken” JSON.

🧠 Coming Next…

In Step 2, we’ll look inside the box and check for strings like:

{"key": "value"}