@peter_marklund/json 0.0.2 → 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 CHANGED
@@ -18,15 +18,18 @@ npm install @peter_marklund/json -g
18
18
  echo '{"foo": "1"}' | json .foo
19
19
  ```
20
20
 
21
- ## Developing the Library Locally
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 require(filePath)
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(JSON.stringify(data))
131
+ console.log(stringify(data))
124
132
  }
125
-
133
+
126
134
  function printPrettyJson(data) {
127
- console.log(colorize(JSON.stringify(data, null, 4)))
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.2",
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": "echo \"Error: no test specified\" && exit 1"
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
+ }