Add option to automatically extract value from JSON query result

This commit is contained in:
WarmUpTill 2026-06-24 20:59:00 +02:00 committed by WarmUpTill
parent a04c51397d
commit e4f6d94247
6 changed files with 97 additions and 2 deletions

View File

@ -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."

View File

@ -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<int> &)),
this, SLOT(JsonIndexChanged(const NumberVariable<int> &)));
QWidget::connect(_jsonExtractSingle, SIGNAL(stateChanged(int)), this,
SLOT(JsonQueryExtractSingleChanged(int)));
const std::unordered_map<std::string, QWidget *> 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<int> &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();

View File

@ -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<int> &);
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<MacroActionVariable> _entryData;

View File

@ -99,4 +99,23 @@ std::optional<std::string> AccessJsonArrayIndex(const std::string &jsonStr,
return {};
}
std::optional<std::string>
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<std::string>();
}
return result.dump();
} catch (const nlohmann::json::exception &) {
return {};
}
return {};
}
} // namespace advss

View File

@ -17,5 +17,7 @@ EXPORT std::optional<std::string> QueryJson(const std::string &json,
const std::string &query);
EXPORT std::optional<std::string> AccessJsonArrayIndex(const std::string &json,
const int index);
EXPORT std::optional<std::string>
ExtractSingleJsonArrayElement(const std::string &json);
} // namespace advss

View File

@ -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}");
}