Security News
JSR Working Group Kicks Off with Ambitious Roadmap and Plans for Open Governance
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
bass-clarinet
Advanced tools
SAX based evented streaming JSON parser in Typescript (browser and node)
bass-clarinet
is a JSON parser.
It was forked from clarinet
but the API has been changed significantly.
In addition to the port to TypeScript, the following changes have been made:
onopenobject
no longer includes the first keyJSONTestSuite
is added to the test set. All tests pass.trim
and normalize
options have been dropped. This can be handled by the consumer in the onsimplevalue
callbackcreateStackedDataSubscriber
which pairs onopenobject
/oncloseobject
and onopenarray
/onclosearray
events in a callbackattachStictJSONValidator
to the parser):
spaces_per_tab
bass-clarinet
is a sax-like streaming parser for JSON. works in the browser and node.js. just like you shouldn't use sax
when you need dom
you shouldn't use bass-clarinet
when you need JSON.parse
.
Clear reasons to use bass-clarinet
over the built-in JSON.parse
:
options
belowbass-clarinet
is very much like yajl but written in TypeScript:
npm install bass-clarinet
.ts
file: import * as bc from "bass-clarinet"
//a simple pretty printer
import * as p from "pareto"
import * as fs from "fs"
import * as bc from "bass-clarinet"
const [, , path] = process.argv
if (path === undefined) {
console.error("missing path")
process.exit(1)
}
const dataAsString = fs.readFileSync(path, { encoding: "utf-8" })
function createRequiredValuesPrettyPrinter(indentation: string, writer: (str: string) => void): bc.RequiredValueHandler {
return {
onValue: createValuesPrettyPrinter(indentation, writer),
onMissing: () => {
//write out an empty string to fix this missing data?
},
}
}
function createValuesPrettyPrinter(indentation: string, writer: (str: string) => void): bc.OnValue {
return () => {
return {
array: (beginRange, beginMetaData) => {
writer(beginMetaData.openCharacter)
return {
element: () => createValuesPrettyPrinter(`${indentation}\t`, writer),
end: _endRange => {
writer(`${indentation}${bc.printRange(beginRange)}`)
},
}
},
object: (_beginRange, data) => {
writer(data.openCharacter)
return {
property: (_keyRange, key) => {
writer(`${indentation}\t"${key}": `)
return p.value(createRequiredValuesPrettyPrinter(`${indentation}\t`, writer))
},
end: endRange => {
writer(`${indentation}${bc.printRange(endRange)}`)
},
}
},
simpleValue: (_range, data) => {
if (data.quote !== null) {
writer(`${JSON.stringify(data.value)}`)
} else {
writer(`${data.value}`)
}
return p.value(false)
},
taggedUnion: () => {
return {
option: (_range, option) => {
writer(`| "${option}" `)
return createRequiredValuesPrettyPrinter(`${indentation}`, writer)
},
missingOption: () => {
//
},
}
},
}
}
}
export function createPrettyPrinter(indentation: string, writer: (str: string) => void): bc.ParserEventConsumer<null, null> {
const datasubscriber = bc.createStackedDataSubscriber<null, null>(
{
onValue: createValuesPrettyPrinter(indentation, writer),
onMissing: () => {
//
},
},
error => {
console.error("FOUND STACKED DATA ERROR", error.message)
},
() => {
//onEnd
//no need to return an value, we're only here for the side effects, so return 'null'
return p.success(null)
}
)
return datasubscriber
}
const pp = createPrettyPrinter("\r\n", str => process.stdout.write(str))
bc.parseString(
dataAsString,
() => {
return pp
},
() => {
return pp
},
err => { console.error("FOUND ERROR", err) },
() => {
return p.value(false)
},
).handle(
() => {
//we're only here for the side effects, so no need to handle the error
},
() => {
//we're only here for the side effects, so no need to handle the result (which is 'null' anyway)
}
)
import * as p from "pareto"
import * as p20 from "pareto-20"
import * as fs from "fs"
import * as bc from "bass-clarinet"
function assertUnreachable<RT>(_x: never): RT {
throw new Error("unreachable")
}
const [, , path] = process.argv
if (path === undefined) {
console.error("missing path")
process.exit(1)
}
const dataAsString = fs.readFileSync(path, { encoding: "utf-8" })
export const parserEventConsumer: bc.ParserEventConsumer<null, null> = {
onData: data => {
switch (data.type[0]) {
case bc.BodyEventType.CloseArray: {
//const $ = data.type[1]
//place your code here
break
}
case bc.BodyEventType.CloseObject: {
//const $ = data.type[1]
//place your code here
break
}
case bc.BodyEventType.Colon: {
//const $ = data.type[1]
//place your code here
break
}
case bc.BodyEventType.Comma: {
//const $ = data.type[1]
//place your code here
break
}
case bc.BodyEventType.OpenArray: {
//const $ = data.type[1]
//place your code here
break
}
case bc.BodyEventType.OpenObject: {
//const $ = data.type[1]
//place your code here
break
}
case bc.BodyEventType.Overhead: {
const $ = data.type[1]
switch ($.type[0]) {
case bc.OverheadTokenType.BlockComment: {
//const $ = data.type[1]
//place your code here
break
}
case bc.OverheadTokenType.LineComment: {
//const $ = data.type[1]
//place your code here
break
}
case bc.OverheadTokenType.NewLine: {
//const $ = data.type[1]
//place your code here
break
}
case bc.OverheadTokenType.WhiteSpace: {
//const $ = data.type[1]
//place your code here
break
}
default:
assertUnreachable($.type[0])
}
break
}
case bc.BodyEventType.SimpleValue: {
//const $ = data.type[1]
//place your code here
//in strict JSON, the value is a string, a number, null, true or false
break
}
case bc.BodyEventType.TaggedUnion: {
//const $ = data.type[1]
//place your code here
break
}
default:
assertUnreachable(data.type[0])
}
return p.value(false)
},
onEnd: () => {
//place your code here
return p.success(null)
},
}
const parserStack = bc.createParserStack(
() => {
return parserEventConsumer
},
() => {
return parserEventConsumer
},
err => { console.error("FOUND ERROR", err) },
() => {
return p.value(false)
}
)
p20.createArray([dataAsString]).streamify().handle(
null,
parserStack
)
pass the following argument to the tokenizer function:
spaces_per_tab
- number. needed for proper column info.: Rationale: without knowing how many spaces per tab base-clarinet
is not able to determine the colomn of a character. Default is 4
(ofcourse)pass the following arguments to the parser function. all are optional.
opt
- object bag of settings.
write
- write bytes to the tokenizer. you don't have to do this all at
once. you can keep writing as much as you want.
end
- ends the stream. once ended, no more data may be written, it signals the onend
event.
the parser supports the following additional (to JSON) features
}
or the ]
. Rationale: for serializers it is easier to write a comma for every property/element instead of keeping a state that tracks if a property/element is the first one.//
and block comments /* */
. Rationale: when using JSON-like documents for editing, it is often useful to add comments'
in place of "
. Rationale: In an editor this is less intrusive (although only slightly)<
and >
in place of [
and ]
. Rationale: a semantic distinction can be made between fixed length arrays (ArrayType
) and variable length arrays (lists
)(
and )
in place of {
and }
. Rationale: a semantic distinction can be made between objctes with known properties (Type
) and objects with dynamic keys (dictionary
)!
followed by a value (object
, string
etc), followed by an optional #
(indicating compact
).
compact
is an indicator for a processor (code that uses bass-clarinet
's API) that the data is compact
. base-clarinet
only sends the compact
flag but does not change any other behaviour. Rationale: If a schema is known, the keys of a Type
are known at design time. these types can therefor be converted to ArrayTypes
and thus omit the keys without losing information. This trades in readability in favor of size. This option indicates that this happened in this document. The file can only be properly interpreted by a processor in combination with the schema.| "the chosen option" { "my data": "foo" }
. The same information can ofcourse also be written in strict JSON with an array with 2 elements of which the first element is a string.onerror
(passed as argument to the constructor) - indication that something bad happened. The parser will continue as good as it can
the data subscriber can be seen in the example code above
The stack consists of the following chain: Stream -(string chunks)-> PreTokenizer -(PreToken's)-> Tokenizer -(Token's)-> Parser -(BodyEvent)-> ParserEventConsumer -(Resulting Type)-> ...
PreTokens are low level token parts. For example BlockCommentBegin
Tokens are higher level. For example BlockComment
an example of a BodyEvent is OpenArray
check issues
everyone is welcome to contribute. patches, bug-fixes, new features
bass-clarinet
git checkout -b my_branch
git push origin my_branch
git clone git://github.com/corno/bass-clarinet.git
FAQs
Unknown package
The npm package bass-clarinet receives a total of 0 weekly downloads. As such, bass-clarinet popularity was classified as not popular.
We found that bass-clarinet demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
Security News
Research
An advanced npm supply chain attack is leveraging Ethereum smart contracts for decentralized, persistent malware control, evading traditional defenses.
Security News
Research
Attackers are impersonating Sindre Sorhus on npm with a fake 'chalk-node' package containing a malicious backdoor to compromise developers' projects.