Skip to content

Commit 2010eef

Browse files
committed
Implement @fresha/json-pointer
1 parent 252138d commit 2010eef

File tree

14 files changed

+266
-0
lines changed

14 files changed

+266
-0
lines changed

package-lock.json

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/json-pointer/.eslintrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": ["@fresha"],
3+
"parser": "@typescript-eslint/parser",
4+
"parserOptions": {
5+
"project": ["./tsconfig.eslint.json"]
6+
}
7+
}

packages/json-pointer/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Fresha
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/json-pointer/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# @fresha/json-pointer
2+
3+
Contains shared type definitions and classes, used in other api-tools packages.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('@fresha/jest-config');

packages/json-pointer/package.json

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"name": "@fresha/json-pointer",
3+
"version": "0.1.0",
4+
"description": "Implementation of the JSON Pointer specification",
5+
"main": "build/index.js",
6+
"types": "build/index.d.ts",
7+
"scripts": {
8+
"build": "tsc",
9+
"build:watch": "tsc --watch",
10+
"check": "run-s lint test",
11+
"check:fix": "run-s lint:fix test",
12+
"clean": "rimraf ./build",
13+
"eslint": "eslint ./src",
14+
"eslint:fix": "eslint ./src --fix",
15+
"lint": "run-s eslint typecheck",
16+
"lint:fix": "run-s eslint:fix typecheck",
17+
"prebuild": "npm run clean",
18+
"prebuild:watch": "npm run clean",
19+
"test": "jest",
20+
"typecheck": "tsc --noEmit"
21+
},
22+
"files": [
23+
"build/",
24+
"package.json",
25+
"LICENSE",
26+
"README.md"
27+
],
28+
"keywords": [
29+
"JSON",
30+
"JSON pointer"
31+
],
32+
"author": "Fresha Engineering",
33+
"contributors": [
34+
{
35+
"name": "Andriy Mykulyak",
36+
"email": "andriy@fresha.com",
37+
"url": "https://github.com/mykulyak"
38+
}
39+
],
40+
"maintainers": [
41+
{
42+
"name": "Andriy Mykulyak",
43+
"email": "andriy@fresha.com",
44+
"url": "https://github.com/mykulyak"
45+
}
46+
],
47+
"license": "MIT",
48+
"devDependencies": {
49+
"@fresha/api-tools-core": "0.1.0",
50+
"@fresha/eslint-config": "0.1.0",
51+
"@fresha/jest-config": "0.1.0",
52+
"@fresha/typescript-config": "0.1.0",
53+
"typescript": "^4.7.2"
54+
}
55+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { JSONPointer } from './JSONPointer';
2+
3+
import type { JSONValue } from '@fresha/api-tools-core';
4+
5+
test('constructor', () => {
6+
expect(new JSONPointer('')).toBeInstanceOf(JSONPointer);
7+
expect(new JSONPointer('/')).toBeInstanceOf(JSONPointer);
8+
expect(new JSONPointer('/x/y/z')).toBeInstanceOf(JSONPointer);
9+
expect(() => new JSONPointer('a/b/c')).toThrow();
10+
});
11+
12+
test('get', () => {
13+
const testObj: JSONValue = {
14+
numberProp: 1,
15+
objProp: {
16+
strProp: 'str',
17+
boolProp: true,
18+
},
19+
arrProp: [12, '34', { subProp: 'cdd' }],
20+
nullProp: null,
21+
};
22+
23+
expect(new JSONPointer('').get(testObj)).toBe(testObj);
24+
expect(new JSONPointer('/numberProp').get(testObj)).toBe(1);
25+
expect(new JSONPointer('/objProp/strProp').get(testObj)).toBe('str');
26+
expect(new JSONPointer('/arrProp/2/subProp').get(testObj)).toBe('cdd');
27+
expect(new JSONPointer('/nullProp').get(testObj)).toBe(null);
28+
expect(new JSONPointer('/numberProp/nonExistend').get(testObj)).toBe(undefined);
29+
expect(new JSONPointer('/objProp/nonExistent').get(testObj)).toBe(undefined);
30+
});
31+
32+
test('toString', () => {
33+
expect(new JSONPointer('').toString()).toBe('');
34+
expect(new JSONPointer('/prop/1/arr/obj/str/num/bool').toString()).toBe(
35+
'/prop/1/arr/obj/str/num/bool',
36+
);
37+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import assert from 'assert';
2+
3+
import type { JSONObject, JSONValue } from '@fresha/api-tools-core';
4+
5+
export class JSONPointer {
6+
private readonly segments: string[];
7+
8+
constructor(segments: string | string[]) {
9+
this.segments = typeof segments === 'string' ? segments.split('/') : segments;
10+
assert.equal(this.segments[0], '');
11+
}
12+
13+
get(obj: JSONValue): JSONValue | undefined {
14+
if (this.segments.length === 1) {
15+
return obj;
16+
}
17+
let res: JSONValue | undefined = obj;
18+
for (const seg of this.segments.slice(1)) {
19+
if (seg) {
20+
res = res != null ? (res as JSONObject)[seg] : undefined;
21+
if (res === undefined) {
22+
break;
23+
}
24+
}
25+
}
26+
return res;
27+
}
28+
29+
toString(): string {
30+
return this.segments.join('/');
31+
}
32+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { JSONPointerResolver } from './JSONPointerResolver';
2+
3+
test('get', () => {
4+
const testObj = {
5+
numberProp: 1,
6+
objProp: {
7+
strProp: 'str',
8+
boolProp: true,
9+
},
10+
arrProp: [12, '34', { subProp: 'cdd' }],
11+
nullProp: null,
12+
};
13+
const resolver = new JSONPointerResolver(testObj);
14+
15+
expect(resolver.get('/')).toBe(testObj);
16+
expect(resolver.get('/objProp/strProp')).toBe('str');
17+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { JSONPointer } from './JSONPointer';
2+
3+
import type { JSONValue } from '@fresha/api-tools-core';
4+
5+
export class JSONPointerResolver {
6+
private readonly obj: JSONValue;
7+
8+
constructor(obj: JSONValue) {
9+
this.obj = obj;
10+
}
11+
12+
get(ptrOrStr: string | string[] | JSONPointer): JSONValue | undefined {
13+
const ptr = ptrOrStr instanceof JSONPointer ? ptrOrStr : new JSONPointer(ptrOrStr);
14+
return ptr.get(this.obj);
15+
}
16+
}

0 commit comments

Comments
 (0)