Skip to content

Commit 00f8624

Browse files
committed
feat: add substitution feature
1 parent b1243a4 commit 00f8624

File tree

4 files changed

+62
-17
lines changed

4 files changed

+62
-17
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ An example mapping could be as following. In this example we'll rename the `.nes
4747
"default_null"
4848
],
4949
"__fields": {
50+
"secret": {
51+
"map": "{{ secret.key }}"
52+
},
5053
"nested_prop": {
5154
"type": "object",
5255
"__fields": {
@@ -71,10 +74,11 @@ An example mapping could be as following. In this example we'll rename the `.nes
7174
}
7275
```
7376

74-
Running `translate(sample, mapping)` against it would derive the following JSON:
77+
Running `translate(sample, mapping, substitute={ 'secret.key': 'abc123' })` against it would derive the following JSON:
7578

7679
```json
7780
{
81+
"secret": "abc123",
7882
"nested_prop": {
7983
"name": "Terry Davis",
8084
"years_on_earth": "50",

examples/mapping1.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"default_null"
66
],
77
"__fields": {
8+
"bananao": {
9+
"map": "{{ teste }}"
10+
},
811
"entry": {
912
"map": "shipment",
1013
"type": "object",

src/normalize_json/normalize.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,16 @@
3737
]
3838

3939
Node = typing.TypedDict('Node', {
40-
'map': typing.NotRequired[str | list[str]],
40+
'map': str | list[str],
4141
'type': AcceptedType,
42-
'array': typing.NotRequired[bool],
43-
'default': typing.NotRequired[typing.Any],
44-
'modifiers': typing.NotRequired[list[Modifier]],
45-
'trim_start': typing.NotRequired[int],
46-
'trim_end': typing.NotRequired[int],
47-
'pick_until': typing.NotRequired[str],
48-
'__fields': typing.NotRequired[dict[str, 'Node']]
49-
})
42+
'array': bool,
43+
'default': typing.Any,
44+
'modifiers': list[Modifier],
45+
'trim_start': int,
46+
'trim_end': int,
47+
'pick_until': str,
48+
'__fields': dict[str, 'Node']
49+
}, total=False)
5050

5151
Mapping = typing.TypedDict('Mapping', {
5252
'array': typing.NotRequired[bool],
@@ -106,7 +106,7 @@ def check_types(node: Node, value: typing.Any, modifiers: list[Modifier]):
106106
if node.get('array') \
107107
else value.__class__.__name__
108108

109-
vexpected = TYPE_MAPPING.get(node['type'])
109+
vexpected = TYPE_MAPPING.get(node.get('type', 'string'))
110110
if actual == vexpected \
111111
or (actual == 'int' and vexpected in ['number', 'float']) \
112112
or (actual == 'NoneType' and 'default_null' in modifiers):
@@ -156,7 +156,13 @@ def get_initial_value(target: typing.Any, mapped_name: str, flat_obj: RawObject)
156156
return initial_value
157157

158158

159-
def translate(target: T | tuple[T, int], mapping: Mapping, acc: RawObject = {}, inherited_modifiers: list[Modifier] | None = None, inherited_flat_obj: tuple[RawObject, RawObject] | None = None) -> T:
159+
def translate(
160+
target: T | tuple[T, int],
161+
mapping: Mapping, acc: RawObject = {},
162+
inherited_modifiers: list[Modifier] | None = None,
163+
inherited_flat_obj: tuple[RawObject, RawObject] | None = None,
164+
substitute: dict[str, typing.Any] = {}
165+
) -> T:
160166
ret: RawObject = {}
161167
flat_obj, flat_obj_arr = inherited_flat_obj or (
162168
flatten(typing.cast(RawObject, target)),
@@ -178,6 +184,8 @@ def translate(target: T | tuple[T, int], mapping: Mapping, acc: RawObject = {},
178184
mapped_name = node.get('map', original_name)
179185
initial_value: typing.Any = None
180186

187+
var_name: str|None = None
188+
181189
modifiers = node.get('modifiers', root_modifiers)
182190
if 'reverse' in modifiers:
183191
mapped_name, original_name = original_name, mapped_name
@@ -198,16 +206,23 @@ def translate(target: T | tuple[T, int], mapping: Mapping, acc: RawObject = {},
198206
elif mapped_name in flat_obj_arr and flat_obj_arr[mapped_name] != None:
199207
initial_value = flat_obj_arr.get(mapped_name)
200208
break
209+
elif mapped_name[:2] == "{{":
210+
var_name = mapped_name[2:].replace(' ', '')[:-2]
211+
break
201212

202213
mapped_name = typing.cast(str, mapped_name)
203214
original_name = typing.cast(str, original_name)
204215

216+
if var_name:
217+
ret[original_name] = substitute.get(var_name)
218+
continue
219+
205220
if '__fields' in node:
206221
if not node.get('map'):
207-
value = translate(typing.cast(typing.Any, target), node, acc, modifiers)
222+
value = translate(typing.cast(typing.Any, target), node, acc, modifiers, substitute=substitute)
208223
else:
209224
child: typing.Any = initial_value or target[original_name]
210-
value = translate(child, node, acc, modifiers, (flat_obj, flat_obj_arr))
225+
value = translate(child, node, acc, modifiers, (flat_obj, flat_obj_arr), substitute=substitute)
211226
if node.get('array') and not isinstance(value, list):
212227
value = [value]
213228

@@ -231,7 +246,7 @@ def translate(target: T | tuple[T, int], mapping: Mapping, acc: RawObject = {},
231246
raise ValueError('illegal array')
232247

233248
result = [
234-
translate((e, idx), mapping, acc, inherited_modifiers, (flat_obj, flat_obj_arr))
249+
translate((e, idx), mapping, acc, inherited_modifiers, (flat_obj, flat_obj_arr), substitute=substitute)
235250
for idx, e in enumerate(typing.cast(list[typing.Any], target))
236251
]
237252

tests/translate.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,13 @@
119119
}
120120

121121
mapping2: Mapping = {
122+
'modifiers': [
123+
'default_null'
124+
],
122125
'__fields': {
126+
'secret_key': {
127+
'map': '{{ secrets.key }}'
128+
},
123129
'items': {
124130
'map': 'items',
125131
'type': 'object',
@@ -142,7 +148,11 @@
142148
'type': 'string'
143149
},
144150
'age': {
145-
'type': 'integer'
151+
'type': 'integer',
152+
'map': [
153+
'age',
154+
'{{ defaults.age }}'
155+
]
146156
}
147157
}
148158
}
@@ -182,7 +192,6 @@ def test_translate_output(self):
182192
self.assertEqual(result['trim_me_end'], 'abc')
183193
self.assertEqual(result['pick_until'], 'Terry')
184194

185-
186195
def test_translate_inloco(self):
187196
unserialized = unserialize(sample2)
188197
result = translate(unserialized, mapping2)
@@ -192,3 +201,17 @@ def test_translate_inloco(self):
192201
self.assertEqual(result['items']['data'][0]['age'], 23)
193202
self.assertEqual(result['items']['data'][1]['name'], 'Thor')
194203
self.assertEqual(result['items']['data'][1]['age'], 15)
204+
205+
206+
def test_translate_substitute(self):
207+
sample2_copy = sample2.copy()
208+
del sample2_copy['items']['data'][0]['age']
209+
210+
unserialized = unserialize(sample2_copy)
211+
result = translate(unserialized, mapping2, substitute={
212+
'secrets.key': 'abc123',
213+
'defaults.age': 20
214+
})
215+
216+
self.assertEqual(result['secret_key'], 'abc123')
217+
self.assertEqual(result['items']['data'][0]['age'], 20)

0 commit comments

Comments
 (0)