JSON is great. JSON parsers are not. They punish you for every missing quote and misplaced comma. You're a professional -- you know what you meant. jsonic knows too.
a:1,foo:bar → {"a": 1, "foo": "bar"}
It's a JSON parser that isn't strict. And it's very, very extensible.
Available for TypeScript/JavaScript and Go.
npm install jsonicconst{ Jsonic }=require('jsonic')// Relaxed syntax, just worksJsonic('a:1, b:2')// {"a": 1, "b": 2}Jsonic('x, y, z')// ["x", "y", "z"]Jsonic('{a: {b: 1, c: 2}}')// {"a": {"b": 1, "c": 2}}import{Jsonic}from'jsonic'Jsonic('a:1, b:2')// {"a": 1, "b": 2}More than you'd expect. All of the following parse to {"a": 1, "b": "B"}:
a:1,b:B
a:1
b:B
a:1
// a:2
# a:3
/* b wants
* to B
*/
b:B
{ "a": 100e-2, '\u0062':`\x42`, }
That last one mixes double quotes, single quotes, backticks, unicode escapes, hex escapes, and scientific notation. It doesn't matter. jsonic handles it.
Here's the full set of relaxations:
- Unquoted keys and values:
a:1→{"a": 1} - Implicit top-level object:
a:1,b:2→{"a": 1, "b": 2} - Implicit top-level array:
a,b→["a", "b"] - Trailing commas:
{a:1,b:2,}→{"a": 1, "b": 2} - Single-quoted strings:
'hello'works like"hello" - Backtick strings:
`hello`works like"hello" - Multiline strings: backtick strings preserve newlines
- Indent-adjusted strings:
'''...\n'''trims leading indent - Comments:
//,#(line),/* */(block) - Object merging:
a:{b:1},a:{c:2}→{"a": {"b": 1, "c": 2}} - Path diving:
a:b:1,a:c:2→{"a": {"b": 1, "c": 2}} - All number formats:
1e1 === 0xa === 0o12 === 0b1010, plus1_000separators - Auto-close at EOF: unclosed
{or[close automatically
For the full syntax reference, see doc/syntax.md.
You might be tempted to think a lenient parser is a simple thing. It isn't. jsonic is built around a rule-based parser and a matcher-based lexer. Both are fully customizable through options and plugins. You can change almost anything about how parsing works -- and you don't have to understand the internals to do it.
Let's start simple. Create a configured instance with Jsonic.make():
constlenient=Jsonic.make({comment: {lex: false},// disable commentsnumber: {hex: false},// disable hex numbersvalue: {def: {yes: {val: true},no: {val: false}}}})lenient('yes')// trueOptions compose. You turn things off, you turn things on, you define new value tokens. That's it.
See doc/options.md for the full options reference.
When options aren't enough, plugins let you reach deeper. They can modify the grammar, add matchers, or hook into parse events:
functionmyPlugin(jsonic,options){// Register a custom fixed tokenjsonic.options({fixed: {token: {'#TL': '~'}}})constT_TILDE=jsonic.token('#TL')// Modify grammar rulesjsonic.rule('val',(rs)=>{rs.open([{s: [T_TILDE],a: (rule)=>{rule.node=options.tildeValue??null}}])})}constj=Jsonic.make()j.use(myPlugin,{tildeValue: 42})j('~')// 42Consider what just happened: we invented a new syntax element (~),
told the parser what to do when it encounters one, and wired it up with
a configurable value. The parser itself doesn't care what symbols you
use. It only cares about rules.
See doc/plugins.md for the plugin authoring guide.
See doc/api.md for the full API.
The essentials:
| Function / Property | Description |
|---|---|
Jsonic(src) | Parse a string with default settings |
Jsonic.make(options?) | Create a configured parser instance |
instance.use(plugin, opts?) | Register a plugin |
instance.rule(name, definer) | Modify a grammar rule |
instance.token(ref) | Get or create a token type |
instance.sub({lex?, rule?}) | Subscribe to parse events |
instance.options | Current options |
There's a Go port with the same core parsing behavior. Same syntax, same relaxations, same results. See the Go documentation for installation and usage.
import"github.com/jsonicjs/jsonic/go"result, err:=jsonic.Parse("a:1, b:2")MIT. Copyright (c) Richard Rodger.