Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions include/sonic/dom/dynamicnode.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@
namespace sonic_json {

template <typename Allocator = SONIC_DEFAULT_ALLOCATOR>
class DNode : public GenericNode<DNode, Allocator> {
class DNode : public GenericNode<DNode<Allocator>> {
public:
using NodeType = DNode;
using BaseNode = GenericNode<DNode, Allocator>;
using BaseNode = GenericNode<DNode>;
using AllocatorType = Allocator;
using MemberNode = typename NodeTraits<DNode>::MemberNode;
using MemberIterator = typename NodeTraits<DNode>::MemberIterator;
Expand Down
120 changes: 118 additions & 2 deletions include/sonic/dom/genericnode.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,34 @@ class MemberNodeT {
template <typename derived_t>
struct NodeTraits;

template <bool UpdateExistentKey, typename T, typename S, typename Allocator,
typename>
static inline void Update(T&, const S&, Allocator&);

// Copied from http://coliru.stacked-crooked.com/a/8dd19d21817cadf5
template <template <typename...> class C, typename... Ts>
std::true_type is_base_of_template_impl(const C<Ts...>*);

template <template <typename...> class C>
std::false_type is_base_of_template_impl(...);

template <typename T, template <typename...> class C>
using is_base_of_template =
decltype(is_base_of_template_impl<C>(std::declval<T*>()));

/**
* @brief Basic class represent a json value.
* @tparam tmpalte <typename> class Node Derived class.
* @tparam Allocator Allocator type for derived class, which allocating memory
* for container and string.
*/
template <template <typename> class Node, typename Allocator>
template <typename NodeType>
class GenericNode {
public:
using NodeType = Node<Allocator>; ///< Derived class type.
using alloc_type =
typename NodeTraits<NodeType>::alloc_type; ///< Dervied class allocator
///< type.
using Allocator = alloc_type;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allocator 名字和 alloc_type 重复

using MemberNode =
typename NodeTraits<NodeType>::MemberNode; ///< Derived class key-value
///< pair struct.
Expand Down Expand Up @@ -1041,6 +1056,43 @@ class GenericNode {
return err == kErrorNone ? wb.ToString() : "";
}

/**
* @brief Json Merge patch, rfc7386
* @param copy_const_string whether copy the strings that are managed by
* user.
*/
template <typename PatchType,
typename std::enable_if<
is_base_of_template<PatchType, GenericNode>::value,
bool>::type = false>
void MergePatch(const PatchType& patch, alloc_type& alloc,
bool copy_const_string=false) {
return Merge(*(downCast()), patch, alloc, copy_const_string);
}

/**
* @brief updates from another object. Copying non-existent key. Updating\n
* existent key \b deeply if template parameter is true.
* @tparam UpdateExistentKey overwritting existent key
* @param src the source node
* @param alloc Allocator that manage the memory of *this
* @param copy_const_string whether copy the strings that are managed by
* user.
* @note cases: \n
* {"a":"b"} .UPDATE. {"a":"c} => {"a":"c"} (UpdateExistedKey == true) \n
* {"a":"b"} .UPDATE. {"a":"c} => {"a":"b"} (UpdateExistedKey == false) \n
* {"a":"b"} .UPDATE. {"b":"c} => {"a":"c", "b":"c"} \n
* {"a":{"bb":1}} .UPDATE. {"a":{"cc":1}} => {"a":{"bb":1, "cc":1}}
*/
template <bool UpdateExistentKey = true, typename S,
typename std::enable_if<is_base_of_template<S, GenericNode>::value,
bool>::type = false>
void UpdateFrom(const S& src, alloc_type& alloc,
bool copy_const_string=false) {
return Update<UpdateExistentKey>(*(downCast()), src, alloc,
copy_const_string);
}

protected:
sonic_force_inline NodeType* next() noexcept {
return downCast()->nextImpl();
Expand Down Expand Up @@ -1180,4 +1232,68 @@ class GenericNode {
}
};

/*
* @brief merge two json, rfc 7386
* @param alloc Memory Allocator for original
*/
template <
typename T, typename P, typename Allocator,
typename std::enable_if<is_base_of_template<T, GenericNode>::value &&
is_base_of_template<P, GenericNode>::value,
bool>::type = false>
static inline void Merge(T& original, const P& patch, Allocator& alloc,
bool copy_const_string=false) {
if (patch.IsObject()) {
if (!original.IsObject()) {
original.SetObject();
}
original.CreateMap(alloc);
for (auto itr = patch.MemberBegin(), e = patch.MemberEnd(); itr != e;
++itr) {
if (itr->value.IsNull()) {
original.RemoveMember(itr->name.GetStringView());
} else {
auto m = original.FindMember(itr->name.GetStringView());
if (m == original.MemberEnd()) {
T null_node{};
auto n =
original.AddMember(itr->name.GetStringView(), null_node, alloc);
Merge(n->value, itr->value, alloc);
} else {
Merge(m->value, itr->value, alloc);
}
}
}
} else {
original.CopyFrom(patch, alloc, copy_const_string);
}
}

template <
bool UpdateExistentKey = true, typename T, typename S, typename Allocator,
typename std::enable_if<is_base_of_template<T, GenericNode>::value &&
is_base_of_template<S, GenericNode>::value,
bool>::type = false>
static inline void Update(T& original, const S& src, Allocator& alloc,
bool copy_const_string=false) {
if (src.IsObject()) {
if (!original.IsObject()) {
original.CopyFrom(src, alloc, copy_const_string);
} else {
original.CreateMap(alloc);
for (auto itr = src.MemberBegin(), e = src.MemberEnd(); itr != e; ++itr) {
auto m = original.FindMember(itr->name.GetStringView());
if (m == original.MemberEnd()) {
T new_node(itr->value, alloc);
original.AddMember(itr->name.GetStringView(), new_node, alloc);
} else {
Update<UpdateExistentKey>(m->value, itr->value, alloc);
}
}
}
} else if (UpdateExistentKey) {
original.CopyFrom(src, alloc, copy_const_string);
}
}

} // namespace sonic_json
118 changes: 118 additions & 0 deletions tests/node_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -794,4 +794,122 @@ TYPED_TEST(NodeTest, SourceAllocator) {
EXPECT_TRUE(c.GetStringView() == a.GetStringView());
}

template <bool UpdateExistedKey>
void UpdateTestHelper(const std::string& s_dst, const std::string& s_src,
const std::string& s_expect) {
using Doc = sonic_json::Document;
using Node = typename sonic_json::Document::NodeType;

Doc expect;
expect.Parse(s_expect);
{
Doc dst, src;
dst.Parse(s_dst);
src.Parse(s_src);

Update<UpdateExistedKey>((Node&)(dst), (Node&)(src), dst.GetAllocator());
EXPECT_TRUE(dst == expect) << "dst: " << s_dst << std::endl
<< "src: " << s_src << std::endl
<< "expect: " << s_expect << std::endl
<< "actual: " << dst.Dump() << std::endl;
}
{
Doc dst, src;
dst.Parse(s_dst);
EXPECT_FALSE(dst.HasParseError()) << "dst: " << s_dst << std::endl;
src.Parse(s_src);
EXPECT_FALSE(src.HasParseError()) << "src: " << s_src << std::endl;

dst.UpdateFrom<UpdateExistedKey>(src, dst.GetAllocator());
EXPECT_TRUE(dst == expect) << "dst: " << s_dst << std::endl
<< "src: " << s_src << std::endl
<< "expect: " << s_expect << std::endl
<< "actual: " << dst.Dump() << std::endl;
}
}

TEST(NodeTest, Update) {
UpdateTestHelper<true>(R"({"a":1})", R"({"a":2})", R"({"a":2})");
UpdateTestHelper<true>(R"({"a":1})", R"({"b":2})", R"({"a":1, "b":2})");
UpdateTestHelper<true>(R"("a")", R"("b")", R"("b")");
UpdateTestHelper<true>(R"({"a":1})", R"({"a":{"b":1, "b":1}})",
R"({"a":{"b":1, "b":1}})");
UpdateTestHelper<true>(R"({"a":[1,2,3], "b":[], "c":{}})", R"({"a":[]})",
R"({"a":[], "b":[], "c":{}})");

UpdateTestHelper<false>(R"({"a":1})", R"({"a":2})", R"({"a":1})");
UpdateTestHelper<false>(R"({"a":1})", R"({"b":2})", R"({"a":1, "b":2})");
UpdateTestHelper<false>(R"("a")", R"("b")", R"("a")");
UpdateTestHelper<false>(R"({"a":1})", R"({"c":{"b":1, "b":1}})",
R"({"a":1, "c":{"b":1, "b":1}})");
}

void MergeTestHelper(const std::string& original, const std::string& patch,
const std::string& result) {
using Doc = sonic_json::Document;
Doc o, p, r;
o.Parse(original);
EXPECT_FALSE(o.HasParseError()) << "original: " << original << std::endl;
p.Parse(patch);
EXPECT_FALSE(p.HasParseError()) << "patch: " << patch << std::endl;
r.Parse(result);
EXPECT_FALSE(r.HasParseError()) << "result: " << result << std::endl;

o.MergePatch(p, o.GetAllocator());
EXPECT_TRUE(o == r) << "original: " << original << std::endl
<< "patch: " << patch << std::endl
<< "result: " << result << std::endl
<< "actual: " << o.Dump() << std::endl;
}

TEST(NodeTest, Merge) {
MergeTestHelper(R"({"a":"b"})", R"({"a":"c"})", R"({"a":"c"})");
MergeTestHelper(R"({"a":"b"})", R"({"b":"c"})", R"({"a":"b","b":"c"})");
MergeTestHelper(R"({"a":"b"})", R"({"a":null})", R"({})");
MergeTestHelper(R"({"a":"b", "b":"c"})", R"({"a":null})", R"({"b":"c"})");
MergeTestHelper(R"({"a":["b"]})", R"({"a":"c"})", R"({"a":"c"})");
MergeTestHelper(R"({"a":"c"})", R"({"a":["b"]})", R"({"a":["b"]})");
MergeTestHelper(R"({"a":{"b":"c"}})", R"({"a":{"b":"d", "c":null}})",
R"({"a":{"b":"d"}})");
MergeTestHelper(R"({"a":[{"b":"c"}]})", R"({"a":[1]})", R"({"a":[1]})");
MergeTestHelper(R"(["a","b"])", R"(["c", "d"])", R"(["c", "d"])");
MergeTestHelper(R"({"a":"b"})", R"(["c"])", R"(["c"])");
MergeTestHelper(R"({"a":"foo"})", R"(null)", R"(null)");
MergeTestHelper(R"({"a":"foo"})", R"("bar")", R"("bar")");
MergeTestHelper(R"({"e":null})", R"({"a":1})", R"({"e":null, "a":1})");
MergeTestHelper(R"([1,2])", R"({"a":"b", "c":null})", R"({"a":"b"})");
MergeTestHelper(R"({})", R"({"a":{"bb":{"ccc":null}}})",
R"({"a":{"bb":{}}})");
}

void DiffTestHelper(const std::string& from, const std::string& to,
const std::string& expect) {
using Doc = sonic_json::Document;
Doc f, t, e;
EXPECT_FALSE(f.Parse(from).HasParseError()) << from << std::endl;
EXPECT_FALSE(t.Parse(to).HasParseError()) << to << std::endl;
EXPECT_FALSE(e.Parse(expect).HasParseError()) << expect << std::endl;
Doc patch = Diff(f, t);
EXPECT_TRUE(patch == e) << "From json: " << from << std::endl
<< "To json: " << to << std::endl
<< "Exepct json: " << expect << std::endl
<< "Patch ans: " << patch.Dump() << std::endl;
}

TEST(NodeTest, Diff) {
DiffTestHelper(R"({"a":"b"})", R"({"a":"c"})", R"({"a":"c"})");
DiffTestHelper(R"({"a":"b"})", R"({"a":"b","b":"c"})", R"({"b":"c"})");
DiffTestHelper(R"({"a":"b"})", R"({})", R"({"a":null})");
DiffTestHelper(R"({"a":["b"]})", R"({"a":"c"})", R"({"a":"c"})");
DiffTestHelper(R"({"a":"c"})", R"({"a":["b"]})", R"({"a":["b"]})");
DiffTestHelper(R"({"a":{"b":"c","c":"d"}})", R"({"a":{"b":"d"}})",
R"({"a":{"b":"d","c":null}})");
DiffTestHelper(R"({"a":[{"b":"c"}]})", R"({"a":[1]})", R"({"a":[1]})");
DiffTestHelper(R"(["a","b"])", R"(["c","d"])", R"(["c","d"])");
DiffTestHelper(R"({"a":"b"})", R"(["c"])", R"(["c"])");
DiffTestHelper(R"({"a":"foo"})", R"(null)", R"(null)");
DiffTestHelper(R"({"a":"foo"})", R"("bar")", R"("bar")");
DiffTestHelper(R"({})", R"({"a":{"bb":{"ccc":null}}})",
R"({"a":{"bb":{"ccc":null}}})");
}
} // namespace