Skip to content

Commit eb76a36

Browse files
authored
Merge pull request #59 from Peefy/publish-jsonpatch-module
feat: impl init jsonpatch module
2 parents 91c2ac1 + b5ef6e9 commit eb76a36

4 files changed

Lines changed: 171 additions & 0 deletions

File tree

jsonpatch/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
## Introduction
2+
3+
`jsonpatch` is a module for applying JSON patches (RFC 6902) for KCL values.
4+
5+
TODO: more jsonpatch actions including "add", "remove", "move", etc.
6+
7+
## How to Use
8+
9+
+ Add the dependency
10+
11+
```shell
12+
kcl mod add jsonpatch
13+
```
14+
15+
+ Write the code
16+
17+
```python
18+
import jsonpatch
19+
20+
data = {
21+
"firstName": "John",
22+
"lastName": "Doe",
23+
"age": 30,
24+
"address": {
25+
"streetAddress": "1234 Main St",
26+
"city": "New York",
27+
"state": "NY",
28+
"postalCode": "10001"
29+
},
30+
"phoneNumbers": [
31+
{
32+
"type": "home",
33+
"number": "212-555-1234"
34+
},
35+
{
36+
"type": "work",
37+
"number": "646-555-5678"
38+
}
39+
]
40+
}
41+
phoneNumbers0type = jsonpatch.get_obj(data, "phoneNumbers/0/type")
42+
newObj = jsonpatch.set_obj(data, "phoneNumbers/0/type", "school")
43+
```
44+
45+
## Resource
46+
47+
The code source and documents are [here](https://github.com/kcl-lang/artifacthub/tree/main/jsonpatch)

jsonpatch/kcl.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[package]
2+
name = "jsonpatch"
3+
version = "0.0.1"
4+
description = "`jsonpatch` is a module for applying JSON patches (RFC 6902) for KCL values."
5+

jsonpatch/kcl.mod.lock

Whitespace-only changes.

jsonpatch/main.k

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
type PatchOperationType = "add" | "remove" | "replace" | "move" | "test" | "copy"
2+
KCL_BUILTIN_TYPES = ["int", "str", "bool", "float", "NoneType", "UndefinedType", "any", "list", "dict", "function", "number_multiplier"]
3+
NULL_CONSTANTS = [Undefined, None]
4+
5+
schema Patch:
6+
op: PatchOperationType
7+
path: str
8+
value?: any
9+
10+
type JsonPatch = [Patch]
11+
12+
get_obj = lambda obj: any, path: str -> any {
13+
elements = path.lstrip('/').split('/')
14+
_get_obj_n(obj, elements, 0)
15+
}
16+
17+
get_obj_by_key = lambda obj: any, key: str -> any {
18+
result = Undefined
19+
ty = typeof(obj)
20+
# Schema
21+
if ty not in KCL_BUILTIN_TYPES:
22+
result = obj[key]
23+
# Config
24+
elif ty == "dict":
25+
result = obj[key]
26+
# List
27+
elif ty == "list":
28+
idx = _get_list_index_from_key(key)
29+
if idx not in NULL_CONSTANTS:
30+
result = obj[idx]
31+
result
32+
}
33+
34+
_get_list_index_from_key = lambda key: str -> int {
35+
result = Undefined
36+
if key == "-":
37+
result = -1
38+
elif key.startswith("-") and key[1:].isdigit():
39+
result = -int(key)
40+
elif key.isdigit():
41+
result = int(key)
42+
result
43+
}
44+
45+
# Private function
46+
_get_obj_n = lambda obj: any, elements: [str], n: int -> any {
47+
assert n >= 0
48+
result = obj
49+
if n < len(elements):
50+
result = _get_obj_n(get_obj_by_key(obj, elements[n]), elements, n + 1)
51+
result
52+
}
53+
54+
_build_patch_obj_n = lambda obj: {str:}, value: any, elements: [str], n: int -> {str:} {
55+
assert n >= 0
56+
result = Undefined
57+
if n < len(elements):
58+
current_path = "/".join(elements[:n+1])
59+
current_obj = get_obj(obj, current_path)
60+
assert current_obj not in NULL_CONSTANTS, "list value not found for path: ${current_path}"
61+
if n + 1 < len(elements):
62+
next_key = _get_list_index_from_key(elements[n + 1])
63+
next_val = _build_patch_obj_n(obj, value, elements, n + 2)
64+
# List key
65+
if next_key not in NULL_CONSTANTS:
66+
idx = next_key
67+
assert 0 <= idx < len(current_obj), "value not found for path: {}".format(current_path + "/" + elements[n + 1])
68+
result = {
69+
"${elements[n]}": [None] * idx + [next_val] + [None] * (len(current_obj) - 1 - idx)
70+
}
71+
# Config key
72+
else:
73+
result = {
74+
"${elements[n]}": next_val
75+
}
76+
# No Next value
77+
else:
78+
result = {
79+
"${elements[n]}" = value
80+
}
81+
result
82+
}
83+
84+
build_patch = lambda obj: any, path: str, value: any -> any {
85+
_build_patch_obj_n(obj, value, path.lstrip('/').split('/'), 0)
86+
}
87+
88+
set_obj = lambda obj: any, path: str, value: any -> any {
89+
obj | _build_patch_obj_n(obj, value, path.lstrip('/').split('/'), 0)
90+
}
91+
92+
apply_patch = lambda obj: any, patch: Patch {
93+
dst = obj
94+
if patch.op == "add":
95+
assert False
96+
elif patch.op == "remove":
97+
assert False
98+
elif patch.op == "replace":
99+
dst = build_patch(obj, patch.path, patch.value)
100+
elif patch.op == "move":
101+
assert False
102+
elif patch.op == "test":
103+
assert False
104+
elif patch.op == "copy":
105+
assert False
106+
dst
107+
}
108+
109+
apply_patchs = lambda obj: any, patch: JsonPatch -> any {
110+
"""Apply list of patches to specified json document."""
111+
assert False, "TODO: PRs welcome"
112+
{}
113+
}
114+
115+
make_patch = lambda src: any, dst: any -> JsonPatch {
116+
"""Generates patch by comparing two document objects."""
117+
assert False, "TODO: PRs welcome"
118+
[]
119+
}

0 commit comments

Comments
 (0)