Procházet zdrojové kódy

feat: one path helper on the smartbotic global, for every node to share

fszontagh před 1 měsícem
rodič
revize
516cb4a648

+ 98 - 0
src/runner/engine/script_engine.cpp

@@ -1549,6 +1549,104 @@ void ScriptEngine::setupBuiltinAPIs() {
         return result;
     }, "pick", 2));
 
+    // utils.getFieldValue(data, path) - Read a nested value by dotted path.
+    // Supports a.b.c, arrays via key[0] or a bare numeric segment, and .length
+    // on an array. Returns undefined for anything missing.
+    JS_SetPropertyStr(ctx, utils, "getFieldValue", JS_NewCFunction(ctx, [](JSContext* ctx, JSValue this_val, int argc, JSValue* argv) -> JSValue {
+        if (argc < 1) {
+            return JS_ThrowTypeError(ctx, "utils.getFieldValue requires a value and a path");
+        }
+
+        if (argc < 2 || JS_IsUndefined(argv[1]) || JS_IsNull(argv[1])) {
+            return JS_DupValue(ctx, argv[0]);
+        }
+
+        const char* path_c = JS_ToCString(ctx, argv[1]);
+        if (!path_c) {
+            return JS_EXCEPTION;
+        }
+        std::string path(path_c);
+        JS_FreeCString(ctx, path_c);
+
+        if (path.empty()) {
+            return JS_DupValue(ctx, argv[0]);
+        }
+
+        std::vector<std::string> keys;
+        size_t start = 0;
+        while (true) {
+            size_t dot = path.find('.', start);
+            if (dot == std::string::npos) {
+                keys.push_back(path.substr(start));
+                break;
+            }
+            keys.push_back(path.substr(start, dot - start));
+            start = dot + 1;
+        }
+
+        JSValue current = JS_DupValue(ctx, argv[0]);
+
+        for (const auto& raw_key : keys) {
+            if (JS_IsUndefined(current) || JS_IsNull(current)) {
+                JS_FreeValue(ctx, current);
+                return JS_UNDEFINED;
+            }
+
+            bool is_array = JS_IsArray(ctx, current);
+
+            if (raw_key == "length" && is_array) {
+                JSValue length = JS_GetPropertyStr(ctx, current, "length");
+                JS_FreeValue(ctx, current);
+                current = length;
+                continue;
+            }
+
+            // key[3] - step into the property, then index it
+            std::string key = raw_key;
+            bool has_index = false;
+            int64_t index = 0;
+            size_t bracket = raw_key.find('[');
+            if (bracket != std::string::npos && raw_key.back() == ']') {
+                std::string index_text = raw_key.substr(bracket + 1, raw_key.size() - bracket - 2);
+                if (!index_text.empty() &&
+                    index_text.find_first_not_of("0123456789") == std::string::npos) {
+                    key = raw_key.substr(0, bracket);
+                    index = std::stoll(index_text);
+                    has_index = true;
+                }
+            }
+
+            if (!key.empty()) {
+                bool numeric = key.find_first_not_of("0123456789") == std::string::npos;
+                if (numeric && is_array) {
+                    JSValue element = JS_GetPropertyUint32(ctx, current, (uint32_t)std::stoll(key));
+                    JS_FreeValue(ctx, current);
+                    current = element;
+                } else {
+                    if (!JS_IsObject(current)) {
+                        JS_FreeValue(ctx, current);
+                        return JS_UNDEFINED;
+                    }
+                    JSValue next = JS_GetPropertyStr(ctx, current, key.c_str());
+                    JS_FreeValue(ctx, current);
+                    current = next;
+                }
+            }
+
+            if (has_index) {
+                if (!JS_IsArray(ctx, current)) {
+                    JS_FreeValue(ctx, current);
+                    return JS_UNDEFINED;
+                }
+                JSValue element = JS_GetPropertyUint32(ctx, current, (uint32_t)index);
+                JS_FreeValue(ctx, current);
+                current = element;
+            }
+        }
+
+        return current;
+    }, "getFieldValue", 2));
+
     // utils.omit(obj, keys) - Omit specific keys from an object
     JS_SetPropertyStr(ctx, utils, "omit", JS_NewCFunction(ctx, [](JSContext* ctx, JSValue this_val, int argc, JSValue* argv) -> JSValue {
         if (argc < 2 || !JS_IsObject(argv[0]) || !JS_IsArray(ctx, argv[1])) {

+ 23 - 0
tests/nodes/utils-get-field-value.json

@@ -0,0 +1,23 @@
+{
+  "name": "verify-utils-get-field-value",
+  "nodes": [
+    {"id": "n1", "name": "Trigger", "type": "click-trigger", "position": {"x": 0, "y": 0}, "config": {}},
+    {"id": "n2", "name": "Probe", "type": "code", "position": {"x": 0, "y": 100},
+     "config": {"code": "const d = { a: { b: { c: 7 } }, list: [{ id: 'x' }, { id: 'y' }], flat: [10, 20, 30] };\nconst g = smartbotic.utils.getFieldValue;\nreturn {\n  nested: g(d, 'a.b.c'),\n  bracket: g(d, 'list[1].id'),\n  numeric: g(d, 'flat.2'),\n  length: g(d, 'flat.length'),\n  missing: g(d, 'a.nope.c') === undefined,\n  throughNull: g({ a: null }, 'a.b') === undefined,\n  emptyPath: g(d, '').a.b.c,\n  notAnObject: g(d, 'a.b.c.d') === undefined\n};"}}
+  ],
+  "connections": [
+    {"sourceNodeId": "n1", "sourceOutput": "main", "targetNodeId": "n2", "targetInput": "data"}
+  ],
+  "expect": {
+    "n2": {"status": "completed", "output": {"result": {
+      "nested": 7,
+      "bracket": "y",
+      "numeric": 30,
+      "length": 3,
+      "missing": true,
+      "throughNull": true,
+      "emptyPath": 7,
+      "notAnObject": true
+    }}}
+  }
+}