# `avril:fjson` fJSON ("friendly JSON") is a JSON superset object notation used for the avril configuration and lockfile formats. unlike JSON, it's well-suited to being a human-authored configuration file format it is essentially JSON, except: - the root of a document may be an "implicit map" (i.e. imaginary `{ }`) - simple ASCII identifier keys may be unquoted in maps - trailing commas are permitted in maps and arrays - commas can be replaced by newlines in maps and arrays - strings may be single-quoted - documents may contain `// comment`s an fJSON document can look like this: ``` name: "@me/my-library" version: "0.1.0" description: 'Implements the "myLibrary" protocol.' deps: [ // we use the REST client for interacting with myLibrary servers "./lib/rest-client/" "./lib/schema-generation/" ] ``` the `avril:fjson` built-in library implements the following operations: - `parse(source: string): unknown`: parse an fJSON document into a JavaScript value - an empty or comment-only document will become `{}` - `stringify(value: unknown): string`: convert a JavaScript value into a pretty-printed fJSON document - two-space indentation - will wrap; targets an 80-column width - no final newline - `apply(source: string, ...overrides): string`: update an fJSON map document's properties in-place, preserving comments and ordering. see below - replaced strings retain their choice of quote mark - invalid overrides throw `TypeError` - non-own+enumerable props are ignored in overrides - best-effort: property deletion may leave residual whitespace/blank lines/floating comments ## fJSON.apply(…) `apply` allows you to programmatically modify an fJSON document without unintentionally losing any information that can't be represented in a `parse` -> `stringify` round-trip: for instance, comments don't make it into the data returned by `parse`, since they're only lexically- and not semantically-present in the document. `apply` takes a source document and any number of object updates, which will be applied left-to-right. object updates are plain objects (à la Object.assign), but can contain `undefined` to remove properties from the target. additionally, object updates will also drill down into nested objects that are present instead of replacing them. it follows that you can replace a nested map by first setting it to `undefined` and then setting it to the new map: ```typescript import * as fJSON from "avril:fjson"; fJSON.apply("x: 0, y: { a: 1, b: 2 }", { y: { c: 3 } }) // => "x: 0, y: { a: 1, b: 2, c: 3 }" fJSON.apply( "x: 0, y: { a: 1, b: 2 }", { y: undefined }, { y: { c: 3 } }, ) // => "x: 0, y: { c: 3 }" ```