@peter_marklund/json 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/bin/json.js +12 -4
- package/package.json +3 -2
- package/src/helpers.js +113 -0
- package/test/input/basic.json +27 -0
- package/test/run.js +53 -0
- package/test/test_spec.json +54 -0
package/README.md
CHANGED
|
@@ -18,15 +18,18 @@ npm install @peter_marklund/json -g
|
|
|
18
18
|
echo '{"foo": "1"}' | json .foo
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Running the Tests
|
|
22
22
|
|
|
23
23
|
```sh
|
|
24
|
+
npm install
|
|
24
25
|
npm link
|
|
26
|
+
npm test
|
|
25
27
|
```
|
|
26
28
|
|
|
27
29
|
## Publishing a new Version
|
|
28
30
|
|
|
29
31
|
```sh
|
|
32
|
+
npm login
|
|
30
33
|
npm publish --access public
|
|
31
34
|
```
|
|
32
35
|
|
package/bin/json.js
CHANGED
|
@@ -42,8 +42,10 @@ const fs = require("fs");
|
|
|
42
42
|
const readline = require("readline");
|
|
43
43
|
const _ = require("lodash");
|
|
44
44
|
Object.assign(global, require("lodash"));
|
|
45
|
+
Object.assign(global, require("../src/helpers.js"));
|
|
45
46
|
const { diff } = require("object-diffy");
|
|
46
47
|
const { colorize } = require("json-colorizer");
|
|
48
|
+
const stringify = require('fast-json-stable-stringify')
|
|
47
49
|
|
|
48
50
|
function getCodeArg() {
|
|
49
51
|
let code = process.argv[2] || "data"
|
|
@@ -64,7 +66,7 @@ async function jsonIn(filePath) {
|
|
|
64
66
|
let textInput
|
|
65
67
|
try {
|
|
66
68
|
if (filePath) {
|
|
67
|
-
return
|
|
69
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
68
70
|
} else {
|
|
69
71
|
textInput = readStdIn()
|
|
70
72
|
// NOTE: I've found JSON.parse intermittently errors out for data sizes around 15 MB but require(filePath) can handle more?
|
|
@@ -119,12 +121,18 @@ async function jsonIn(filePath) {
|
|
|
119
121
|
}
|
|
120
122
|
}
|
|
121
123
|
|
|
124
|
+
function printJsonLines(data) {
|
|
125
|
+
for (const line of data) {
|
|
126
|
+
console.log(stringify(line));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
122
130
|
function printJson(data) {
|
|
123
|
-
console.log(
|
|
131
|
+
console.log(stringify(data))
|
|
124
132
|
}
|
|
125
|
-
|
|
133
|
+
|
|
126
134
|
function printPrettyJson(data) {
|
|
127
|
-
console.log(colorize(
|
|
135
|
+
console.log(colorize(stringify(data, null, 4)))
|
|
128
136
|
}
|
|
129
137
|
|
|
130
138
|
async function main() {
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peter_marklund/json",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "A convenient way to work with JSON using JavaScript in the terminal",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
7
|
+
"test": "./test/run.js"
|
|
8
8
|
},
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"homepage": "https://github.com/peter/json#readme",
|
|
24
24
|
"dependencies": {
|
|
25
|
+
"fast-json-stable-stringify": "^2.1.0",
|
|
25
26
|
"json-colorizer": "^3.0.1",
|
|
26
27
|
"lodash": "^4.17.23",
|
|
27
28
|
"object-diffy": "^1.0.4"
|
package/src/helpers.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
function flattenJson(data, path = []) {
|
|
2
|
+
if (Array.isArray(data)) {
|
|
3
|
+
return data.reduce((acc, value, index) => {
|
|
4
|
+
const valueJson = flattenJson(value, [...path, index]);
|
|
5
|
+
for (const [key, value] of Object.entries(valueJson)) {
|
|
6
|
+
acc[key] = value;
|
|
7
|
+
}
|
|
8
|
+
return acc;
|
|
9
|
+
}, {});
|
|
10
|
+
} else if (typeof data === "object" && data !== null) {
|
|
11
|
+
return Object.keys(data).reduce((acc, key) => {
|
|
12
|
+
const keyJson = flattenJson(data[key], [...path, key]);
|
|
13
|
+
for (const [key, value] of Object.entries(keyJson)) {
|
|
14
|
+
acc[key] = value;
|
|
15
|
+
}
|
|
16
|
+
return acc;
|
|
17
|
+
}, {});
|
|
18
|
+
} else {
|
|
19
|
+
return { [path.join(".")]: data };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// https://stackoverflow.com/questions/1248302/how-to-get-the-size-of-a-javascript-object
|
|
24
|
+
function sizeOfObject(object) {
|
|
25
|
+
var objectList = [];
|
|
26
|
+
var stack = [object];
|
|
27
|
+
var bytes = 0;
|
|
28
|
+
|
|
29
|
+
while (stack.length) {
|
|
30
|
+
var value = stack.pop();
|
|
31
|
+
|
|
32
|
+
if (typeof value === "boolean") {
|
|
33
|
+
bytes += 4;
|
|
34
|
+
} else if (typeof value === "string") {
|
|
35
|
+
bytes += value.length * 2;
|
|
36
|
+
} else if (typeof value === "number") {
|
|
37
|
+
bytes += 8;
|
|
38
|
+
} else if (typeof value === "object" && objectList.indexOf(value) === -1) {
|
|
39
|
+
objectList.push(value);
|
|
40
|
+
|
|
41
|
+
for (var i in value) {
|
|
42
|
+
stack.push(value[i]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return bytes;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function base64Decode(data) {
|
|
50
|
+
return data && Buffer.from(data, "base64").toString();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function base64Encode(data) {
|
|
54
|
+
return data && Buffer.from(data).toString("base64");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function percentile(values, pct) {
|
|
58
|
+
const sorted = values.sort((a, b) => a - b);
|
|
59
|
+
const index = Math.floor(sorted.length * pct);
|
|
60
|
+
return sorted[index];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function p50(values) {
|
|
64
|
+
return percentile(values, 0.5);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function p90(values) {
|
|
68
|
+
return percentile(values, 0.9);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function p99(values) {
|
|
72
|
+
return percentile(values, 0.99);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function stdDev(values, avg) {
|
|
76
|
+
const sum = values.reduce((acc, v) => acc + (v - avg) ** 2, 0);
|
|
77
|
+
return Math.sqrt(sum / values.length);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function stats(values) {
|
|
81
|
+
const sum = values.reduce((acc, v) => acc + v, 0);
|
|
82
|
+
const avg = sum / values.length;
|
|
83
|
+
return {
|
|
84
|
+
count: values.length,
|
|
85
|
+
min: Math.min(...values),
|
|
86
|
+
max: Math.max(...values),
|
|
87
|
+
stdDev: stdDev(values, avg),
|
|
88
|
+
sum,
|
|
89
|
+
avg,
|
|
90
|
+
p1: percentile(values, 0.01),
|
|
91
|
+
p5: percentile(values, 0.05),
|
|
92
|
+
p10: percentile(values, 0.1),
|
|
93
|
+
p20: percentile(values, 0.2),
|
|
94
|
+
p30: percentile(values, 0.3),
|
|
95
|
+
p40: percentile(values, 0.4),
|
|
96
|
+
p50: percentile(values, 0.5),
|
|
97
|
+
p60: percentile(values, 0.6),
|
|
98
|
+
p70: percentile(values, 0.7),
|
|
99
|
+
p80: percentile(values, 0.8),
|
|
100
|
+
p90: percentile(values, 0.9),
|
|
101
|
+
p95: percentile(values, 0.95),
|
|
102
|
+
p99: percentile(values, 0.99),
|
|
103
|
+
p999: percentile(values, 0.999),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
flattenJson,
|
|
109
|
+
sizeOfObject,
|
|
110
|
+
base64Decode,
|
|
111
|
+
base64Encode,
|
|
112
|
+
stats,
|
|
113
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"foo": 1,
|
|
3
|
+
"bar": "Hello world",
|
|
4
|
+
"baz": false,
|
|
5
|
+
"nested": {
|
|
6
|
+
"foo": {
|
|
7
|
+
"bar": "nested value"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"data": [
|
|
11
|
+
{
|
|
12
|
+
"id": 1,
|
|
13
|
+
"name": "Item 1",
|
|
14
|
+
"value": 100
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": 2,
|
|
18
|
+
"name": "Item 2",
|
|
19
|
+
"value": 200
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": 3,
|
|
23
|
+
"name": "Item 3",
|
|
24
|
+
"value": 300
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
package/test/run.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const childProcess = require('child_process')
|
|
6
|
+
const { promisify } = require('util')
|
|
7
|
+
const assert = require('node:assert')
|
|
8
|
+
const stringify = require('fast-json-stable-stringify')
|
|
9
|
+
|
|
10
|
+
const exec = (cmd) => promisify(childProcess.exec)(cmd).then(result => result.stdout.trim())
|
|
11
|
+
|
|
12
|
+
// Is the input a JSON string, number, boolean, or null (i.e. not an object or array)
|
|
13
|
+
function isJsonScalarValue(text) {
|
|
14
|
+
// return text && (text === 'null' || text === 'true' || text === 'false' || text.match(/^\d+$/) || (text.startsWith('"') && text.endsWith('"')))
|
|
15
|
+
return !(text.startsWith('{') || text.startsWith('['))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function runTest(test) {
|
|
19
|
+
const startTime = Date.now()
|
|
20
|
+
console.log('\n--------------------------------------')
|
|
21
|
+
console.log(`Test: ${test.name}`)
|
|
22
|
+
console.log('--------------------------------------\n')
|
|
23
|
+
console.log(`command: ${test.command}`)
|
|
24
|
+
const output = await exec(test.command)
|
|
25
|
+
console.log(`output: ${output}`)
|
|
26
|
+
const elapsedTime = Date.now() - startTime
|
|
27
|
+
console.log(`elapsed: ${elapsedTime}`)
|
|
28
|
+
if (isJsonScalarValue(test.expected)) {
|
|
29
|
+
assert.strictEqual(output, test.expected)
|
|
30
|
+
} else {
|
|
31
|
+
assert.strictEqual(stringify(JSON.parse(output)), stringify(JSON.parse(test.expected)))
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function run() {
|
|
36
|
+
try {
|
|
37
|
+
const startTime = Date.now()
|
|
38
|
+
const spec = JSON.parse(fs.readFileSync(path.join(__dirname, 'test_spec.json'), 'utf8'))
|
|
39
|
+
for (const test of spec.tests) {
|
|
40
|
+
const result = await runTest(test)
|
|
41
|
+
}
|
|
42
|
+
const elapsedTime = Date.now() - startTime
|
|
43
|
+
console.log(`\nTotal elapsed: ${elapsedTime}`)
|
|
44
|
+
console.log(`Total tests run: ${spec.tests.length}`)
|
|
45
|
+
process.exit(0)
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.log(error.stack || error)
|
|
48
|
+
console.log('FAILURE!')
|
|
49
|
+
process.exit(1)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
run()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tests": [
|
|
3
|
+
{
|
|
4
|
+
"name": "echo_no_arg",
|
|
5
|
+
"command": "echo '{\"foo\": 1}' | json",
|
|
6
|
+
"expected": "{\"foo\": 1}"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"name": "echo_dot",
|
|
10
|
+
"command": "echo '{\"foo\": 1}' | json .",
|
|
11
|
+
"expected": "{\"foo\": 1}"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "echo_data",
|
|
15
|
+
"command": "echo '{\"foo\": 1}' | json data",
|
|
16
|
+
"expected": "{\"foo\": 1}"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "get_path",
|
|
20
|
+
"command": "cat test/input/basic.json | json '.nested?.foo?.bar'",
|
|
21
|
+
"expected": "nested value"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "file_path_arg",
|
|
25
|
+
"command": "json .foo test/input/basic.json",
|
|
26
|
+
"expected": "1"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "lodash_sum",
|
|
30
|
+
"command": "cat test/input/basic.json | json 'sum(data.data.map(i => i.value))'",
|
|
31
|
+
"expected": "600"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "object_keys",
|
|
35
|
+
"command": "cat test/input/basic.json | json 'Object.keys(data)'",
|
|
36
|
+
"expected": "[\"foo\", \"bar\", \"baz\", \"nested\", \"data\"]"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "array_length",
|
|
40
|
+
"command": "cat test/input/basic.json | json '.data.length'",
|
|
41
|
+
"expected": "3"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "flatten_json",
|
|
45
|
+
"command": "cat test/input/basic.json | json 'flattenJson(data)'",
|
|
46
|
+
"expected": "{\"foo\": 1, \"bar\": \"Hello world\", \"baz\": false, \"nested.foo.bar\": \"nested value\", \"data.0.id\": 1, \"data.0.name\": \"Item 1\", \"data.0.value\": 100, \"data.1.id\": 2, \"data.1.name\": \"Item 2\", \"data.1.value\": 200, \"data.2.id\": 3, \"data.2.name\": \"Item 3\", \"data.2.value\": 300}"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "stats",
|
|
50
|
+
"command": "cat test/input/basic.json | json 'pick(stats(data.data.map(i => i.value)), [\"min\", \"max\", \"avg\", \"p50\", \"p90\"])'",
|
|
51
|
+
"expected": "{\"min\": 100, \"max\": 300, \"avg\": 200, \"p50\": 200, \"p90\": 300}"
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|