From 9d83653f5bbed5b13610697a357c0fe0870f9c4a Mon Sep 17 00:00:00 2001 From: Twice Date: Mon, 18 Mar 2024 21:06:41 +0900 Subject: [PATCH] Support value setting for nonexistent paths in JSON.SET (#2178) --- src/types/json.h | 32 +++++++++++++++++++++++++++++--- tests/cppunit/types/json_test.cc | 8 ++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/types/json.h b/src/types/json.h index 4b786834a39..5caf212e7db 100644 --- a/src/types/json.h +++ b/src/types/json.h @@ -151,11 +151,34 @@ struct JsonValue { Status Set(std::string_view path, JsonValue &&new_value) { try { - jsoncons::jsonpath::json_replace(value, path, [&new_value](const std::string & /*path*/, jsoncons::json &origin) { - origin = new_value.value; - }); + bool is_set = false; + jsoncons::jsonpath::json_replace(value, path, + [&new_value, &is_set](const std::string & /*path*/, jsoncons::json &origin) { + origin = new_value.value; + is_set = true; + }); + + if (!is_set) { + // NOTE: this is a workaround since jsonpath doesn't support replace for nonexistent paths in jsoncons + // and in this workaround we can only accept normalized path + // refer to https://github.com/danielaparker/jsoncons/issues/496 + jsoncons::jsonpath::json_location location = jsoncons::jsonpath::json_location::parse(path); + jsoncons::jsonpointer::json_pointer ptr{}; + + for (const auto &element : location) { + if (element.has_name()) + ptr /= element.name(); + else { + ptr /= element.index(); + } + } + + jsoncons::jsonpointer::replace(value, ptr, new_value.value, true); + } } catch (const jsoncons::jsonpath::jsonpath_error &e) { return {Status::NotOK, e.what()}; + } catch (const jsoncons::jsonpointer::jsonpointer_error &e) { + return {Status::NotOK, e.what()}; } return Status::OK(); @@ -413,6 +436,9 @@ struct JsonValue { bool not_exists = jsoncons::jsonpath::json_query(value, path).empty(); if (not_exists) { + // NOTE: this is a workaround since jsonpath doesn't support replace for nonexistent paths in jsoncons + // and in this workaround we can only accept normalized path + // refer to https://github.com/danielaparker/jsoncons/issues/496 jsoncons::jsonpath::json_location location = jsoncons::jsonpath::json_location::parse(path); jsoncons::jsonpointer::json_pointer ptr{}; diff --git a/tests/cppunit/types/json_test.cc b/tests/cppunit/types/json_test.cc index 82bfa944097..40b8f2a10a6 100644 --- a/tests/cppunit/types/json_test.cc +++ b/tests/cppunit/types/json_test.cc @@ -96,6 +96,14 @@ TEST_F(RedisJsonTest, Set) { ASSERT_EQ(json_val_.Dump().GetValue(), "[{},[]]"); ASSERT_THAT(json_->Set(key_, "$[1]", "invalid").ToString(), MatchesRegex(".*syntax_error.*")); ASSERT_TRUE(json_->Del(key_, "$", &result).ok()); + + ASSERT_TRUE(json_->Set(key_, "$", R"({"a":1})").ok()); + ASSERT_TRUE(json_->Set(key_, "$.b", "2").ok()); + ASSERT_TRUE(json_->Set(key_, "$.c", R"({"x":3})").ok()); + ASSERT_TRUE(json_->Set(key_, "$.c.y", "4").ok()); + + ASSERT_TRUE(json_->Get(key_, {}, &json_val_).ok()); + ASSERT_EQ(json_val_.value, jsoncons::json::parse(R"({"a":1,"b":2,"c":{"x":3,"y":4}})")); } TEST_F(RedisJsonTest, Get) {