From 06e6f1e69f3f245473663f12307ef0bcd244ef99 Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Tue, 16 Dec 2025 10:11:30 -0500 Subject: [PATCH] string(JSON): Add STRING_ENCODE mode --- Help/command/string.rst | 10 + Help/release/dev/string-json-improvements.rst | 2 +- Source/cmStringCommand.cxx | 197 +++++++++--------- Tests/RunCMake/string/JSON.cmake | 16 ++ .../RunCMake/string/JSONWrongMode-stderr.txt | 2 +- 5 files changed, 130 insertions(+), 97 deletions(-) diff --git a/Help/command/string.rst b/Help/command/string.rst index b369f0282c..f0fb53dc3b 100644 --- a/Help/command/string.rst +++ b/Help/command/string.rst @@ -56,6 +56,8 @@ Synopsis [ ...] ) string(JSON [ERROR_VARIABLE ] `EQUAL `__ ) + string(JSON [ERROR_VARIABLE ] + `STRING_ENCODE `__ ) Search and Replace ^^^^^^^^^^^^^^^^^^ @@ -629,3 +631,11 @@ string is passed as a single argument even if it contains semicolons. and ```` should be valid JSON. The ```` will be set to a true value if the JSON objects are considered equal, or a false value otherwise. + +.. signature:: + string(JSON [ERROR_VARIABLE ] + STRING_ENCODE ) + :target: STRING-ENCODE + + Turn a raw string into a JSON string surrounded by quotes. Special characters + will be properly escaped inside the JSON string. diff --git a/Help/release/dev/string-json-improvements.rst b/Help/release/dev/string-json-improvements.rst index 4fa37a2bc2..3315e68dcb 100644 --- a/Help/release/dev/string-json-improvements.rst +++ b/Help/release/dev/string-json-improvements.rst @@ -1,4 +1,4 @@ string-json-improvements ------------------------ -* The :command:`string(JSON)` command gained a new ``GET_RAW`` mode. +* The :command:`string(JSON)` command gained new ``GET_RAW`` and ``STRING_ENCODE`` modes. diff --git a/Source/cmStringCommand.cxx b/Source/cmStringCommand.cxx index 8116153c5d..1a888c15fa 100644 --- a/Source/cmStringCommand.cxx +++ b/Source/cmStringCommand.cxx @@ -954,113 +954,120 @@ bool HandleJSONCommand(std::vector 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 != "SET"_s && mode != "EQUAL"_s && mode != "STRING_ENCODE"_s) { throw json_error(cmStrCat( "got an invalid mode '"_s, mode, "', expected one of GET, GET_RAW, TYPE, MEMBER, LENGTH, REMOVE, SET, " - " EQUAL"_s)); + " EQUAL, STRING_ENCODE"_s)); } auto const& jsonstr = args.PopFront("missing json string argument"_s); - Json::Value json = ReadJson(jsonstr); - if (mode == "GET"_s) { - auto const& value = ResolvePath(json, args); - if (value.isObject() || value.isArray()) { - makefile.AddDefinition(*outputVariable, WriteJson(value)); - } else if (value.isBool()) { - makefile.AddDefinitionBool(*outputVariable, value.asBool()); - } else { - makefile.AddDefinition(*outputVariable, value.asString()); - } - - } else if (mode == "GET_RAW"_s) { - auto const& value = ResolvePath(json, args); - makefile.AddDefinition(*outputVariable, WriteJson(value)); - - } else if (mode == "TYPE"_s) { - auto const& value = ResolvePath(json, args); - makefile.AddDefinition(*outputVariable, JsonTypeToString(value.type())); - - } else if (mode == "MEMBER"_s) { - auto const& indexStr = args.PopBack("missing member index"_s); - auto const& value = ResolvePath(json, args); - if (!value.isObject()) { - throw json_error( - cmStrCat("MEMBER needs to be called with an element of " - "type OBJECT, got "_s, - JsonTypeToString(value.type())), - args); - } - auto const index = ParseIndex( - indexStr, Args{ args.begin(), args.end() + 1 }, value.size()); - auto const memIt = std::next(value.begin(), index); - makefile.AddDefinition(*outputVariable, memIt.name()); - - } else if (mode == "LENGTH"_s) { - auto const& value = ResolvePath(json, args); - if (!value.isArray() && !value.isObject()) { - throw json_error(cmStrCat("LENGTH needs to be called with an " - "element of type ARRAY or OBJECT, got "_s, - JsonTypeToString(value.type())), - args); - } - - cmAlphaNum sizeStr{ value.size() }; - makefile.AddDefinition(*outputVariable, sizeStr.View()); - - } else if (mode == "REMOVE"_s) { - auto const& toRemove = - args.PopBack("missing member or index to remove"_s); - auto& value = ResolvePath(json, args); - - if (value.isArray()) { - auto const index = ParseIndex( - toRemove, Args{ args.begin(), args.end() + 1 }, value.size()); - Json::Value removed; - value.removeIndex(index, &removed); - - } else if (value.isObject()) { - Json::Value removed; - value.removeMember(toRemove, &removed); - - } else { - throw json_error(cmStrCat("REMOVE needs to be called with an " - "element of type ARRAY or OBJECT, got "_s, - JsonTypeToString(value.type())), - args); - } + if (mode == "STRING_ENCODE"_s) { + Json::Value json(jsonstr); makefile.AddDefinition(*outputVariable, WriteJson(json)); + } else { + Json::Value json = ReadJson(jsonstr); - } else if (mode == "SET"_s) { - auto const& newValueStr = args.PopBack("missing new value remove"_s); - auto const& toAdd = args.PopBack("missing member name to add"_s); - auto& value = ResolvePath(json, args); - - Json::Value newValue = ReadJson(newValueStr); - if (value.isObject()) { - value[toAdd] = newValue; - } else if (value.isArray()) { - auto const index = - ParseIndex(toAdd, Args{ args.begin(), args.end() + 1 }); - if (value.isValidIndex(index)) { - value[static_cast(index)] = newValue; + if (mode == "GET"_s) { + auto const& value = ResolvePath(json, args); + if (value.isObject() || value.isArray()) { + makefile.AddDefinition(*outputVariable, WriteJson(value)); + } else if (value.isBool()) { + makefile.AddDefinitionBool(*outputVariable, value.asBool()); } else { - value.append(newValue); + makefile.AddDefinition(*outputVariable, value.asString()); } - } else { - throw json_error(cmStrCat("SET needs to be called with an " - "element of type OBJECT or ARRAY, got "_s, - JsonTypeToString(value.type()))); + + } else if (mode == "GET_RAW"_s) { + auto const& value = ResolvePath(json, args); + makefile.AddDefinition(*outputVariable, WriteJson(value)); + + } else if (mode == "TYPE"_s) { + auto const& value = ResolvePath(json, args); + makefile.AddDefinition(*outputVariable, + JsonTypeToString(value.type())); + + } else if (mode == "MEMBER"_s) { + auto const& indexStr = args.PopBack("missing member index"_s); + auto const& value = ResolvePath(json, args); + if (!value.isObject()) { + throw json_error( + cmStrCat("MEMBER needs to be called with an element of " + "type OBJECT, got "_s, + JsonTypeToString(value.type())), + args); + } + auto const index = ParseIndex( + indexStr, Args{ args.begin(), args.end() + 1 }, value.size()); + auto const memIt = std::next(value.begin(), index); + makefile.AddDefinition(*outputVariable, memIt.name()); + + } else if (mode == "LENGTH"_s) { + auto const& value = ResolvePath(json, args); + if (!value.isArray() && !value.isObject()) { + throw json_error(cmStrCat("LENGTH needs to be called with an " + "element of type ARRAY or OBJECT, got "_s, + JsonTypeToString(value.type())), + args); + } + + cmAlphaNum sizeStr{ value.size() }; + makefile.AddDefinition(*outputVariable, sizeStr.View()); + + } else if (mode == "REMOVE"_s) { + auto const& toRemove = + args.PopBack("missing member or index to remove"_s); + auto& value = ResolvePath(json, args); + + if (value.isArray()) { + auto const index = ParseIndex( + toRemove, Args{ args.begin(), args.end() + 1 }, value.size()); + Json::Value removed; + value.removeIndex(index, &removed); + + } else if (value.isObject()) { + Json::Value removed; + value.removeMember(toRemove, &removed); + + } else { + throw json_error(cmStrCat("REMOVE needs to be called with an " + "element of type ARRAY or OBJECT, got "_s, + JsonTypeToString(value.type())), + args); + } + makefile.AddDefinition(*outputVariable, WriteJson(json)); + + } else if (mode == "SET"_s) { + auto const& newValueStr = args.PopBack("missing new value remove"_s); + auto const& toAdd = args.PopBack("missing member name to add"_s); + auto& value = ResolvePath(json, args); + + Json::Value newValue = ReadJson(newValueStr); + if (value.isObject()) { + value[toAdd] = newValue; + } else if (value.isArray()) { + auto const index = + ParseIndex(toAdd, Args{ args.begin(), args.end() + 1 }); + if (value.isValidIndex(index)) { + value[static_cast(index)] = newValue; + } else { + value.append(newValue); + } + } else { + throw json_error(cmStrCat("SET needs to be called with an " + "element of type OBJECT or ARRAY, got "_s, + JsonTypeToString(value.type()))); + } + + makefile.AddDefinition(*outputVariable, WriteJson(json)); + + } else if (mode == "EQUAL"_s) { + auto const& jsonstr2 = + args.PopFront("missing second json string argument"_s); + Json::Value json2 = ReadJson(jsonstr2); + makefile.AddDefinitionBool(*outputVariable, json == json2); } - - makefile.AddDefinition(*outputVariable, WriteJson(json)); - - } else if (mode == "EQUAL"_s) { - auto const& jsonstr2 = - args.PopFront("missing second json string argument"_s); - Json::Value json2 = ReadJson(jsonstr2); - makefile.AddDefinitionBool(*outputVariable, json == json2); } } catch (json_error const& e) { diff --git a/Tests/RunCMake/string/JSON.cmake b/Tests/RunCMake/string/JSON.cmake index 365fc474f2..3bfb4d071c 100644 --- a/Tests/RunCMake/string/JSON.cmake +++ b/Tests/RunCMake/string/JSON.cmake @@ -360,3 +360,19 @@ assert_json_equal("${error}" "${result}" "foo" : "bar", "array" : [5, "val", {"some": "other"}, null, "append"] }]=]) + +# Test STRING_ENCODE +string(JSON result STRING_ENCODE Hello) +assert_strequal("${result}" "\"Hello\"") +string(JSON result STRING_ENCODE "\"Hello\"") +assert_strequal("${result}" "\"\\\"Hello\\\"\"") +string(JSON result STRING_ENCODE null) +assert_strequal("${result}" "\"null\"") +string(JSON result STRING_ENCODE 0) +assert_strequal("${result}" "\"0\"") +string(JSON result STRING_ENCODE false) +assert_strequal("${result}" "\"false\"") +string(JSON result STRING_ENCODE {}) +assert_strequal("${result}" "\"{}\"") +string(JSON result STRING_ENCODE []) +assert_strequal("${result}" "\"[]\"") diff --git a/Tests/RunCMake/string/JSONWrongMode-stderr.txt b/Tests/RunCMake/string/JSONWrongMode-stderr.txt index 5a7ff6b041..b37a084f96 100644 --- a/Tests/RunCMake/string/JSONWrongMode-stderr.txt +++ b/Tests/RunCMake/string/JSONWrongMode-stderr.txt @@ -1,5 +1,5 @@ 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\. + GET_RAW, TYPE, MEMBER, LENGTH, REMOVE, SET, EQUAL, STRING_ENCODE\. Call Stack \(most recent call first\): CMakeLists\.txt:[0-9]+ \(include\)