diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index c7d78e93..147d8e0d 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -1288,6 +1288,8 @@ AdvSceneSwitcher.action.variable.type.stringLength="Set to length of string" AdvSceneSwitcher.action.variable.type.extractJsonField="Extract JSON field with name" AdvSceneSwitcher.action.variable.type.queryJson="Query JSON" AdvSceneSwitcher.action.variable.type.queryJson.info="You can use \"JSONPath\" (RFC 9535) syntax here.\nSo, for example:\n\n • $['books'][0]['category']\n • $.books[0].category\n\nIf the query or the JSON should be invalid, the variable value will not be changed.\nIf the input is valid, the result of the query will always be a JSON array." +AdvSceneSwitcher.action.variable.type.queryJson.extractSingle="Extract value if single result" +AdvSceneSwitcher.action.variable.type.queryJson.extractSingle.info="When enabled and the query returns exactly one result, the value is extracted from the result array and stored directly.\nIf the query matches zero or more than one element, the full JSON array is stored as usual." AdvSceneSwitcher.action.variable.type.accessJsonArray="Access JSON array at index" AdvSceneSwitcher.action.variable.type.setToTempvar="Set to macro property" AdvSceneSwitcher.action.variable.type.setToTempvar.help="This action type will allow you to extract values out of segments of the current macro and assign those values to the selected variable.\nFor example, you can get the current scene name from a scene condition and assign this name to a variable." diff --git a/lib/macro/macro-action-variable.cpp b/lib/macro/macro-action-variable.cpp index 57bb3495..35a66461 100644 --- a/lib/macro/macro-action-variable.cpp +++ b/lib/macro/macro-action-variable.cpp @@ -447,7 +447,19 @@ bool MacroActionVariable::PerformAction() if (!value.has_value()) { return true; } - var->SetValue(*value); + + if (!_jsonQueryExtractSingle) { + var->SetValue(*value); + return true; + } + + auto extracted = ExtractSingleJsonArrayElement(*value); + if (extracted.has_value()) { + var->SetValue(*extracted); + } else { + var->SetValue(*value); + } + return true; } case Action::ARRAY_JSON: { @@ -551,6 +563,8 @@ bool MacroActionVariable::Save(obs_data_t *obj) const obs_data_set_bool(obj, "allowRepeatValues", _allowRepeatValues); _jsonQuery.Save(obj, "jsonQuery"); _jsonIndex.Save(obj, "jsonIndex"); + obs_data_set_bool(obj, "jsonQueryExtractSingle", + _jsonQueryExtractSingle); obs_data_set_int(obj, "version", 1); @@ -611,6 +625,8 @@ bool MacroActionVariable::Load(obs_data_t *obj) _allowRepeatValues = obs_data_get_bool(obj, "allowRepeatValues"); _jsonQuery.Load(obj, "jsonQuery"); _jsonIndex.Load(obj, "jsonIndex"); + _jsonQueryExtractSingle = + obs_data_get_bool(obj, "jsonQueryExtractSingle"); return true; } @@ -904,6 +920,15 @@ MacroActionVariableEdit::MacroActionVariableEdit( "AdvSceneSwitcher.action.variable.type.queryJson.info"), this)), _jsonIndex(new VariableSpinBox(this)), + _jsonExtractSingle(new QCheckBox( + obs_module_text( + "AdvSceneSwitcher.action.variable.type.queryJson.extractSingle"), + this)), + _jsonExtractSingleHelp(new HelpIcon( + obs_module_text( + "AdvSceneSwitcher.action.variable.type.queryJson.extractSingle.info"), + this)), + _jsonQueryLayout(new QVBoxLayout()), _entryLayout(new QHBoxLayout()) { _numValue->setMinimum(-9999999999); @@ -935,6 +960,11 @@ MacroActionVariableEdit::MacroActionVariableEdit( _randomNumberEnd->setMaximum(9999999999); _randomValues->SetMaxStringSize(99999999); _jsonIndex->setMaximum(999); + auto jsonExtractSingleLayout = new QHBoxLayout(); + jsonExtractSingleLayout->addWidget(_jsonExtractSingle); + jsonExtractSingleLayout->addWidget(_jsonExtractSingleHelp); + jsonExtractSingleLayout->addStretch(); + _jsonQueryLayout->addLayout(jsonExtractSingleLayout); QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)), this, SLOT(VariableChanged(const QString &))); @@ -1033,6 +1063,8 @@ MacroActionVariableEdit::MacroActionVariableEdit( _jsonIndex, SIGNAL(NumberVariableChanged(const NumberVariable &)), this, SLOT(JsonIndexChanged(const NumberVariable &))); + QWidget::connect(_jsonExtractSingle, SIGNAL(stateChanged(int)), this, + SLOT(JsonQueryExtractSingleChanged(int))); const std::unordered_map widgetPlaceholders = { {"{{variables}}", _variables}, @@ -1109,6 +1141,7 @@ MacroActionVariableEdit::MacroActionVariableEdit( auto layout = new QVBoxLayout; layout->addLayout(_entryLayout); + layout->addLayout(_jsonQueryLayout); layout->addLayout(_substringLayout); layout->addWidget(_segmentValueStatus); layout->addWidget(_segmentValue); @@ -1180,6 +1213,7 @@ void MacroActionVariableEdit::UpdateEntryData() _randomValues->SetStringList(_entryData->_randomValues); _jsonQuery->setText(_entryData->_jsonQuery); _jsonIndex->SetValue(_entryData->_jsonIndex); + _jsonExtractSingle->setChecked(_entryData->_jsonQueryExtractSingle); SetWidgetVisibility(); } @@ -1545,6 +1579,12 @@ void MacroActionVariableEdit::JsonIndexChanged(const NumberVariable &value) _entryData->_jsonIndex = value; } +void MacroActionVariableEdit::JsonQueryExtractSingleChanged(int value) +{ + GUARD_LOADING_AND_LOCK(); + _entryData->_jsonQueryExtractSingle = value; +} + void MacroActionVariableEdit::SetWidgetVisibility() { if (!_entryData) { @@ -1740,6 +1780,9 @@ void MacroActionVariableEdit::SetWidgetVisibility() MacroActionVariable::Action::QUERY_JSON); _jsonIndex->setVisible(_entryData->_action == MacroActionVariable::Action::ARRAY_JSON); + SetLayoutVisible(_jsonQueryLayout, + _entryData->_action == + MacroActionVariable::Action::QUERY_JSON); adjustSize(); updateGeometry(); diff --git a/lib/macro/macro-action-variable.hpp b/lib/macro/macro-action-variable.hpp index 43d2a740..b68ae3be 100644 --- a/lib/macro/macro-action-variable.hpp +++ b/lib/macro/macro-action-variable.hpp @@ -116,6 +116,7 @@ public: StringVariable _jsonQuery = "$.some.nested.value"; IntVariable _jsonIndex = 0; + bool _jsonQueryExtractSingle = true; private: void DecrementCurrentSegmentVariableRef(); @@ -187,6 +188,7 @@ private slots: void AllowRepeatValuesChanged(int); void JsonQueryChanged(); void JsonIndexChanged(const NumberVariable &); + void JsonQueryExtractSingleChanged(int); signals: void HeaderInfoChanged(const QString &); @@ -239,8 +241,11 @@ private: QCheckBox *_allowRepeatValues; QVBoxLayout *_randomValueLayout; VariableLineEdit *_jsonQuery; - QLabel *_jsonQueryHelp; + HelpIcon *_jsonQueryHelp; VariableSpinBox *_jsonIndex; + QCheckBox *_jsonExtractSingle; + HelpIcon *_jsonExtractSingleHelp; + QVBoxLayout *_jsonQueryLayout; QHBoxLayout *_entryLayout; std::shared_ptr _entryData; diff --git a/lib/utils/json-helpers.cpp b/lib/utils/json-helpers.cpp index 1ca8486b..5987afa0 100644 --- a/lib/utils/json-helpers.cpp +++ b/lib/utils/json-helpers.cpp @@ -99,4 +99,23 @@ std::optional AccessJsonArrayIndex(const std::string &jsonStr, return {}; } +std::optional +ExtractSingleJsonArrayElement(const std::string &jsonStr) +{ + try { + nlohmann::json json = nlohmann::json::parse(jsonStr); + if (!json.is_array() || json.size() != 1) { + return {}; + } + auto result = json.at(0); + if (result.is_string()) { + return result.get(); + } + return result.dump(); + } catch (const nlohmann::json::exception &) { + return {}; + } + return {}; +} + } // namespace advss diff --git a/lib/utils/json-helpers.hpp b/lib/utils/json-helpers.hpp index 0ac90993..be66af58 100644 --- a/lib/utils/json-helpers.hpp +++ b/lib/utils/json-helpers.hpp @@ -17,5 +17,7 @@ EXPORT std::optional QueryJson(const std::string &json, const std::string &query); EXPORT std::optional AccessJsonArrayIndex(const std::string &json, const int index); +EXPORT std::optional +ExtractSingleJsonArrayElement(const std::string &json); } // namespace advss diff --git a/tests/test-json.cpp b/tests/test-json.cpp index 0f7e11ca..8dd374b1 100644 --- a/tests/test-json.cpp +++ b/tests/test-json.cpp @@ -103,3 +103,27 @@ TEST_CASE("AccessJsonArrayIndex", "[json-helpers]") result = advss::AccessJsonArrayIndex("[\"1\", \"2\"]", 1); REQUIRE(*result == "2"); } + +TEST_CASE("ExtractSingleJsonArrayElement", "[json-helpers]") +{ + auto result = advss::ExtractSingleJsonArrayElement("invalid json"); + REQUIRE_FALSE(result); + + result = advss::ExtractSingleJsonArrayElement("{}"); + REQUIRE_FALSE(result); + + result = advss::ExtractSingleJsonArrayElement("[]"); + REQUIRE_FALSE(result); + + result = advss::ExtractSingleJsonArrayElement("[1, 2]"); + REQUIRE_FALSE(result); + + result = advss::ExtractSingleJsonArrayElement("[42]"); + REQUIRE(*result == "42"); + + result = advss::ExtractSingleJsonArrayElement("[\"hello\"]"); + REQUIRE(*result == "hello"); + + result = advss::ExtractSingleJsonArrayElement("[{\"key\": 1}]"); + REQUIRE(*result == "{\"key\":1}"); +}