Skip to content

Commit

Permalink
feat(core): initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
grantila committed Apr 24, 2022
1 parent 5c8f79a commit 23aca83
Show file tree
Hide file tree
Showing 22 changed files with 2,076 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
@@ -0,0 +1,4 @@
node_modules
coverage
dist
yarn*
96 changes: 96 additions & 0 deletions README.md
@@ -0,0 +1,96 @@
[![npm version][npm-image]][npm-url]
[![downloads][downloads-image]][npm-url]
[![build status][build-image]][build-url]
[![coverage status][coverage-image]][coverage-url]
[![Language grade: JavaScript][lgtm-image]][lgtm-url]


# edit-json

Edit a _textual_ JSON (not a JavaScript object) for a minimal diff, either programatically or by applying a [_JSON Patch_ (RFC6902)](https://www.rfc-editor.org/rfc/rfc6902).

When serializing the result down to a string, it'll resemble the source JSON as much as possible with regards to property order, whitespace (indentation) and _flow types_ (arrays and objects on one line).

Editing JSON is easy, just `JSON.parse()` and play around, then `JSON.stringify()`. To apply a _JSON Patch_, there are [several](https://www.npmjs.com/package/fast-json-patch) [packages](https://www.npmjs.com/package/rfc6902) [out](https://www.npmjs.com/package/json-bigint-patch) [there](https://www.npmjs.com/package/jsonpatch).

This package focuses not on working with JSON as a JavaScript object, but as its textual representation. The package parses the JSON string (as e.g. from a file) as tokens, builds up a logical representation of it, and then applies transformations to that representation. Whitespace (tabs, spaces) as well as multi-line or single-line arrays/objects are remembered.

To do the same with YAML, check out [yaml-diff-patch](https://www.npmjs.com/package/yaml-diff-patch).


# Example

Given:

```json
{
"x": "non-alphanumerically ordered properties, obviously",
"foo": [ "same", "line", "array" ],
"bar": {
"some": "object"
}
}
```

Applying the JSON Patch:

```json
[ {
"op": "move",
"from": "/foo",
"path": "/bar/herenow"
} ]
```

Produces:

```json
{
"x": "non-alphanumerically ordered properties, obviously",
"bar": {
"herenow": [ "same", "line", "array" ],
"some": "object"
}
}
```

Properties aren't re-ordered ("x" is still first), but by default, it will try to _insert_ properties orderly, such as when creating "herenow" in "bar". It'll be added before "some", "h" < "s". This is done with a best effort, since it's not always possible (the object might have unordered properties).

Note also that the array is not split into multiple lines, which would happen with default `JSON.stringify` (unless the whole document is one line of course). The source format is kept if possible.


# Install

`npm i edit-json` or `yarn add edit-json`

This is a [pure ESM][pure-esm] package, and requires Node.js >=14.13.1


# Simple usage

### Exports

The package exports `parseJson` (to be documented) and `jsonPatch`.

### Definition

`jsonPatch( json: string, operations: Operations[], options: Options ): string`

Applies a list of _JSON Patch_ operations to the source `json` and returns the new json string.

The options are:

- `whitespace` ('auto' | 'tabs' | number): Specifies whitespace strategy. Defaults to 'auto'. Force tabs using 'tabs' or spaces using number (e.g. 2 or 4).
- `ordered` (boolean): Try to insert new properties in order.


[npm-image]: https://img.shields.io/npm/v/edit-json.svg
[npm-url]: https://npmjs.org/package/edit-json
[downloads-image]: https://img.shields.io/npm/dm/edit-json.svg
[build-image]: https://img.shields.io/github/workflow/status/grantila/edit-json/Master.svg
[build-url]: https://github.com/grantila/edit-json/actions?query=workflow%3AMaster
[coverage-image]: https://coveralls.io/repos/github/grantila/edit-json/badge.svg?branch=master
[coverage-url]: https://coveralls.io/github/grantila/edit-json?branch=master
[lgtm-image]: https://img.shields.io/lgtm/grade/javascript/g/grantila/edit-json.svg?logo=lgtm&logoWidth=18
[lgtm-url]: https://lgtm.com/projects/g/grantila/edit-json/context:javascript
[pure-esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
14 changes: 14 additions & 0 deletions babel.config.cjs
@@ -0,0 +1,14 @@
module.exports = {
presets: [
[
'@babel/preset-env',
{
modules: false,
targets: {
node: 'current',
},
},
],
'@babel/preset-typescript',
],
}
13 changes: 13 additions & 0 deletions jest.config.js
@@ -0,0 +1,13 @@
export default {
resolver: 'ts-jest-resolver',
testEnvironment: 'node',
testMatch: [
'<rootDir>/lib/**/*.test.ts',
],
modulePathIgnorePatterns: [],
collectCoverageFrom: ['<rootDir>/lib/**/*.ts', 'index.ts'],
coveragePathIgnorePatterns: [ '/node_modules/' ],
coverageReporters: ['lcov', 'text', 'html'],
collectCoverage: true,
extensionsToTreatAsEsm: ['.ts'],
}
105 changes: 105 additions & 0 deletions lib/document/document.test.ts
@@ -0,0 +1,105 @@
import { fileURLToPath } from 'node:url'
import { resolve as resolvePath, dirname } from 'node:path'
import { readFile } from 'node:fs/promises'

import { parseJson } from './document.js'


const __dirname = dirname( fileURLToPath( import.meta.url ) );
const rootDir = resolvePath( __dirname, '..', '..' );

describe( 'document', ( ) =>
{
describe( 'primitives', ( ) =>
{
it( 'null', ( ) =>
{
const parsed = parseJson( 'null' );

expect( parsed.toJSON( ) ).toBe( 'null' );
} );

it( 'boolean', ( ) =>
{
const parsed = parseJson( 'false' );

expect( parsed.toJSON( ) ).toBe( 'false' );
} );

it( 'boolean with whitespace', ( ) =>
{
const parsed = parseJson( ' true ' );

expect( parsed.toJSON( ) ).toBe( ' true' );
} );

it( 'string', ( ) =>
{
const parsed = parseJson( ' "foo bar\\nnext line"' );

expect( parsed.toJSON( ) ).toBe( ' "foo bar\\nnext line"' );
} );

it( 'number pos', ( ) =>
{
const parsed = parseJson( '3.14' );

expect( parsed.toJSON( ) ).toBe( '3.14' );
} );

it( 'number neg', ( ) =>
{
const parsed = parseJson( '-3.14' );

expect( parsed.toJSON( ) ).toBe( '-3.14' );
} );

it( 'number sci pos', ( ) =>
{
const parsed = parseJson( '123e5' );

expect( parsed.toJSON( ) ).toBe( '123e5' );
} );

it( 'number sci neg', ( ) =>
{
const parsed = parseJson( '-123e5' );

expect( parsed.toJSON( ) ).toBe( '-123e5' );
} );
} );

describe( 'objects', ( ) =>
{
it( 'empty', ( ) =>
{
const parsed = parseJson( ' {}' );

expect( parsed.toJSON( ) ).toBe( ' { }' );
} );

it( 'flow', ( ) =>
{
const parsed = parseJson( '{ "foo": "bar" }' );

expect( parsed.toJSON( ) ).toBe( '{ "foo": "bar" }' );
} );

it( 'non-flow', ( ) =>
{
const parsed = parseJson( '{\n "foo": "bar"}' );

expect( parsed.toJSON( ) ).toBe( '{\n "foo": "bar"\n}' );
} );

it( 'self packge.json', async ( ) =>
{
const pkgJsonFile = resolvePath( rootDir, 'package.json' );
const pkgJson = await readFile( pkgJsonFile, 'utf-8' );

const parsed = parseJson( pkgJson );

expect( parsed.toJSON( ).trimEnd( ) ).toBe( pkgJson.trimEnd( ) );
} );
} );
} );

0 comments on commit 23aca83

Please sign in to comment.