in AirLib/include/common/common_utils/json.hpp [12472:12731]
basic_json patch(const basic_json& json_patch) const
{
// make a working copy to apply the patch to
basic_json result = *this;
// the valid JSON Patch operations
enum class patch_operations {add, remove, replace, move, copy, test, invalid};
const auto get_op = [](const std::string op)
{
if (op == "add")
{
return patch_operations::add;
}
if (op == "remove")
{
return patch_operations::remove;
}
if (op == "replace")
{
return patch_operations::replace;
}
if (op == "move")
{
return patch_operations::move;
}
if (op == "copy")
{
return patch_operations::copy;
}
if (op == "test")
{
return patch_operations::test;
}
return patch_operations::invalid;
};
// wrapper for "add" operation; add value at ptr
const auto operation_add = [&result](json_pointer & ptr, basic_json val)
{
// adding to the root of the target document means replacing it
if (ptr.is_root())
{
result = val;
}
else
{
// make sure the top element of the pointer exists
json_pointer top_pointer = ptr.top();
if (top_pointer != ptr)
{
result.at(top_pointer);
}
// get reference to parent of JSON pointer ptr
const auto last_path = ptr.pop_back();
basic_json& parent = result[ptr];
switch (parent.m_type)
{
case value_t::null:
case value_t::object:
{
// use operator[] to add value
parent[last_path] = val;
break;
}
case value_t::array:
{
if (last_path == "-")
{
// special case: append to back
parent.push_back(val);
}
else
{
const auto idx = std::stoi(last_path);
if (static_cast<size_type>(idx) > parent.size())
{
// avoid undefined behavior
JSON_THROW(std::out_of_range("array index " + std::to_string(idx) + " is out of range"));
}
else
{
// default case: insert add offset
parent.insert(parent.begin() + static_cast<difference_type>(idx), val);
}
}
break;
}
default:
{
// if there exists a parent it cannot be primitive
assert(false); // LCOV_EXCL_LINE
}
}
}
};
// wrapper for "remove" operation; remove value at ptr
const auto operation_remove = [&result](json_pointer & ptr)
{
// get reference to parent of JSON pointer ptr
const auto last_path = ptr.pop_back();
basic_json& parent = result.at(ptr);
// remove child
if (parent.is_object())
{
// perform range check
auto it = parent.find(last_path);
if (it != parent.end())
{
parent.erase(it);
}
else
{
JSON_THROW(std::out_of_range("key '" + last_path + "' not found"));
}
}
else if (parent.is_array())
{
// note erase performs range check
parent.erase(static_cast<size_type>(std::stoi(last_path)));
}
};
// type check
if (not json_patch.is_array())
{
// a JSON patch must be an array of objects
JSON_THROW(std::invalid_argument("JSON patch must be an array of objects"));
}
// iterate and apply the operations
for (const auto& val : json_patch)
{
// wrapper to get a value for an operation
const auto get_value = [&val](const std::string & op,
const std::string & member,
bool string_type) -> basic_json&
{
// find value
auto it = val.m_value.object->find(member);
// context-sensitive error message
const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'";
// check if desired value is present
if (it == val.m_value.object->end())
{
JSON_THROW(std::invalid_argument(error_msg + " must have member '" + member + "'"));
}
// check if result is of type string
if (string_type and not it->second.is_string())
{
JSON_THROW(std::invalid_argument(error_msg + " must have string member '" + member + "'"));
}
// no error: return value
return it->second;
};
// type check
if (not val.is_object())
{
JSON_THROW(std::invalid_argument("JSON patch must be an array of objects"));
}
// collect mandatory members
const std::string op = get_value("op", "op", true);
const std::string path = get_value(op, "path", true);
json_pointer ptr(path);
switch (get_op(op))
{
case patch_operations::add:
{
operation_add(ptr, get_value("add", "value", false));
break;
}
case patch_operations::remove:
{
operation_remove(ptr);
break;
}
case patch_operations::replace:
{
// the "path" location must exist - use at()
result.at(ptr) = get_value("replace", "value", false);
break;
}
case patch_operations::move:
{
const std::string from_path = get_value("move", "from", true);
json_pointer from_ptr(from_path);
// the "from" location must exist - use at()
basic_json v = result.at(from_ptr);
// The move operation is functionally identical to a
// "remove" operation on the "from" location, followed
// immediately by an "add" operation at the target
// location with the value that was just removed.
operation_remove(from_ptr);
operation_add(ptr, v);
break;
}
case patch_operations::copy:
{
const std::string from_path = get_value("copy", "from", true);;
const json_pointer from_ptr(from_path);
// the "from" location must exist - use at()
result[ptr] = result.at(from_ptr);
break;
}
case patch_operations::test:
{
bool success = false;
JSON_TRY
{
// check if "value" matches the one at "path"
// the "path" location must exist - use at()
success = (result.at(ptr) == get_value("test", "value", false));
}
JSON_CATCH (std::out_of_range&)
{
// ignore out of range errors: success remains false
}
// throw an exception if test fails
if (not success)
{
JSON_THROW(std::domain_error("unsuccessful: " + val.dump()));
}
break;
}
case patch_operations::invalid:
{
// op must be "add", "remove", "replace", "move", "copy", or
// "test"
JSON_THROW(std::invalid_argument("operation value '" + op + "' is invalid"));
}
}
}
return result;
}