diff --git a/jaseci_core/jaseci/__init__.py b/jaseci_core/jaseci/__init__.py index 5ee8e1c9d1..46a30a7505 100644 --- a/jaseci_core/jaseci/__init__.py +++ b/jaseci_core/jaseci/__init__.py @@ -32,6 +32,7 @@ def load_standard(): import jaseci.extens.act_lib.stripe # noqa import jaseci.extens.act_lib.regex # noqa import jaseci.extens.act_lib.maths # noqa + import jaseci.extens.act_lib.djson # noqa load_standard() diff --git a/jaseci_core/jaseci/extens/act_lib/djson.py b/jaseci_core/jaseci/extens/act_lib/djson.py new file mode 100644 index 0000000000..1b91dd2e37 --- /dev/null +++ b/jaseci_core/jaseci/extens/act_lib/djson.py @@ -0,0 +1,130 @@ +"""Built in actions for Jaseci""" +from jaseci.jsorc.live_actions import jaseci_action +from json import loads, dumps +from re import compile + +internal = compile(r"\(([a-zA-Z0-9_\.\$]*?)\)") +full = compile(r"^\{\{([a-zA-Z0-9_\.\$\(\)]*?)\}\}$") +partial = compile(r"\{\{([a-zA-Z0-9_\.\$\(\)]*?)\}\}") + + +def convert_dict(value) -> dict: + try: + if isinstance(value, str): + return loads(value) + else: + return dict(value) + except: + return "Error JSON loads!" + + +def convert_str(value) -> str: + if isinstance(value, (dict, list)): + return dumps(value) + else: + return "" if value == None else str(value) + + +def get_deep_value(keys: list[str], source, default): + if len(keys) == 0: + return source + + key = keys.pop(0) + _type = type(source) + if _type is dict and key in source: + return get_deep_value(keys, source[key], default) + elif _type is list and key.isnumeric(): + return get_deep_value(keys, source[int(key)], default) + else: + return default + + +def get_value(jpath: str, source, default=None): + while internal.search(jpath): + for intern in internal.findall(jpath): + jpath = jpath.replace( + "(" + intern + ")", convert_str(get_value(intern, source, "")) + ) + + if jpath: + keys = jpath.split(".") + key = keys.pop(0) + if key == "$": + if isinstance(source, (dict, list)): + return get_deep_value(keys, source, default) + elif len(keys) == 0: + return source + elif key == "$s": + if isinstance(source, (dict, list)): + return convert_str(get_deep_value(keys, source, default)) + elif len(keys) == 0: + return convert_str(source) + elif key == "$i": + if isinstance(source, (dict, list)): + value = get_deep_value(keys, source, default) + return 0 if value == None else int(value) + elif len(keys) == 0: + return 0 if source == None else int(source) + elif key == "$d": + if isinstance(source, (dict, list)): + return convert_dict(get_deep_value(keys, source, default)) + elif len(keys) == 0: + return convert_dict(source) + else: + pass + # for future syntax + return default + + +def parse_str(key: str, value: str, target, source): + matcher = full.match(value) + if matcher: + value = get_value(matcher.group(1), source) + else: + for jpath in partial.findall(value): + value = value.replace( + "{{" + jpath + "}}", convert_str(get_value(jpath, source, "")) + ) + + if key: + target[key] = value + else: + return value + + +def traverse_dict(target: dict, source): + for key in target.keys(): + traverse(key, target[key], target, source) + + +def traverse_list(target: list, source): + for key, value in enumerate(target): + traverse(key, value, target, source) + + +def traverse(key, value, target, source): + _type = type(value) + if _type is dict: + traverse_dict(value, source) + elif _type is list: + traverse_list(value, source) + elif _type is str: + parse_str(key, value, target, source) + + +@jaseci_action() +def parse(source, target): + """ + Dynamic parsing of target dict or list + + Return - parsed target object + """ + _type = type(target) + if _type is dict: + traverse_dict(target, source) + elif _type is list: + traverse_list(target, source) + elif _type is str: + return parse_str(None, target, None, source) + + return target diff --git a/jaseci_core/jaseci/extens/act_lib/tests/fixtures/djson.jac b/jaseci_core/jaseci/extens/act_lib/tests/fixtures/djson.jac new file mode 100644 index 0000000000..1a4bfe4c90 --- /dev/null +++ b/jaseci_core/jaseci/extens/act_lib/tests/fixtures/djson.jac @@ -0,0 +1,224 @@ +walker parser { + can djson.parse; + with entry { + + report "[1]: whole source"; + report djson.parse( + {"a": 1}, + {"b": "{{$}}"} + ); + + report "[2]: whole source cast to string"; + report djson.parse( + {"a": 1}, + {"b": "{{$s}}"} + ); + + report "[3]: int"; + report djson.parse( + {"a": 1}, + {"b": "{{$.a}}"} + ); + + report "[4]: int cast to string"; + report djson.parse( + {"a": 1}, + {"b": "{{$s.a}}"} + ); + + report "[5]: int partial"; + report djson.parse({"a": 1}, + {"b": "partial-{{$.a}}"} + ); + + report "[6]: string"; + report djson.parse( + {"a": "1"}, + {"b": "{{$.a}}"} + ); + + report "[7]: string partial"; + report djson.parse( + {"a": "1"}, + {"b": "partial-{{$.a}}"} + ); + + report "[8]: int list"; + report djson.parse( + {"a": [1]}, + {"b": "{{$.a}}"} + ); + + report "[9]: int list cast to string"; + report djson.parse( + {"a": [1]}, + {"b": "{{$s.a}}"} + ); + + report "[10]: int list partial"; + report djson.parse( + {"a": [1]}, + {"b": "partial-{{$.a}}"} + ); + + report "[11]: int list index"; + report djson.parse( + {"a": [1]}, + {"b": "{{$.a.0}}"} + ); + + report "[12]: string list"; + report djson.parse( + {"a": ["1"]}, + {"b": "{{$.a}}"} + ); + + report "[13]: string list cast to string"; + report djson.parse( + {"a": ["1"]}, + {"b": "{{$s.a}}"} + ); + + report "[14]: string list partial"; + report djson.parse( + {"a": ["1"]}, + {"b": "partial-{{$.a}}"} + ); + + report "[15]: string list index"; + report djson.parse( + {"a": ["1"]}, + {"b": "{{$.a.0}}"} + ); + + report "[16]: dict"; + report djson.parse( + {"a": {"test": 1}}, + {"b": "{{$.a}}"} + ); + + report "[17]: dict cast to string"; + report djson.parse( + {"a": {"test": 1}}, + {"b": "{{$s.a}}"} + ); + + report "[18]: dict partial"; + report djson.parse( + {"a": {"test": 1}}, + {"b": "partial-{{$.a}}"} + ); + + report "[19]: nested dict"; + report djson.parse( + {"a": {"test": 1}}, + {"b": "{{$.a.test}}"} + ); + + report "[20]: nested dict cast to string"; + report djson.parse( + {"a": {"test": 1}}, + {"b": "{{$s.a.test}}"} + ); + + report "[21]: nested dict partial"; + report djson.parse( + {"a": {"test": 1}}, + {"b": "partial-{{$.a.test}}"} + ); + + report "[22]: super nested dict that returns the value"; + report djson.parse( + {"a": {"test": [{ + "a":1, + "b":2, + "c":3 + }]}}, + "{{$.a.test.0.c}}" + ); + + report "[23]: string target"; + report djson.parse( + {"a": "1"}, + "{{$.a}}" + ); + + report "[24]: string target"; + report djson.parse( + {"a": "1"}, + "{{$.a}}" + ); + + report "[25]: string target partial"; + report djson.parse( + {"a": "1"}, + "partial-{{$.a}}" + ); + + report "[26]: string source"; + report djson.parse( + "testing", + {"b": "{{$}}"} + ); + + report "[27]: string source partial"; + report djson.parse( + "testing", + {"b": "partial-{{$}}"} + ); + + report "[28]: int source"; + report djson.parse( + 1, + {"b": "{{$}}"} + ); + + report "[29]: int source cast"; + report djson.parse( + 1, + {"b": "{{$s}}"} + ); + + report "[30]: int source partial"; + report djson.parse( + 1, + {"b": "partial-{{$}}"} + ); + + report "[31]: int source string target"; + report djson.parse( + 1, + "{{$}}" + ); + + report "[32]: int source string target cast"; + report djson.parse( + 1, + "{{$s}}" + ); + + report "[33]: not existing"; + report djson.parse( + {"a": {"b": 1}}, + {"b": "{{$.a.c}}"} + ); + + report "[34]: not existing cast to string empty"; + report djson.parse( + {"a": {"b": 1}}, + {"b": "{{$s.a.c}}"} + ); + + report "[35]: string source cast to dict"; + report djson.parse( + "{}", + "{{$d}}" + ); + + report "[36]: string source cast to int"; + report djson.parse( + "1", + "{{$i}}" + ); + } +} \ No newline at end of file diff --git a/jaseci_core/jaseci/extens/act_lib/tests/test_djson.py b/jaseci_core/jaseci/extens/act_lib/tests/test_djson.py new file mode 100644 index 0000000000..902ef2b7a9 --- /dev/null +++ b/jaseci_core/jaseci/extens/act_lib/tests/test_djson.py @@ -0,0 +1,85 @@ +from jaseci.utils.test_core import CoreTest, jac_testcase + + +class DjsonTest(CoreTest): + fixture_src = __file__ + + @jac_testcase("djson.jac", "parser") + def test_djson_parse(self, ret): + self.assertEqual( + ret["report"], + [ + "[1]: whole source", + {"b": {"a": 1}}, + "[2]: whole source cast to string", + {"b": '{"a": 1}'}, + "[3]: int", + {"b": 1}, + "[4]: int cast to string", + {"b": "1"}, + "[5]: int partial", + {"b": "partial-1"}, + "[6]: string", + {"b": "1"}, + "[7]: string partial", + {"b": "partial-1"}, + "[8]: int list", + {"b": [1]}, + "[9]: int list cast to string", + {"b": "[1]"}, + "[10]: int list partial", + {"b": "partial-[1]"}, + "[11]: int list index", + {"b": 1}, + "[12]: string list", + {"b": ["1"]}, + "[13]: string list cast to string", + {"b": '["1"]'}, + "[14]: string list partial", + {"b": 'partial-["1"]'}, + "[15]: string list index", + {"b": "1"}, + "[16]: dict", + {"b": {"test": 1}}, + "[17]: dict cast to string", + {"b": '{"test": 1}'}, + "[18]: dict partial", + {"b": 'partial-{"test": 1}'}, + "[19]: nested dict", + {"b": 1}, + "[20]: nested dict cast to string", + {"b": "1"}, + "[21]: nested dict partial", + {"b": "partial-1"}, + "[22]: super nested dict that returns the value", + 3, + "[23]: string target", + "1", + "[24]: string target", + "1", + "[25]: string target partial", + "partial-1", + "[26]: string source", + {"b": "testing"}, + "[27]: string source partial", + {"b": "partial-testing"}, + "[28]: int source", + {"b": 1}, + "[29]: int source cast", + {"b": "1"}, + "[30]: int source partial", + {"b": "partial-1"}, + "[31]: int source string target", + 1, + "[32]: int source string target cast", + "1", + "[33]: not existing", + {"b": None}, + "[34]: not existing cast to string empty", + {"b": ""}, + "[35]: string source cast to dict", + {}, + "[36]: string source cast to int", + 1, + ], + )