Skip to content

Instantly share code, notes, and snippets.

@luoling8192
Created July 30, 2025 19:00
Show Gist options
  • Select an option

  • Save luoling8192/0abd8b5187557390e7f40c43a02cae95 to your computer and use it in GitHub Desktop.

Select an option

Save luoling8192/0abd8b5187557390e7f40c43a02cae95 to your computer and use it in GitHub Desktop.
JSON Parser
import { describe, expect, it } from 'vitest'
function jsonParse(json: string) {
let currentIndex = 0
function token() {
return json[currentIndex]
}
function nextToken() {
currentIndex++
}
function parseValue() {
skipSpace()
const token = json[currentIndex]
switch (token) {
case '{':
return parseObject()
case '"':
return parseString()
case '[':
return parseArray()
default:
if (isNumber()) {
return parseNumber()
}
return parseKeyword()
}
}
function parseObject(): any {
let obj = {}
nextToken()
skipSpace()
if (token() === '}') {
nextToken()
return {}
}
skipSpace()
while (currentIndex < json.length) {
let key = ''
if (token() === '"')
key = parseString()
skipSpace()
if (token() === ':')
nextToken() // next
skipSpace()
const value = parseValue()
obj = Object.assign(obj, { [key]: value })
skipSpace()
if (token() === '}') {
nextToken()
return obj
}
if (token() === ',') {
nextToken() // next
continue
}
throw new Error(`Unexpected token : ${token()}`)
}
}
// [1, 2, 3]
function parseArray(): any {
nextToken() // next
skipSpace()
if (token() === ']') {
nextToken()
return []
}
const array = []
while (currentIndex < json.length) {
skipSpace()
const value = parseValue()
array.push(value)
if (token() === ']') {
nextToken()
return array
}
if (token() === ',') {
nextToken()
continue
}
throw new Error(`Unexpected token ${token()}`)
}
}
function parseString() {
nextToken()
let result = ''
while (currentIndex < json.length) {
const token = json[currentIndex++]
if (token === '"')
break
result += token
}
return result
}
function isNumber() {
const token = json[currentIndex]
return (token >= '0' && token <= '9') || token === '-'
}
function skipSpace() {
while (currentIndex < json.length
&& (json[currentIndex] === ' '
|| json[currentIndex] === '\n'
|| json[currentIndex] === '\t')) {
nextToken()
}
}
function parseKeyword() {
if (token() === 't' && json.slice(currentIndex, currentIndex + 4) === 'true') {
currentIndex += 4
return true
}
if (token() === 'f' && json.slice(currentIndex, currentIndex + 5) === 'false') {
currentIndex += 5
return false
}
if (token() === 'n' && json.slice(currentIndex, currentIndex + 4) === 'null') {
currentIndex += 4
return null
}
throw new Error(`Unexpected token ${token()}`)
}
function parseNumber() {
let result = ''
while (currentIndex < json.length) {
if (!isNumber())
return Number(result)
result += token()
nextToken()
}
}
return parseValue()
}
// function expectJSON(json: string) {
// expect(JSON.stringify(jsonParse(json))).toBe(json)
// }
function expectObject(obj: any) {
const str = JSON.stringify(obj)
const res = JSON.stringify(jsonParse(str))
expect(res).toBe(str)
}
describe('json parser', () => {
it('should parse {}', () => {
expectObject({})
})
it('should parse {"a":1}', () => {
expectObject({ a: 1 })
})
it('should parse { "a": 1 }', () => {
expect(JSON.stringify(jsonParse(`{ "a": 1 }`))).toBe(`{"a":1}`)
})
it('should parse {"a":{}}', () => {
expectObject({ a: {} })
})
it('should parse {"a":{"b":1}}', () => {
expectObject({ a: { b: 1 } })
})
it('should parse {"a":{"b":1,"c":"hello"}}', () => {
expectObject({ a: { b: 1, c: 'hello' } })
})
it('should parse array', () => {
expectObject({ a: { b: 1, c: 'hello', d: [1, 2, 3] } })
})
it('should parse boolean and null', () => {
expectObject({ a: true, b: false, c: null, d: [false, null, 1] })
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment