const enum State { TopLevel, Array, Object, } const enum Char { // Punctuation Newline = 0x0A, Space = 0x20, Quote = 0x22, Plus = 0x2B, Comma = 0x2C, Minus = 0x2D, Dot = 0x2E, Slash = 0x2F, Colon = 0x3A, OpenBracket = 0x5B, Backslash = 0x5C, CloseBracket = 0x5D, OpenBrace = 0x7B, CloseBrace = 0x7D, // Numbers Digit0 = 0x30, Digit1 = 0x31, Digit2 = 0x32, Digit3 = 0x33, Digit4 = 0x34, Digit5 = 0x35, Digit6 = 0x36, Digit7 = 0x37, Digit8 = 0x38, Digit9 = 0x39, // Uppercase letters UpperA = 0x41, UpperE = 0x45, UpperF = 0x46, // Lowercase letters LowerA = 0x61, LowerB = 0x62, LowerE = 0x65, LowerF = 0x66, LowerL = 0x6C, LowerN = 0x6E, LowerR = 0x72, LowerS = 0x73, LowerT = 0x74, LowerU = 0x75, } const fromCharCode = String.fromCharCode function throwSyntaxError(bytes: Uint8Array, index: number, message?: string): void { const c = bytes[index] let line = 1 let column = 0 for (let i = 0; i < index; i++) { if (bytes[i] === Char.Newline) { line++ column = 0 } else { column++ } } throw new SyntaxError( message ? message : index === bytes.length ? 'Unexpected end of input while parsing JSON' : c >= 0x20 && c <= 0x7E ? `Unexpected character ${fromCharCode(c)} in JSON at position ${index} (line ${line}, column ${column})` : `Unexpected byte 0x${c.toString(16)} in JSON at position ${index} (line ${line}, column ${column})`) } export function JSON_parse(bytes: Uint8Array): any { if (!(bytes instanceof Uint8Array)) { throw new Error(`JSON input must be a Uint8Array`) } const propertyStack: (string | null)[] = [] const objectStack: any[] = [] const stateStack: State[] = [] const length = bytes.length let property: string | null = null let state = State.TopLevel let object: any let i = 0 while (i < length) { let c = bytes[i++] // Skip whitespace if (c <= Char.Space) { continue } let value: any // Validate state inside objects if (state === State.Object && property === null && c !== Char.Quote && c !== Char.CloseBrace) { throwSyntaxError(bytes, --i) } switch (c) { // True case Char.LowerT: { if (bytes[i++] !== Char.LowerR || bytes[i++] !== Char.LowerU || bytes[i++] !== Char.LowerE) { throwSyntaxError(bytes, --i) } value = true break } // False case Char.LowerF: { if (bytes[i++] !== Char.LowerA || bytes[i++] !== Char.LowerL || bytes[i++] !== Char.LowerS || bytes[i++] !== Char.LowerE) { throwSyntaxError(bytes, --i) } value = false break } // Null case Char.LowerN: { if (bytes[i++] !== Char.LowerU || bytes[i++] !== Char.LowerL || bytes[i++] !== Char.LowerL) { throwSyntaxError(bytes, --i) } value = null break } // Number begin case Char.Minus: case Char.Dot: case Char.Digit0: case Char.Digit1: case Char.Digit2: case Char.Digit3: case Char.Digit4: case Char.Digit5: case Char.Digit6: case Char.Digit7: case Char.Digit8: case Char.Digit9: { let index = i value = fromCharCode(c) c = bytes[i] // Scan over the rest of the number while (true) { switch (c) { case Char.Plus: case Char.Minus: case Char.Dot: case Char.Digit0: case Char.Digit1: case Char.Digit2: case Char.Digit3: case Char.Digit4: case Char.Digit5: case Char.Digit6: case Char.Digit7: case Char.Digit8: case Char.Digit9: case Char.LowerE: case Char.UpperE: { value += fromCharCode(c) c = bytes[++i] continue } } // Number end break } // Convert the string to a number value = +value // Validate the number if (isNaN(value)) { throwSyntaxError(bytes, --index, 'Invalid number') } break } // String begin case Char.Quote: { value = '' while (true) { if (i >= length) { throwSyntaxError(bytes, length) } c = bytes[i++] // String end if (c === Char.Quote) { break } // Escape sequence else if (c === Char.Backslash) { switch (bytes[i++]) { // Normal escape sequence case Char.Quote: value += '\"'; break case Char.Slash: value += '\/'; break case Char.Backslash: value += '\\'; break case Char.LowerB: value += '\b'; break case Char.LowerF: value += '\f'; break case Char.LowerN: value += '\n'; break case Char.LowerR: value += '\r'; break case Char.LowerT: value += '\t'; break // Unicode escape sequence case Char.LowerU: { let code = 0 for (let j = 0; j < 4; j++) { c = bytes[i++] code <<= 4 if (c >= Char.Digit0 && c <= Char.Digit9) code |= c - Char.Digit0 else if (c >= Char.LowerA && c <= Char.LowerF) code |= c + (10 - Char.LowerA) else if (c >= Char.UpperA && c <= Char.UpperF) code |= c + (10 - Char.UpperA) else throwSyntaxError(bytes, --i) } value += fromCharCode(code) break } // Invalid escape sequence default: throwSyntaxError(bytes, --i); break } } // ASCII text else if (c <= 0x7F) { value += fromCharCode(c) } // 2-byte UTF-8 sequence else if ((c & 0xE0) === 0xC0) { value += fromCharCode(((c & 0x1F) << 6) | (bytes[i++] & 0x3F)) } // 3-byte UTF-8 sequence else if ((c & 0xF0) === 0xE0) { value += fromCharCode(((c & 0x0F) << 12) | ((bytes[i++] & 0x3F) << 6) | (bytes[i++] & 0x3F)) } // 4-byte UTF-8 sequence else if ((c & 0xF8) == 0xF0) { let codePoint = ((c & 0x07) << 18) | ((bytes[i++] & 0x3F) << 12) | ((bytes[i++] & 0x3F) << 6) | (bytes[i++] & 0x3F) if (codePoint > 0xFFFF) { codePoint -= 0x10000 value += fromCharCode(((codePoint >> 10) & 0x3FF) | 0xD800) codePoint = 0xDC00 | (codePoint & 0x3FF) } value += fromCharCode(codePoint) } } // Force V8's rope representation to be flattened to compact the string and avoid running out of memory value[0] break } // Array begin case Char.OpenBracket: { value = [] // Push the stack propertyStack.push(property) objectStack.push(object) stateStack.push(state) // Enter the array property = null object = value state = State.Array continue } // Object begin case Char.OpenBrace: { value = {} // Push the stack propertyStack.push(property) objectStack.push(object) stateStack.push(state) // Enter the object property = null object = value state = State.Object continue } // Array end case Char.CloseBracket: { if (state !== State.Array) { throwSyntaxError(bytes, --i) } // Leave the array value = object // Pop the stack property = propertyStack.pop() as string | null object = objectStack.pop() state = stateStack.pop() as State break } // Object end case Char.CloseBrace: { if (state !== State.Object) { throwSyntaxError(bytes, --i) } // Leave the object value = object // Pop the stack property = propertyStack.pop() as string | null object = objectStack.pop() state = stateStack.pop() as State break } default: { throwSyntaxError(bytes, --i) } } c = bytes[i] // Skip whitespace while (c <= Char.Space) { c = bytes[++i] } switch (state) { case State.TopLevel: { // Expect the end of the input if (i === length) { return value } break } case State.Array: { object.push(value) // Check for more values if (c === Char.Comma) { i++ continue } // Expect the end of the array if (c === Char.CloseBracket) { continue } break } case State.Object: { // Property if (property === null) { property = value // Expect a colon if (c === Char.Colon) { i++ continue } } // Value else { object[property] = value property = null // Check for more values if (c === Char.Comma) { i++ continue } // Expect the end of the object if (c === Char.CloseBrace) { continue } } break } } // It's an error if we get here break } throwSyntaxError(bytes, i) }