mirror of
https://github.com/Kitware/CMake.git
synced 2026-08-04 14:50:23 +00:00
Merge topic 'json-partial-equals'
b14da400de string(JSON): Add PARTIAL_EQUAL for subset style JSON comparisons
Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !11725
This commit is contained in:
@@ -59,6 +59,8 @@ Synopsis
|
||||
<member|index> [<member|index> ...] <value>)
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-var>]
|
||||
`EQUAL <JSON-EQUAL_>`__ <json-string1> <json-string2>)
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-var>]
|
||||
`PARTIAL_EQUAL <JSON-EQUAL_>`__ <json-string1> <json-string2>)
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-var>]
|
||||
`STRING_ENCODE <STRING-ENCODE_>`__ <string>)
|
||||
|
||||
@@ -637,6 +639,42 @@ string is passed as a single argument even if it contains semicolons.
|
||||
will be set to a true value if the JSON objects are considered equal,
|
||||
or a false value otherwise.
|
||||
|
||||
.. signature::
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-var>]
|
||||
PARTIAL_EQUAL <json-pattern> <json-actual>)
|
||||
:target: JSON-PARTIAL_EQUAL
|
||||
|
||||
.. versionadded:: 4.4
|
||||
|
||||
Compare two JSON values using *partial* equality. The first argument
|
||||
(``<json-pattern>``) is treated as a pattern to match against the second
|
||||
argument (``<json-actual>``), which is treated as the complete JSON value.
|
||||
The contents of ``<json-pattern>`` and ``<json-actual>`` must be valid JSON.
|
||||
|
||||
The ``<out-var>`` will be set to a true value if ``<json-pattern>`` is
|
||||
contained within ``<json-actual>`` according to the rules below, or a false
|
||||
value otherwise.
|
||||
|
||||
Partial equality is not symmetric. In general,
|
||||
``PARTIAL_EQUAL(A, B)`` and ``PARTIAL_EQUAL(B, A)`` may produce different
|
||||
results.
|
||||
|
||||
Matching rules:
|
||||
|
||||
* **Scalars** (null, number, string, boolean) must match exactly.
|
||||
* **Objects** match if every member in ``<json-pattern>`` exists in
|
||||
``<json-actual>`` and the corresponding values also match recursively.
|
||||
Extra members in ``<json-actual>`` are ignored.
|
||||
* **Arrays** match if every element in ``<json-pattern>`` matches a
|
||||
distinct element in ``<json-actual>`` recursively. Once an element in
|
||||
``<json-actual>`` successfully matches an element in ``<json-pattern>``,
|
||||
it is consumed and cannot be reused to match any subsequent elements.
|
||||
Array element order is not significant. Extra elements in
|
||||
``<json-actual>`` are ignored.
|
||||
|
||||
Type differences do not match (for example, an object does not match an
|
||||
array, and a string does not match a number).
|
||||
|
||||
.. signature::
|
||||
string(JSON <out-var> [ERROR_VARIABLE <error-var>]
|
||||
STRING_ENCODE <string>)
|
||||
|
||||
5
Help/release/dev/json-partial-equals.rst
Normal file
5
Help/release/dev/json-partial-equals.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
json-partial-equals
|
||||
-------------------
|
||||
|
||||
* The :command:`string(JSON ... PARTIAL_EQUAL) <string(JSON-PARTIAL_EQUAL)>`
|
||||
command was added to compare two JSON values using *partial* equality.
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "cmStringCommand.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
@@ -928,6 +929,56 @@ std::string WriteJson(Json::Value const& value)
|
||||
return Json::writeString(writer, value);
|
||||
}
|
||||
|
||||
bool JsonPartialMatch(Json::Value const& pattern, Json::Value const& actual)
|
||||
{
|
||||
if (pattern.type() != actual.type()) {
|
||||
return false;
|
||||
}
|
||||
switch (pattern.type()) {
|
||||
case Json::nullValue:
|
||||
case Json::intValue:
|
||||
case Json::uintValue:
|
||||
case Json::realValue:
|
||||
case Json::stringValue:
|
||||
case Json::booleanValue:
|
||||
return pattern == actual;
|
||||
|
||||
case Json::objectValue: {
|
||||
std::vector<std::string> const keys = pattern.getMemberNames();
|
||||
return std::all_of(keys.begin(), keys.end(),
|
||||
[&](std::string const& key) {
|
||||
return actual.isMember(key) &&
|
||||
JsonPartialMatch(pattern[key], actual[key]);
|
||||
});
|
||||
}
|
||||
|
||||
case Json::arrayValue: {
|
||||
if (actual.size() < pattern.size()) {
|
||||
return false;
|
||||
}
|
||||
std::vector<bool> matched(actual.size(), false);
|
||||
for (Json::Value const& p : pattern) {
|
||||
bool found = false;
|
||||
for (Json::ArrayIndex j = 0; j < actual.size(); ++j) {
|
||||
if (matched[j]) {
|
||||
continue;
|
||||
}
|
||||
if (JsonPartialMatch(p, actual[j])) {
|
||||
matched[j] = true;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool HandleJSONCommand(std::vector<std::string> const& arguments,
|
||||
@@ -954,11 +1005,12 @@ bool HandleJSONCommand(std::vector<std::string> const& arguments,
|
||||
auto const& mode = args.PopFront("missing mode argument"_s);
|
||||
if (mode != "GET"_s && mode != "GET_RAW"_s && mode != "TYPE"_s &&
|
||||
mode != "MEMBER"_s && mode != "LENGTH"_s && mode != "REMOVE"_s &&
|
||||
mode != "SET"_s && mode != "EQUAL"_s && mode != "STRING_ENCODE"_s) {
|
||||
mode != "SET"_s && mode != "EQUAL"_s && mode != "STRING_ENCODE"_s &&
|
||||
mode != "PARTIAL_EQUAL"_s) {
|
||||
throw json_error(cmStrCat(
|
||||
"got an invalid mode '"_s, mode,
|
||||
"', expected one of GET, GET_RAW, TYPE, MEMBER, LENGTH, REMOVE, SET, "
|
||||
" EQUAL, STRING_ENCODE"_s));
|
||||
" EQUAL, PARTIAL_EQUAL, STRING_ENCODE"_s));
|
||||
}
|
||||
|
||||
auto const& jsonstr = args.PopFront("missing json string argument"_s);
|
||||
@@ -1067,6 +1119,12 @@ bool HandleJSONCommand(std::vector<std::string> const& arguments,
|
||||
args.PopFront("missing second json string argument"_s);
|
||||
Json::Value json2 = ReadJson(jsonstr2);
|
||||
makefile.AddDefinitionBool(*outputVariable, json == json2);
|
||||
} else if (mode == "PARTIAL_EQUAL"_s) {
|
||||
auto const& jsonstr2 =
|
||||
args.PopFront("missing second json string argument"_s);
|
||||
Json::Value json2 = ReadJson(jsonstr2);
|
||||
makefile.AddDefinitionBool(*outputVariable,
|
||||
JsonPartialMatch(json, json2));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -393,3 +393,320 @@ string(JSON result STRING_ENCODE {})
|
||||
assert_strequal("${result}" "\"{}\"")
|
||||
string(JSON result STRING_ENCODE [])
|
||||
assert_strequal("${result}" "\"[]\"")
|
||||
|
||||
string(JSON result PARTIAL_EQUAL
|
||||
[=[
|
||||
{
|
||||
"foo":"bar"
|
||||
}
|
||||
]=]
|
||||
[=[
|
||||
{
|
||||
"foo": "bar",
|
||||
"extra": 1
|
||||
}
|
||||
]=])
|
||||
|
||||
if(NOT result)
|
||||
message(SEND_ERROR "Expected ON got ${result}")
|
||||
endif()
|
||||
|
||||
string(JSON result PARTIAL_EQUAL
|
||||
[=[
|
||||
{
|
||||
"foo":"bar"
|
||||
}
|
||||
]=]
|
||||
[=[
|
||||
{
|
||||
"foo1": "bar"
|
||||
}
|
||||
]=])
|
||||
if(result)
|
||||
message(SEND_ERROR "EXPECTED OFF got ${result}")
|
||||
endif()
|
||||
|
||||
string(JSON result PARTIAL_EQUAL
|
||||
[=[
|
||||
{
|
||||
"types" : {
|
||||
"number" : 5
|
||||
}
|
||||
}
|
||||
]=]
|
||||
[=[
|
||||
{
|
||||
"foo" : "bar",
|
||||
"array" : [5, "val", {"some": "other"}, null],
|
||||
"types" : {
|
||||
"null" : null,
|
||||
"number" : 5,
|
||||
"string" : "foo",
|
||||
"boolean" : false,
|
||||
"array" : [1,2,3],
|
||||
"object" : {}
|
||||
},
|
||||
"values" : {
|
||||
"null" : null,
|
||||
"number" : 5,
|
||||
"string" : "foo",
|
||||
"false" : false,
|
||||
"true" : true
|
||||
},
|
||||
"object": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"special" : {
|
||||
"foo;bar" : "value1",
|
||||
";" : "value2",
|
||||
"semicolon" : ";",
|
||||
"list" : ["one", "two;three", "four"],
|
||||
"quote" : "\"",
|
||||
"\"" : "quote",
|
||||
"backslash" : "\\",
|
||||
"\\" : "backslash",
|
||||
"slash" : "\/",
|
||||
"\/" : "slash",
|
||||
"newline" : "\n",
|
||||
"\n" : "newline",
|
||||
"return" : "\r",
|
||||
"\r" : "return",
|
||||
"tab" : "\t",
|
||||
"\t" : "tab",
|
||||
"backspace" : "\b",
|
||||
"\b" : "backspace",
|
||||
"formfeed" : "\f",
|
||||
"\f" : "formfeed"
|
||||
}
|
||||
}
|
||||
]=])
|
||||
if(NOT result)
|
||||
message(SEND_ERROR "EXPECTED ON got ${result} for nested subset")
|
||||
endif()
|
||||
|
||||
string(JSON result PARTIAL_EQUAL
|
||||
[=[
|
||||
{
|
||||
"types" : {
|
||||
"number" : 6
|
||||
}
|
||||
}
|
||||
]=]
|
||||
[=[
|
||||
{
|
||||
"foo" : "bar",
|
||||
"array" : [5, "val", {"some": "other"}, null],
|
||||
"types" : {
|
||||
"null" : null,
|
||||
"number" : 5,
|
||||
"string" : "foo",
|
||||
"boolean" : false,
|
||||
"array" : [1,2,3],
|
||||
"object" : {}
|
||||
},
|
||||
"values" : {
|
||||
"null" : null,
|
||||
"number" : 5,
|
||||
"string" : "foo",
|
||||
"false" : false,
|
||||
"true" : true
|
||||
},
|
||||
"object": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"special" : {
|
||||
"foo;bar" : "value1",
|
||||
";" : "value2",
|
||||
"semicolon" : ";",
|
||||
"list" : ["one", "two;three", "four"],
|
||||
"quote" : "\"",
|
||||
"\"" : "quote",
|
||||
"backslash" : "\\",
|
||||
"\\" : "backslash",
|
||||
"slash" : "\/",
|
||||
"\/" : "slash",
|
||||
"newline" : "\n",
|
||||
"\n" : "newline",
|
||||
"return" : "\r",
|
||||
"\r" : "return",
|
||||
"tab" : "\t",
|
||||
"\t" : "tab",
|
||||
"backspace" : "\b",
|
||||
"\b" : "backspace",
|
||||
"formfeed" : "\f",
|
||||
"\f" : "formfeed"
|
||||
}
|
||||
}
|
||||
]=])
|
||||
if(result)
|
||||
message(SEND_ERROR "EXPECTED OFF got ${result} for nested mismatch")
|
||||
endif()
|
||||
|
||||
string(JSON result PARTIAL_EQUAL
|
||||
[=[
|
||||
[
|
||||
2,
|
||||
1
|
||||
]
|
||||
]=]
|
||||
[=[
|
||||
[
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
]=])
|
||||
if(NOT result)
|
||||
message(SEND_ERROR "Expected ON got ${result} for unordered array subset")
|
||||
endif()
|
||||
|
||||
string(JSON result PARTIAL_EQUAL
|
||||
[=[
|
||||
[
|
||||
2,
|
||||
4
|
||||
]
|
||||
]=]
|
||||
[=[
|
||||
[
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
]=])
|
||||
if(result)
|
||||
message(SEND_ERROR "Expected OFF got ${result} for missing array element")
|
||||
endif()
|
||||
|
||||
string(JSON result PARTIAL_EQUAL
|
||||
[=[
|
||||
[
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
]=]
|
||||
[=[
|
||||
[
|
||||
1,
|
||||
2
|
||||
]
|
||||
]=])
|
||||
if(result)
|
||||
message(SEND_ERROR "Expected OFF got ${result} for pattern larger than actual")
|
||||
endif()
|
||||
|
||||
string(JSON result PARTIAL_EQUAL
|
||||
[=[
|
||||
[
|
||||
1,
|
||||
1
|
||||
]
|
||||
]=]
|
||||
[=[
|
||||
[
|
||||
1,
|
||||
2,
|
||||
1
|
||||
]
|
||||
]=])
|
||||
if(NOT result)
|
||||
message(SEND_ERROR "EXPECTED ON got ${result} for duplicate match")
|
||||
endif()
|
||||
|
||||
string(JSON result PARTIAL_EQUAL
|
||||
[=[
|
||||
[
|
||||
{
|
||||
"id":2
|
||||
},
|
||||
{
|
||||
"id":1
|
||||
}
|
||||
]
|
||||
]=]
|
||||
[=[
|
||||
[
|
||||
{
|
||||
"id":1,
|
||||
"name":"A"
|
||||
},
|
||||
{
|
||||
"id":2,
|
||||
"name":"B"
|
||||
},
|
||||
{
|
||||
"id":3,
|
||||
"name":"C"
|
||||
}
|
||||
]
|
||||
]=])
|
||||
if(NOT result)
|
||||
message(SEND_ERROR "EXPECTED ON got ${result} for array of object subset")
|
||||
endif()
|
||||
|
||||
string(JSON result PARTIAL_EQUAL
|
||||
[=[
|
||||
[
|
||||
{
|
||||
"id":4
|
||||
}
|
||||
]
|
||||
]=]
|
||||
[=[
|
||||
[
|
||||
{
|
||||
"id":1
|
||||
},
|
||||
{
|
||||
"id":2
|
||||
}
|
||||
]
|
||||
]=])
|
||||
if(result)
|
||||
message(SEND_ERROR "Expected OFF got ${result} for array of object mismatch")
|
||||
endif()
|
||||
|
||||
string(JSON result PARTIAL_EQUAL
|
||||
[=[
|
||||
{
|
||||
"tags": [
|
||||
"b",
|
||||
"a"
|
||||
]
|
||||
}
|
||||
]=]
|
||||
[=[
|
||||
{
|
||||
"tags": [
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
],
|
||||
"other": 123
|
||||
}
|
||||
]=])
|
||||
if(NOT result)
|
||||
message(SEND_ERROR "EXPECTED ON got ${result} for object+array partial match")
|
||||
endif()
|
||||
|
||||
string(JSON result PARTIAL_EQUAL
|
||||
[=[
|
||||
{
|
||||
"tags": [
|
||||
"a",
|
||||
"a"
|
||||
]
|
||||
}
|
||||
]=]
|
||||
[=[
|
||||
{
|
||||
"tags": [
|
||||
"a",
|
||||
"b"
|
||||
]
|
||||
}
|
||||
]=])
|
||||
if(result)
|
||||
message(SEND_ERROR "Expected OFF got ${result} for duplicate array mismatch in object")
|
||||
endif()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
CMake Error at JSONWrongMode\.cmake:1 \(string\):
|
||||
string sub-command JSON got an invalid mode 'FOO', expected one of GET,
|
||||
GET_RAW, TYPE, MEMBER, LENGTH, REMOVE, SET, EQUAL, STRING_ENCODE\.
|
||||
GET_RAW, TYPE, MEMBER, LENGTH, REMOVE, SET, EQUAL, PARTIAL_EQUAL,
|
||||
STRING_ENCODE\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:[0-9]+ \(include\)
|
||||
|
||||
Reference in New Issue
Block a user