Skip to content

Commit

Permalink
feat:implement json.merge
Browse files Browse the repository at this point in the history
Fix #181
  • Loading branch information
cunla committed Jul 1, 2023
1 parent 2a200de commit 8d81c1c
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 17 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
lupa: true
hypothesis: true
- python-version: "3.11"
redis-image: "redis/redis-stack-server:7.0.6-RC3"
redis-image: "redis/redis-stack-server:7.2.0-RC2"
redis-py: "4.6.0"
lupa: true
json: true
Expand Down
32 changes: 28 additions & 4 deletions fakeredis/stack/_json_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# Standard Library Imports
import json
from json import JSONDecodeError
from typing import Any, Union
from typing import Any, Union, Dict

from jsonpath_ng import Root, JSONPath
from jsonpath_ng.exceptions import JsonPathParserError
Expand Down Expand Up @@ -46,6 +46,21 @@ def _path_is_root(path: JSONPath) -> bool:
return path == Root()


def _dict_deep_merge(source: Dict, destination: Dict) -> Dict:
"""Deep merge of two dictionaries
"""
for key, value in source.items():
if value is None and key in destination:
del destination[key]
elif isinstance(value, dict):
node = destination.setdefault(key, {})
_dict_deep_merge(value, node)
else:
destination[key] = value

return destination


class JSONObject:
"""Argument converter for JSON objects."""

Expand Down Expand Up @@ -440,6 +455,15 @@ def json_mset(self, *args):
JSONCommandsMixin._json_set(key, path_str, value)
return helpers.OK

# @command(name="JSON.MERGE", fixed=(Key(), bytes, bytes), repeat=(bytes,), flags=msgs.FLAG_LEAVE_EMPTY_VAL)
# def json_merge(self, key, path_str, mult_by, *args):
# pass # TODO
@command(name="JSON.MERGE", fixed=(Key(), bytes, JSONObject), repeat=(), flags=msgs.FLAG_LEAVE_EMPTY_VAL)
def json_merge(self, key, path_str: bytes, value: JsonType):
path: JSONPath = _parse_jsonpath(path_str)
if key.value is not None and (type(key.value) is not dict) and not _path_is_root(path):
raise SimpleError(msgs.JSON_WRONG_REDIS_TYPE)
matching = path.find(key.value)
for item in matching:
prev_value = item.value if item is not None else dict()
_dict_deep_merge(value, prev_value)
if len(matching) > 0:
key.updated()
return helpers.OK
28 changes: 16 additions & 12 deletions test/test_json/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,27 +558,31 @@ def test_nummultby(r: redis.Redis):
assert r.json().nummultby("doc1", ".b[0].a", 3) == 6


@testtools.run_test_if_redispy_ver('above', '5')
@testtools.run_test_if_redispy_ver('above', '4.6')
@pytest.mark.min_server('7.1')
def test_json_merge(r: redis.Redis):
# Test with root path $
key = "person_data"
r.json().set(key, Path.root_path(), {"person1": {"personal_data": {"name": "John"}}}, )
r.json().merge(key, Path.root_path(), {"person1": {"personal_data": {"hobbies": "reading"}}})
assert r.json().get(key) == {"person1": {"personal_data": {"name": "John", "hobbies": "reading"}}}
assert r.json().set("person_data", "$", {"person1": {"personal_data": {"name": "John"}}}, )
assert r.json().merge("person_data", "$", {"person1": {"personal_data": {"hobbies": "reading"}}})
assert r.json().get("person_data") == {"person1": {"personal_data": {"name": "John", "hobbies": "reading"}}}

# Test with root path path $.person1.personal_data
r.json().merge(key, "$.person1.personal_data", {"country": "Israel"})
assert r.json().get(key) == {
"person1": {"personal_data": {"name": "John", "hobbies": "reading", "country": "Israel"}}}
assert r.json().merge("person_data", "$.person1.personal_data", {"country": "Israel"})
assert r.json().get("person_data") == {"person1": {
"personal_data": {"name": "John", "hobbies": "reading", "country": "Israel"}
}}

# Test with null value to delete a value
r.json().merge("person_data", "$.person1.personal_data", {"name": None})
assert r.json().get(key) == {"person1": {"personal_data": {"country": "Israel", "hobbies": "reading"}}}
assert r.json().merge("person_data", "$.person1.personal_data", {"name": None})
assert r.json().get("person_data") == {
"person1": {"personal_data": {"country": "Israel", "hobbies": "reading"}}
}


@testtools.run_test_if_redispy_ver('above', '5')
@testtools.run_test_if_redispy_ver('above', '4.6')
@pytest.mark.min_server('7.1')
def test_mset(r: redis.Redis):
r.json().mset("1", Path.root_path(), 1, "2", Path.root_path(), 2)
r.json().mset([("1", Path.root_path(), 1), ("2", Path.root_path(), 2)])

assert r.json().mget(["1"], Path.root_path()) == [1]
assert r.json().mget(["1", "2"], Path.root_path()) == [1, 2]

0 comments on commit 8d81c1c

Please sign in to comment.