char/fjson
friendly JSON implementation in TypeScript
git clone https://git.t4t.associates/char/fjson
17b13b5
main
1import { FjsonError , type Span } from "./errors.ts" ; 2import { type Options , resolveMaxDepth } from "./options.ts" ; 3import { Scanner , type Token } from "./scan.ts" ; 4 5/** any JSON data */ 6export type JsonValue = 7| null 8| boolean 9| number 10| string 11| JsonValue [] 12| { [ key :string ] :JsonValue }; 13 14export type JsonObject = { [ key :string ] :JsonValue }; 15 16export interface NullNode { 17readonly kind :"null" ; 18readonly span :Span ; 19} 20export interface BoolNode { 21readonly kind :"boolean" ; 22readonly value :boolean ; 23readonly span :Span ; 24} 25export interface NumberNode { 26readonly kind :"number" ; 27readonly value :number ; 28readonly raw :string ; 29readonly span :Span ; 30} 31export interface StringNode { 32readonly kind :"string" ; 33readonly value :string ; 34readonly span :Span ; 35} 36export interface ArrayNode { 37readonly kind :"array" ; 38readonly items :SyntaxNode []; 39readonly span :Span ; 40} 41export interface ObjectMember { 42readonly key :string ; 43readonly keySpan :Span ; 44readonly value :SyntaxNode ; 45} 46export interface ObjectNode { 47readonly kind :"object" ; 48readonly members :ObjectMember []; 49readonly span :Span ; 50} 51 52/** spanned AST types */ 53export type SyntaxNode = 54| NullNode 55| BoolNode 56| NumberNode 57| StringNode 58| ArrayNode 59| ObjectNode ; 60 61function defineKey ( object :JsonObject , key :string , value :JsonValue ) :void { 62// use defineProperty to avoid prototype's setter 63Object . defineProperty ( object , key , { 64 value, 65enumerable :true , 66writable :true , 67configurable :true , 68}); 69} 70 71/** drop span info & convert */ 72export function nodeToValue ( node :SyntaxNode ) :JsonValue { 73switch ( node . kind ) { 74case "null" : 75return null ; 76case "boolean" : 77case "number" : 78case "string" : 79return node . value ; 80case "array" : 81return node . items . map ( nodeToValue ); 82case "object" :{ 83const object :JsonObject = {}; 84for ( const member of node . members ) { 85defineKey ( object , member . key , nodeToValue ( member . value )); 86} 87return object ; 88} 89} 90} 91 92class Parser { 93readonly #scanner:Scanner ; 94readonly #maxDepth:number ; 95 96constructor ( source :string , maxDepth :number ) { 97this . #scanner= new Scanner ( source ); 98this . #maxDepth= maxDepth ; 99} 100 101parseRoot () :SyntaxNode { 102const token = this . #scanner. peek (); 103const implicit = token . kind === "eof" || 104(( token . kind === "string" || token . kind === "identifier" ) && 105this . #scanner. peek ( 1 ). kind === ":" ); 106const value = implicit ?this . #object( false , 0 ) :this . #value( 0 ); 107if ( this . #scanner. peek (). kind !== "eof" ) { 108throw this . #error( "Unexpected content after the root value" ); 109} 110return value ; 111} 112 113parseObjectRoot () :ObjectNode { 114const value = this . #object( this . #scanner. peek (). kind === "{" , 0 ); 115if ( this . #scanner. peek (). kind !== "eof" ) { 116throw this . #error( "Unexpected content after the root object" ); 117} 118return value ; 119} 120 121 #error( message :string ) :FjsonError { 122const { start, end} = this . #scanner. peek (). span ; 123return new FjsonError ( "syntax" , message , { 124 start, 125end :Math . min ( start + 1 , end ), 126}); 127} 128 129 #expect( kind :Token [ "kind" ]) :Token { 130if ( this . #scanner. peek (). kind !== kind ) { 131throw this . #error( `Expected \` ${ kind } \`` ); 132} 133return this . #scanner. take (); 134} 135 136 #atEnd( closing :"}" | "]" | "eof" ) :boolean { 137const token = this . #scanner. peek (); 138if ( token . kind === closing ) return true ; 139if ( token . kind === "eof" ) throw this . #error( `Expected \` ${ closing } \`` ); 140return false ; 141} 142 143 #separator( closing :"}" | "]" | "eof" ) :void { 144if ( this . #atEnd( closing )) return ; 145const token = this . #scanner. peek (); 146if ( token . kind === "," ) { 147this . #scanner. take (); 148} else if ( ! token . newlineBefore ) { 149throw this . #error( "Expected `,` or newline" ); 150} 151} 152 153 #object( braced :boolean , depth :number ) :ObjectNode { 154const start = this . #scanner. peek (). span . start ; 155if ( braced ) this . #expect( "{" ); 156const closing = braced ?"}" :"eof" ; 157const members :ObjectMember [] = []; 158const keys = new Map < string , Span >(); 159let end = start ; 160while ( ! this . #atEnd( closing )) { 161const token = this . #scanner. peek (); 162if ( token . kind !== "string" && token . kind !== "identifier" ) { 163throw this . #error( "Expected an object key" ); 164} 165this . #scanner. take (); 166const key = token . value ; 167const keySpan = token . span ; 168const previous = keys . get ( key ); 169if ( previous !== undefined ) { 170throw new FjsonError ( 171"syntax" , 172`Duplicate key ${ JSON . stringify ( key )} ` , 173keySpan , 174previous , 175); 176} 177keys . set ( key , keySpan ); 178this . #expect( ":" ); 179const value = this . #value( depth + 1 ); 180members . push ({ key, keySpan, value}); 181end = value . span . end ; 182this . #separator( closing ); 183} 184if ( braced ) end = this . #expect( "}" ). span . end ; 185return { kind :"object" , members, span :{ start, end} }; 186} 187 188 #array( depth :number ) :ArrayNode { 189const start = this . #expect( "[" ). span . start ; 190const items :SyntaxNode [] = []; 191while ( ! this . #atEnd( "]" )) { 192items . push ( this . #value( depth + 1 )); 193this . #separator( "]" ); 194} 195const end = this . #expect( "]" ). span . end ; 196return { kind :"array" , items, span :{ start, end} }; 197} 198 199 #value( depth :number ) :SyntaxNode { 200const token = this . #scanner. peek (); 201const { span} = token ; 202switch ( token . kind ) { 203case "{" : 204case "[" : 205if ( depth > this . #maxDepth) { 206throw new FjsonError ( 207"nesting" , 208`fJSON nesting exceeds ${ this . #maxDepth } levels below the root` , 209span , 210); 211} 212return token . kind === "{" 213 ?this . #object( true , depth ) 214 :this . #array( depth ); 215case "string" : 216this . #scanner. take (); 217return { kind :"string" , value :token . value , span}; 218case "number" : 219this . #scanner. take (); 220return { kind :"number" , value :token . value , raw :token . raw , span}; 221case "identifier" : 222this . #scanner. take (); 223if ( token . value === "true" || token . value === "false" ) { 224return { kind :"boolean" , value :token . value === "true" , span}; 225} 226if ( token . value === "null" ) return { kind :"null" , span}; 227throw new FjsonError ( 228"syntax" , 229`Unknown value ${ 230JSON . stringify ( token . value ) 231} ; strings must be quoted` , 232span , 233); 234default : 235throw this . #error( "Expected a value" ); 236} 237} 238} 239 240/** parse document into AST */ 241export function parseTree ( source :string , options ?:Options ) :SyntaxNode { 242return new Parser ( source , resolveMaxDepth ( options )). parseRoot (); 243} 244 245/** parse document into object node (throws if wrong type) */ 246export function parseObjectTree ( source :string , options ?:Options ) :ObjectNode { 247return new Parser ( source , resolveMaxDepth ( options )). parseObjectRoot (); 248} 249 250/** safe unknown -> JsonValue conversion */ 251export function toJsonValue ( value :unknown , options ?:Options ) :JsonValue { 252let json :string | undefined ; 253try { 254json = JSON . stringify ( value ); 255} catch { 256throw new TypeError ( "Value is not JSON serializable" ); 257} 258if ( json === undefined ) { 259throw new TypeError ( "Value is not JSON serializable" ); 260} 261return parse ( json , options ); 262} 263 264/** 265* parse an fJSON document. 266* empty or comment-only documents return `{}` 267*/ 268export function parse ( source :string , options ?:Options ) :JsonValue { 269return nodeToValue ( parseTree ( source , options )); 270} 271 272/** parse an fJSON document which **must** be an object */ 273export function parseObject ( source :string , options ?:Options ) :JsonObject { 274const object :JsonObject = {}; 275for ( const member of parseObjectTree ( source , options ). members ) { 276defineKey ( object , member . key , nodeToValue ( member . value )); 277} 278return object ; 279}