# API

The Jsonic API is deliberately kept as small as possible.

TIP

If all you want to do is parse easy-going JSON, you don't need this API! Just call Jsonic(...your-json-source...) and use the return value.

This API is for customizing the JSON parser, so if that is your game, read on...

The default import Jsonic is intended as utility function and to be used as-is. For customization, and to access the API methods, create a new Jsonic instance with Jsonic.make().

Some plugins may decorate the main Jsonic object with additional methods.

  •      🍓 🍐 Jsonic
  •           🍐 id
  •      🍓      toString
  •      🍓      make
  • 🍒 🍓 🍐 options
  • 🍒 🍓      use
  • 🍒 🍓      rule
  • 🍒 🍓      lex
  • 🍒 🍓 🍐 token
(🍒 available after Jsonic.make(), 🍓 method or function, 🍐 property or set of properties)



# Methods

# 🍓 🍐 Jsonic: Just parse already!

Jsonic(source: string, meta?: object): any

Returns: any Object (or value) containing the parsed JSON data.

  • source: string required : The JSON source text to parse.
  • meta?: object optional : Provide meta data for this parse.

This is the top level utility function that parses JSON source text using the standard syntax extensions of Jsonic. It cannot be customized (call Jsonic.make() to do that), and always behaves the same way.

  • 🍓 Function: Jsonic(...)
  • 🍐 Property set: jsonic.id

# ✨ Example

let earth = Jsonic('name: Terra, moons: [{name: Luna}]')

The earth variable now contains the following data:

// earth ->
{
  "name": "Terra",
  "moons": [
    {
      "name": "Luna"
    }
  ]
}

# ✨ Example: using the meta parameter

let one = Jsonic('1', {log:-1}) // one === 1

The meta value of {log:-1} prints a debug log of the lexing and parsing process to STDOUT. Very useful when you are writing a plugin! See the plugin section for more details.

lex	#NR	"1"	1	0:1	@LTP
lex	#ZZ		1	0:1	@LTP
rule	val/1	open	0	0	[#NR #ZZ]	["1" ]
parse	val/1	open	alt=10	[#NR]	0	p=	r=	b=	#NR	["1"]	c:	n:
lex	#ZZ		1	0:1	@LTP
node	val/1	open	w=Z	
stack	0	
rule	val/1	close	0	1	[#ZZ #ZZ]	[ ]
parse	val/1	close	alt=2	[#AA]	1	p=	r=	b=1	#ZZ	[null]	c:	n:
node	val/1	close	w=O	1
stack	0

Apart from log, the meta object contains plugin-specific parameters. See your friendly neighbourhood plugin documentation for more.




# 🍐 id: Unique instance identifier

.id: string

It's useful to be able to identify unique instances when you're debugging.

Use the tag option to set a custom tag for the instance.

  • 🍐 Property: jsonic.id

# ✨ Example

// format: Jsonic/<Date.now()>/<Math.random()[2:8]>/<options.tag>
Jsonic.id // === 'Jsonic/1614085850042/022636/-'
Jsonic.make({tag:'foo'}).id // === 'Jsonic/1614085851902/375149/foo'




# 🍓 toString: String description of the Jsonic instance

.toString(): string

Returns the value of the .id property as the string description of the instance.

  • 🍓 Method: jsonic.toString()

# ✨ Example

// format: Jsonic/<Date.now()>/<Math.random()[2:8]>/<options.tag>
''+Jsonic // === 'Jsonic/1614085850042/022636/-'
''+(Jsonic.make({tag:'foo'})) // === 'Jsonic/1614085851902/375149/foo'




# 🍓 make: Create a new customizable Jsonic instance.

.make(options?: object): Jsonic

Returns: Jsonic A new Jsonic instance that can be modified.

  • options?: object optional : Partial options tree.

The returned function can be used in the same way as the top level Jsonic function. It also exposes the rest of the API (such as options) so you can customize the parser.

  • 🍓 Method: jsonic.make(...)

# ✨ Example

let array_of_numbers = Jsonic('1,2,3') 
// array_of_numbers === [1, 2, 3]

let no_numbers_please = Jsonic.make({number: {lex: false}})
let array_of_strings = no_numbers_please('1,2,3') 
// array_of_strings === ['1', '2', '3']

You must call make to customize Jsonic. This protects the Jsonic import which is a shared global object.

Calling make again on the new Jsonic instance will generate another new instance that inherits the configuration of its parent and can itself be independently customized. Which is what you want.

# ✨ Example: child instances inherit from parent instances

let no_numbers_please = Jsonic.make({number: {lex: false}})
no_numbers_please('1,2,3') // === ['1', '2', '3'] as before

let pipe_separated = no_numbers_please.make({token: {'#CA':{c:'|'}}})
pipe_separated('1|2|3') // === ['1', '2', '3'], but:
pipe_separated('1,2,3') // === '1,2,3' !!!

To understand how the token option works, and all the other options, see the Options section.




# 🍓 🍐 options: Get and set options for a Jsonic instance.

.options(options: object): object

Returns: object merged object containing the full option tree

  • options: object required : Partial options tree.
  • 🍒 Only available on: jsonic = Jsonic.make()
  • 🍓 Method: jsonic.options(...)
  • 🍐 Property set of option tree: jsonic.options.number.lex

# ✨ Example

let jsonic = Jsonic.make()

jsonic.options().comment.lex // === true
jsonic.options.comment.lex // === true - as a convenience

let no_comment = Jsonic.make()
no_comment.options({comment: {lex: false}})

// Returns {"a": 1, "#b": 2}
no_comment(`
  a: 1
  #b: 2
`)

// Whereas this returns only {"a": 1} as # starts a one line comment
Jsonic(`
  a: 1
  #b: 2
`)




# 🍒 🍓 use: Register a plugin.

.use(plugin: function, plugin_options?: object): Jsonic

Returns: Jsonic instance (this allows chaining)

  • plugin: function required : Plugin definition function
    • (jsonic: Jsonic) => Jsonic
  • plugin_options?: object optional : Plugin-specific options
  • 🍒 Only available on: jsonic = Jsonic.make()
  • 🍓 Method: jsonic.use(...)

# ✨ Example

let jsonic = Jsonic.make().use(function piper(jsonic) {
  jsonic.options({token: {'#CA':{c:'|'}}})
})
jsonic('a|b|c') // === ['a', 'b', 'c']

Plugins are defined by a function that takes the Jsonic instance as a first parameter, and then changes the options and parsing rules of that instance. For more, see the plugin writing guide.

# ✨ Example: plugin options

function sepper(jsonic) {
  let sep = jsonic.options.plugin.sepper.sep
  jsonic.options({token: {'#CA':{c:sep}}})
}

let jsonic = Jsonic.make().use(sepper, {sep:';'})
jsonic('a;b;c') // === ['a', 'b', 'c']

Plugin options are added to the main options under the plugin key using the name of the plugin function as a sub-key. Thus function sepper(...) means that jsonic.options.plugin.sepper contains the plugin option.

Notice that you can refer to options directly as properties of the .options method, as a convenience.

# ✨ Example: plugin chaining

When defining a custom Jsonic instance, you'll probably be registering multiple plugins. The .use method can be chained to make this easier.

function foo(jsonic) {
  jsonic.foo = function() {
    return 1
  }
}
function bar(jsonic) {
  jsonic.bar = function() {
    return this.foo() * 2
  }
}
let jsonic = Jsonic.make()
    .use(foo)
    .use(bar)
// jsonic.foo() === 1
// jsonic.bar() === 2




# 🍒 🍓 rule: Define or modify a parser rule.

.rule(name?: string, define?: function): RuleSpec

Returns: RuleSpec Rule specification

  • name?: string optional : Rule name
  • define?: function optional : Rule definition function
    • (rs: RuleSpec, rsm: RuleSpecMap) => RuleSpec

The .rule method (and the .lex and .token) methods ar intended mostly for use inside plugin definitions. They allow you to modify the way that Jsonic works.

The .rule method takes the name of a rule and if it exists, provides the rule specification as first parameter to the rule definition function. If the rule does not exist, you can create a new rule specification and return that to define a new rule.

The details of rule definition are covered in the Plugins section.

  • 🍒 Only available on: jsonic = Jsonic.make()
  • 🍓 Method: jsonic.rule(...)

# 🍒 ✨ Example

let concat = Jsonic.make()

// Get all the rules
Object.keys(concat.rule()) // === ['val', 'map', 'list', 'pair', 'elem']

// Get a rule by name
let val_rule = jsonic.rule('val') // val_rule.name === 'val'

// Modify a rule 
let ST = concat.token.ST
concat.rule('val',(rule)=>{
  // Concatentate strings (ST) instead of forming array elements
  rule.def.open.unshift({s:[ST,ST], h:(alt,rule,ctx)=>{
    rule.node = ctx.t0.val + ctx.t1.val
    // Disable default value handling
    rule.bc = false
  }})
})

concat('"a" "b"') // === 'ab'
Jsonic('"a" "b"') // === ['a', 'b']

// Create a new rule (for a new token)
concat.options({
  token: { '#HH': {c:'%'} }
})

let HH = concat.token.HH
concat.rule('hundred', ()=>{
  return new RuleSpec({
    after_open: (rule)=>{
      // % always becomes the value 100
      rule.node = 100
    }
  })
})
concat.rule('val', (rulespec)=>{
  rulespec.def.open.unshift({s:[HH], p:'hundred'})
})

concat('{x:1, y:%}') // === {x:1, y:100}




# 🍒 🍓 lex: Define a lex matcher.

.lex(state?: Tin, match?: function): LexMatcher[]

Returns: LexMatcher[] Ordered list of lex matchers for this lex state.

  • state?: Tin optional : Token identifier number
  • matcher?: function optional : Lex matcher function
    • (state: LexMatcherState) => LexMatcherResult

The .lex method (like the .rule and .token methods) allows you to change the way that Jsonic works. The .lex method attaches a matcher function to a given lex state. This matcher has the opportunity to examine the current source text position and generate a token, or pass lexing over to the standard machinery.

The Jsonic is state based, although most of the normal lexing happens in the top lex state (LTP).

For more about lex matchers, see the Plugins section.

  • 🍒 Only available on: jsonic = Jsonic.make()
  • 🍓 Method: jsonic.lex(...)

# ✨ Example

let tens = Jsonic.make()
let VL = tens.token.VL
let LTP = tens.token.LTP

// Match characters in the top lex state (LTP)
tens.lex(LTP, function tens_matcher(state) {

  // % -> 10, %% -> 20, %%% -> 30, etc.
  let marks = state.src.substring(state.sI).match(/^%+/)
  if(marks) {
    let len = marks[0].length
    state.token.tin = VL
    state.token.val = 10 * len

    // Update lexer position and column
    return {
      sI: state.sI + len,
      cI: state.cI + len
    }
  }
})

tens('a:1,b:%%,c:[%%%%]') // === {a:1, b:20, c:[40]}




# 🍒 🍓 🍐 token: Resolve a token by name or index.

.token(ref: Tin | string): string | Tin

Returns: string | Tin Token identifier or token name (opposite of ref type).

  • ref: Tin | string required : Token identifier number or name

The .token method lets you get the unique token identification number (Tin) of a named token in the current Jsonic instance, or lookup the name of a token by its Tin.

As lexer states must also be unique, they are generated as pseudo-tokens using the same index of tokens. While child Jsonic instances (generated with .make) will inherit the index of their parents, in general token identification is usable only for a specific Jsonic instance.

  • 🍒 Only available on: jsonic = Jsonic.make()
  • 🍓 Method: jsonic.token(...)
  • 🍐 Property set of token names and Tins: jsonic.token.NR

# ✨ Example

let jsonic = Jsonic.make()
jsonic.token.ST // === 11, String token identification number
jsonic.token(11) // === '#ST', String token name
jsonic.token('#ST') // === 11, String token name