Javascript:确定未知数组长度并动态映射

Posted

技术标签:

【中文标题】Javascript:确定未知数组长度并动态映射【英文标题】:Javascript: Determine unknown array length and map dynamically 【发布时间】:2015-07-18 18:07:15 【问题描述】:

尽我所能解释我想要做什么。

我有两个模型,我的模型和我收到的 api 响应。当 items api 响应进来时,我需要将它映射到我的模型并插入所有项目。这当然很简单。继承人的问题,我需要这样做而不真正知道我在处理什么。我的代码将在两个字符串中传递,一个是我的模型映射路径,另一个是 api 响应映射路径。

这是两条路径

var myPath = "outputModel.items[].uniqueName"
var apiPath = "items[].name"

基本上对于apiPath 中的所有items,推入myPath 中的items 并设置为uniqueName

归结为,我的代码不知道何时需要映射两个项目,或者即使它们包含数组或简单字段到字段的路径。它们甚至可以包含多个数组,如下所示:

************************ 示例 *************************

var items = [
    
        name: "Hammer",
        skus:[
            num:"12345qwert"
        ]
    ,
    
        name: "Bike",
        skus:[
            num:"asdfghhj",
            num:"zxcvbn"
        ]
    ,
    
        name: "Fork",
        skus:[
            num:"0987dfgh"
        ]
    
]

var outputModel = 
    storeName: "",
    items: [
        
            name: "",
            sku:""
        
    ]
;


outputModel.items[].name = items[].name;
outputModel.items[].sku = items[].skus[].num;

************************ 这是上面的预期结果

var result = 
    storeName: "",
    items: [
        
            name: "Hammer",
            sku:"12345qwert"
        ,
        
            name: "Bike",
            sku:"asdfghhj"
        ,
        
            name: "Bike",
            sku:"zxcvbn"
        ,
        
            name: "Fork",
            sku:"0987dfgh"        
    ]
;

我将获得一组用于映射每个值的路径。在上面的例子中,我得到了两组路径,因为我正在映射两个值。它必须遍历两组数组才能在我的模型中创建单个数组。

问题 - 无论两个模型路径是什么样的,我如何才能动态检测数组并正确移动数据?可能吗?

【问题讨论】:

在你的最后一个例子中,你期望你的输出模型最后是什么样子? 为什么不只有一个结构来输出模型?即使唯一可用的属性是名称,它也可以设置为 [name: 'name']。使用一种类型的结构可能会更容易工作和维护您的应用程序。 @SMcCrohan 我添加了预期的输出。请注意,如果我需要运行一段代码 2 次,我没问题,因为有 2 组路径,如示例 2 @Rob:问题在于,如果左侧和右侧包含不相等数量的数组,则您的符号会模棱两可。看来您只是在使用笛卡尔积?请具体说明应如何处理此类情况。 @Rob outputModel.items[].sku = items[].skus[].num; 有一个小问题,因为在您为名称 Bike 提供的示例中,sku 的值应该是一个 nums 数组,而不仅仅是第一个,因为它在您的例子。基本上在您可以定义适当语义的路径上运行一些正则表达式( bleah ),否则您会遇到很多不一致的情况。这很有趣,但不可行,除非您对归因和路由系统有适当的语义。我明天会尝试给出一个实际的答案。 (顺便说一句,你好@Bergi,好久不见:D) 【参考方案1】:

所以您已经定义了一种小语言来定义一些数据寻址和操作规则。让我们考虑一种方法,它可以让你说

access(apiPath, function(value)  insert(myPath, value); 

access 函数在apiPath 中找到所有需要的项目,然后回调insert,将它们插入myPath。我们的工作是编写创建accessinsert 函数的函数;或者,你可以说,将你的小语言“编译”成我们可以执行的函数。

我们将编写名为make_accessormake_inserter的“编译器”,如下所示:

function make_accessor(program) 

  return function(obj, callback) 

    return function do_segment(obj, segments) 
      var start    = segments.shift()             // Get first segment
      var pieces   = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
      var property = pieces[1];
      var isArray  = pieces[2];                   // [] on end
      obj          = obj[property];               // drill down

      if (!segments.length)                      // last segment; callback
        if (isArray) 
          return obj.forEach(callback);
         else 
          return callback(obj);
        
       else                                     // more segments; recurse
        if (isArray)                             // array--loop over elts
          obj.forEach(function(elt)  do_segment(elt, segments.slice()); );
         else 
          do_segment(obj, segments.slice());      // scalar--continue
        
      
    (obj, program.split('.'));
  ;

我们现在可以通过调用make_accessor('items[].name') 来创建访问器。

接下来,我们来写插入器:

function make_inserter(program) 

  return function(obj, value) 

    return function do_segment(obj, segments) 
      var start    = segments.shift()             // Get first segment
      var pieces   = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
      var property = pieces[1];
      var isArray  = pieces[2];                   // [] on end

      if (segments.length)                       // more segments
        if (!obj[property]) 
          obj[property] = isArray ? [] : ;
        
        do_segment(obj, segments.slice());
       else                                     // last segment
        obj[property] = value;
      
    (obj, program.split('.'));
  ;

现在,您可以将整个逻辑表达为

access = make_accessor('items[].name');
insert = make_inserter('outputModel.items[].uniqueName');

access(apiPath, function(val)  insert(myPath, val); );

【讨论】:

我真的很喜欢你用这个去哪里,但是我无法让它工作。我在 *****Testing Solution****** 中发布了我尝试测试的确切代码 在撕开这个答案后,我做了一些小的修改,但它没有产生正确的结果。此外,它与包含两个数组的路径中断,如示例 2。也许我所做的修改偏离了您的目标路径?好像很接近了。我用有点工作的版本更新了我的问题。 make_inserter 有问题;它需要知道将对象创建为新插入数组的元素。抱歉,添麻烦了。有机会我会解决这个问题的。【参考方案2】:

正如 cmets 中提到的,输入格式没有严格的定义,很难做到完美的错误处理和处理所有极端情况。

这是我的冗长实现,适用于您的示例,但在其他一些情况下可能会失败:

function merge_objects(a, b) 
    var c = , attr;
    for (attr in a)  c[attr] = a[attr]; 
    for (attr in b)  c[attr] = b[attr]; 
    return c;



var id = 
    inner: null,
    name: "id",
    repr: "id",
    type: "map",
    exec: function (input)  return input; 
;

// set output field
function f(outp, mapper) 
    mapper = typeof mapper !== "undefined" ? mapper : id;
    var repr = "f("+outp+","+mapper.repr+")";
    var name = "f("+outp;
    return 
        inner: mapper,
        name: name,
        repr: repr,
        type: "map",
        clone: function(mapper)  return f(outp, mapper); ,
        exec:
        function (input) 
            var out = ;
            out[outp] = mapper.exec(input);
            return out;
        
    ;


// set input field
function p(inp, mapper) 
    var repr = "p("+inp+","+mapper.repr+")";
    var name = "p("+inp;
    return 
        inner: mapper,
        name: name,
        repr: repr,
        type: mapper.type,
        clone: function(mapper)  return p(inp, mapper); ,
        exec: function (input) 
            return mapper.exec(input[inp]);
        
    ;


// process array
function arr(mapper) 
    var repr = "arr("+mapper.repr+")";
    return 
        inner: mapper,
        name: "arr",
        repr: repr,
        type: mapper.type,
        clone: function(mapper)  return arr(mapper); ,
        exec: function (input) 
            var out = [];
            for (var i=0; i<input.length; i++) 
                out.push(mapper.exec(input[i]));
            
            return out;
        
    ;


function combine(m1, m2) 
    var type = (m1.type == "flatmap" || m2.type == "flatmap") ? "flatmap" : "map";
    var repr = "combine("+m1.repr+","+m2.repr+")";
    return 
        inner: null,
        repr: repr,
        type: type,
        name: "combine",
        exec:
        function (input) 
            var out1 = m1.exec(input);
            var out2 = m2.exec(input);
            var out, i, j;


            if (m1.type == "flatmap" && m2.type == "flatmap") 
                out = [];
                for (i=0; i<out1.length; i++) 
                    for (j=0; j<out2.length; j++) 
                        out.push(merge_objects(out1[i], out2[j]));
                    
                
                return out;
            

            if (m1.type == "flatmap" && m2.type != "flatmap") 
                out = [];
                for (i=0; i<out1.length; i++) 
                    out.push(merge_objects(out1[i], out2));
                
                return out;
            

            if (m1.type != "flatmap" && m2.type == "flatmap") 
                out = [];
                for (i=0; i<out2.length; i++) 
                    out.push(merge_objects(out2[i], out1));
                
                return out;
            

            return merge_objects(out1, out2);
        
    ;


function flatmap(mapper) 
    var repr = "flatmap("+mapper.repr+")";
    return 
        inner: mapper,
        repr: repr,
        type: "flatmap",
        name: "flatmap",
        clone: function(mapper)  return flatmap(mapper); ,
        exec:
        function (input) 
            var out = [];
            for (var i=0; i<input.length; i++) 
                out.push(mapper.exec(input[i]));
            
            return out;
        
    ;




function split(s, t) 
    var i = s.indexOf(t);

    if (i == -1) return null;
    else 
        return [s.slice(0, i), s.slice(i+2, s.length)];
    


function compile_one(inr, outr) 
    inr = (inr.charAt(0) == ".") ? inr.slice(1, inr.length) : inr;
    outr = (outr.charAt(0) == ".") ? outr.slice(1, outr.length) : outr;

    var box = split(inr, "[]");
    var box2 = split(outr, "[]");
    var m, ps, fs, i, j;

    if (box == null && box2 == null)  // no array!
        m = id;

        ps = inr.split(".");
        fs = outr.split(".");

        for (i=0; i<fs.length; i++)  m = f(fs[i], m); 
        for (j=0; j<ps.length; j++)  m = p(ps[j], m); 

        return m;
    

    if (box != null && box2 != null)  // array on both sides
        m = arr(compile_one(box[1], box2[1]));

        ps = box[0].split(".");
        fs = box[0].split(".");

        for (i=0; i<fs.length; i++)  m = f(fs[i], m); 
        for (j=0; j<ps.length; j++)  m = p(ps[j], m); 

        return m;
    

    if (box != null && box2 == null)  // flatmap
        m = flatmap(compile_one(box[1], outr));

        ps = box[0].split(".");

        for (j=0; j<ps.length; j++)  m = p(ps[j], m); 

        return m;
    

    return null;


function merge_rules(m1, m2) 
    if (m1 == null) return m2;
    if (m2 == null) return m1;

    if (m1.name == m2.name && m1.inner != null) 
        return m1.clone(merge_rules(m1.inner, m2.inner));
     else 
        return combine(m1, m2);
    



var input = 
    store: "myStore",
    items: [
        name: "Hammer", skus:[num:"12345qwert"],
        name: "Bike", skus:[num:"asdfghhj", num:"zxcvbn"],
        name: "Fork", skus:[num:"0987dfgh"]
    ]
;

var m1 = compile_one("items[].name", "items[].name");
var m2 = compile_one("items[].skus[].num", "items[].sku");
var m3 = compile_one("store", "storeName");
var m4 = merge_rules(m3,merge_rules(m1, m2));
var out = m4.exec(input);


alert(JSON.stringify(out));

【讨论】:

输出准确,并处理了我测试的一些模型更改。我确实在使用 merge_rules 时遇到了麻烦,因为它只需要两条规则,当我将一条规则更改为这样时 compile_one("store", "storeName");它不会处理它。这给了我一些可以开始工作的东西。我将您的答案标记为已接受。万分感谢!!!如果您对如何使合并处理任意数量的路径集或为什么它在我更改时损坏有任何想法,将不胜感激。 jsfiddle.net/ads4kubf @Rob 修复了原始代码以适用于您的情况,原因是我应该在将输出放入字段后将映射器类型重置为正常。现在您的更改应该可以工作了。我明天会检查它是否适用于超过 2 条规则。【参考方案3】:

我借用了较早的答案并进行了改进,以解决您的两个示例,这应该是通用的。虽然如果您计划使用 2 组输入顺序运行此操作,那么行为将与我在我的 cmets 中针对您的原始问题所概述的那样。

    var apiObj = 
    items: [
        name: "Hammer",
        skus: [
            num: "12345qwert"
        ]
    , 
        name: "Bike",
        skus: [
            num: "asdfghhj"
        , 
            num: "zxcvbn"
        ]
    , 
        name: "Fork",
        skus: [
            num: "0987dfgh"
        ]
    ]
;

var myObj =  //Previously has values
    storeName: "",
    items: [
        uniqueName: ""
    ],
    outputModel: 
        items: [
            name: "Hammer"
        ]
    
;

/** Also works with this **
var myPath = "outputModel.items[].uniqueName";
var apiPath = "items[].name";
*/
var myPath = "outputModel.items[].sku";
var apiPath = "items[].skus[].num";

function make_accessor(program) 

    return function (obj, callback) 
        (function do_segment(obj, segments) 
            var start = segments.shift() // Get first segment
            var pieces = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
            var property = pieces[1];
            var isArray = pieces[2]; // [] on end
            obj = obj[property]; // drill down

            if (!segments.length)  // last segment; callback
                if (isArray) 
                    return obj.forEach(callback);
                 else 
                    return callback(obj);
                
             else  // more segments; recurse
                if (isArray)  // array--loop over elts
                    obj.forEach(function (elt) 
                        do_segment(elt, segments.slice());
                    );
                 else 
                    do_segment(obj, segments.slice()); // scalar--continue
                
            
        )(obj, program.split('.'));
    ;


function make_inserter(program) 

    return function (obj, value) 
        (function do_segment(obj, segments) 
            var start = segments.shift() // Get first segment
            var pieces = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
            var property = pieces[1];
            var isArray = pieces[2]; // [] on end
            if (segments.length)  // more segments
                if (!obj[property]) 
                    obj[property] = isArray ? [] : ;
                
                do_segment(obj[property], segments.slice());
             else  // last segment
                if (Array.isArray(obj)) 
                    var addedInFor = false;
                    for (var i = 0; i < obj.length; i++) 
                        if (!(property in obj[i])) 
                            obj[i][property] = value;
                            addedInFor = true;
                            break;
                        
                    
                    if (!addedInFor) 
                        var entry = ;
                        entry[property] = value;
                        obj.push(entry);
                    
                 else obj[property] = value;
            
        )(obj, program.split('.'));
    ;


access = make_accessor(apiPath);
insert = make_inserter(myPath);

access(apiObj, function (val) 
    insert(myObj, val);
);

console.log(myObj);

【讨论】:

我还没有完成代码,但是这个输出仍然没有达到它。请注意项目数组 1、2、3 是如何缺少名称属性的。 " "storeName": "", "items": [ "uniqueName": "" ], "outputModel": "items": [ "name": "Hammer", "sku": "12345qwert" , "sku": "asdfghhj" , "sku": "zxcvbn" , "sku": "0987dfgh" " @Rob,我想你错过了什么。我在此示例中采用的输入在映射集中没有名称。在您早期的一个 cmets 中,您提到如果有多个集合,您愿意使用给定的 myPath, apiPath 集合多次运行该程序。因此,如果您再次使用 var myPath = "outputModel.items[].uniqueName"; var apiPath = "items[].name"; 集和第一次运行的 myObj 运行此程序,那么根据映射集,myObj 对象将在项目数组 1、2、3 中具有 name 属性。 如果我们必须针对每组路径运行它,我们必须将先前的结果传递回其中,以便所有结果最终都在同一个 obj 中。【参考方案4】:

旧解决方案: https://jsfiddle.net/d7by0ywy/):

这是我的新通用解决方案,前提是您提前知道要处理的两个对象(此处称为inpout)。如果您事先不知道它们,您可以使用旧解决方案中的技巧将= 两侧的对象分配给inpout (https://jsfiddle.net/uxdney3L/3/)。

限制:两边的数组数量必须相同,并且数组必须包含对象。否则它会模棱两可,如果你想让它更复杂,你就必须想出一个更好的语法来表达规则(或者为什么你没有函数而不是规则?)。

歧义示例: out.items[].sku=inp[].skus[].num 您是将num 的值的数组分配给sku,还是分配具有num 属性的对象数组? p>

数据:

rules = [
  'out.items[].name=inp[].name',
  'out.items[].sku[].num=inp[].skus[].num'
];

inp = [
    'name': 'Hammer',
    'skus':['num':'12345qwert','test':'ignore']
  ,
    'name': 'Bike',
    'skus':['num':'asdfghhj','num':'zxcvbn']
  ,
    'name': 'Fork',
    'skus':['num':'0987dfgh']
];

程序:

function process() 
  if (typeof out == 'undefined') 
    out = ;
  
  var j, r;
  for (j = 0; j < rules.length; j++) 
    r = rules[j].split('=');
    if (r.length != 2) 
      console.log('invalid rule: symbol "=" is expected exactly once');
     else if (r[0].substr(0, 3) != 'out' || r[1].substr(0, 3) != 'inp') 
      console.log('invalid rule: expected "inp...=out..."');
     else 
      processRule(r[0].substr(3).split('[]'), r[1].substr(3).split('[]'), 0, inp, out);
    
  


function processRule(l, r, n, i, o)  // left, right, index, in, out
  var t = r[n].split('.');
  for (var j = 0; j < t.length; j++) 
    if (t[j] != '') 
      i = i[t[j]];
    
  
  t = l[n].split('.');
  if (n < l.length - 1) 
    for (j = 0; j < t.length - 1; j++) 
      if (t[j] != '') 
        if (typeof o[t[j]] == 'undefined') 
          o[t[j]] = ;
        
        o = o[t[j]];
      
    
    if (typeof o[t[j]] == 'undefined') 
      o[t[j]] = [];
    
    o = o[t[j]];
    for (j = 0; j < i.length; j++) 
      if (typeof o[j] == 'undefined') 
          o[j] = ;
      
      processRule(l, r, n + 1, i[j], o[j]);
    
   else 
    for (j = 0; j < t.length - 1; j++) 
      if (t[j] != '') 
        if (typeof o[t[j]] == 'undefined') 
          o[t[j]] = ;
        
        o = o[t[j]];
      
    
    o[t[j]] = i;
  


process();
console.log(out);

【讨论】:

嗨,Maraca,非常接近,但这在这里杀死了它'items[].skus[0].num'。如果我删除 0,它就会爆炸。它必须将第二个数组中的任何项目添加到我的输出中。我还尝试在 'skus' 数组 'skus.data[].field' 中添加另一个数组层,但它无法处理。 @Rob 你不能这样做,因为那样表达是模棱两可的。我认为你想要的可以简单地写成outputmodel.items[].sku=items[].skus 然后它应该可以工作。我在第一部分试图解释什么。检查它:jsfiddle.net/d7by0ywy/1 Maraca,我刚刚修改了您的警报以对输出进行字符串化,您可以看到输出格式错误。它绝对看起来像你的关闭,但有一些小问题。 jsfiddle.net/d7by0ywy/4 @Rob 你只处理 2 个对象吗?像 outputModel 和 items 你事先知道吗?那么可以提出一个非常好的解决方案 总是有一个源对象或数组,也总是有一个输出 obj 或数组。然而,这些对象看起来像什么,代码在传入之前是没有概念的。它必须仅根据两个路径字符串映射这两个对象【参考方案5】:

嗯,一个有趣的问题。以编程方式constructing nested objects from a property accessor string(或the reverse)并不是什么大问题,即使使用multiple descriptors in parallel 也是如此。它确实变得复杂的是需要迭代的数组;当它在 setter 和 getter 端以及多个并行描述符字符串达到不同级别时,这不再那么有趣了。

所以首先我们需要区分脚本中每个访问器描述的数组级别,并解析文本:

function parse(script) 
    return script.split(/\s*[;\r\n]+\s*/g).map(function(line) 
        var assignment = line.split(/\s*=\s*/);
        return assignment.length == 2 ? assignment : null; // console.warn ???
    ).filter(Boolean).map(function(as) 
        as = as.map(function(accessor) 
            var parts = accessor.split("[]").map(function(part) 
                return part.split(".");
            );
            for (var i=1; i<parts.length; i++) 
                // assert(parts[i][0] == "")
                var prev = parts[i-1][parts[i-1].length-1];
                parts[i][0] = prev.replace(/s$/, ""); // singular :-)
            
            return parts;
        );
        if (as[0].length == 1 && as[1].length > 1) // getter contains array but setter does not
            as[0].unshift(["output"]); // implicitly return array (but better throw an error)
        return setter:as[0], getter:as[1];
    );

这样,文本输入可以变成可用的数据结构,现在看起来像这样:

["setter":[["outputModel","items"],["item","name"]],
  "getter":[["items"],["item","name"]],
 "setter":[["outputModel","items"],["item","sku"]],
  "getter":[["items"],["item","skus"],["sku","num"]]]

getter 已经很好地转换为嵌套循环,例如

for (item of items)
    for (sku of item.skus)
        … sku.num …;

这正是我们要去的地方。这些规则中的每一个都相对容易处理,复制对象的属性并为数组迭代数组,但这是我们最关键的问题:我们有多个规则。当我们处理迭代多个数组时,基本的解决方案是创建它们的cartesian product,这确实是我们需要的。但是,我们希望对此进行很多限制 - 我们不想在输入中创建所有 names 和所有 nums 的每个组合,而是希望按照它们来自的 item 对它们进行分组。

为此,我们将为包含对象生成器的输出结构构建某种前缀树,其中每个递归再次成为相应输出子结构的树。

function multiGroupBy(arr, by) 
    return arr.reduce(function(res, x) 
        var p = by(x);
        (res[p] || (res[p] = [])).push(x);
        return res;
    , );

function group(rules) 
    var paths = multiGroupBy(rules, function(rule) 
        return rule.setter[0].slice(1).join(".");
    );
    var res = [];
    for (var path in paths) 
        var pathrules = paths[path],
            array = [];
        for (var i=0; i<pathrules.length; i++) 
            var rule = pathrules[i];
            var comb = 1 + rule.getter.length - rule.setter.length;
            if (rule.setter.length > 1) // its an array
                array.push(
                    generator: rule.getter.slice(0, comb),
                    next: 
                        setter: rule.setter.slice(1),
                        getter: rule.getter.slice(comb)
                    
                )
            else if (rule.getter.length == 1 && i==0)
                res.push(
                    set: rule.setter[0],
                    get: rule.getter[0]
                );
            else
                console.error("invalid:", rule);
        
        if (array.length)
            res.push(
                set: pathrules[0].setter[0],
                cross: product(array)
            );
    
    return res;

function product(pathsetters) 
    var groups = multiGroupBy(pathsetters, function(pathsetter) 
        return pathsetter.generator[0].slice(1).join(".");
    );
    var res = [];
    for (var genstart in groups) 
        var creators = groups[genstart],
            nexts = [],
            nests = [];
        for (var i=0; i<creators.length; i++) 
            if (creators[i].generator.length == 1)
                nexts.push(creators[i].next);
            else
                nests.push(path:creators[i].path, generator: creators[i].generator.slice(1), next:creators[i].next);
        
        res.push(
            get: creators[0].generator[0],
            cross: group(nexts).concat(product(nests))
        );
    
    return res;

现在,我们的规则集 group(parse(script)) 看起来像这样:

[
    "set": ["outputModel","items"],
    "cross": [
        "get": ["items"],
        "cross": [
            "set": ["item","name"],
            "get": ["item","name"]
        , 
            "get": ["item","skus"],
            "cross": [
                "set": ["item","sku"],
                "get": ["sku","num"]
            ]
        ]
    ]
]

这是一个我们可以实际使用的结构,因为它现在清楚地传达了如何将所有嵌套数组和其中的对象匹配在一起的意图。 让我们动态地解释它,为给定的输入构建一个输出:

function transform(structure, input, output) 
    for (var i=0; i<structure.length; i++) 
        output = assign(output, structure[i].set.slice(1), getValue(structure[i], input));
    
    return output;

function retrieve(val, props) 
    return props.reduce(function(o, p)  return o[p]; , val);

function assign(obj, props, val) 
    if (!obj)
        if (!props.length) return val;
        else obj = ;
    for (var j=0, o=obj; j<props.length-1 && o!=null && o[props[j]]; o=o[props[j++]]);
    obj[props[j]] = props.slice(j+1).reduceRight(function(val, p) 
        var o = ;
        o[p] = val;
        return o;
    , val);
    return obj;

function getValue(descriptor, input) 
    if (descriptor.get) // && !cross
        return retrieve(input, descriptor.get.slice(1));
    var arr = [];
    descriptor.cross.reduce(function horror(next, d) 
        if (descriptor.set)
            return function (inp, cb) 
                next(inp, function(res)
                    cb(assign(res, d.set.slice(1), getValue(d, inp)));
                );
            ;
        else // its a crosser
            return function(inp, cb) 
                var g = retrieve(inp, d.get.slice(1)),
                    e = d.cross.reduce(horror, next)
                for (var i=0; i<g.length; i++)
                    e(g[i], cb);
            ;
    , function innermost(inp, cb) 
        cb(); // start to create an item
    )(input, function(res) 
        arr.push(res); // store the item
    );
    return arr;

这确实适用于

var result = transform(group(parse(script)), items); // your expected result

但我们可以做得更好,性能更高:

function compile(structure) 
    function make(descriptor) 
        if (descriptor.get)
            return inputName: descriptor.get[0], output: descriptor.get.join(".") ;

        var outputName = descriptor.set[descriptor.set.length-1];
        var loops = descriptor.cross.reduce(function horror(next, descriptor) 
            if (descriptor.set)
                return function(it, cb) 
                    return next(it, function(res)
                        res.push(descriptor)
                        return cb(res);
                    );
                ;
            else // its a crosser
                return function(it, cb) 
                    var arrName = descriptor.get[descriptor.get.length-1],
                        itName = String.fromCharCode(it);
                    var inner = descriptor.cross.reduce(horror, next)(it+1, cb);
                    return 
                        inputName: descriptor.get[0],
                        statement:  (descriptor.get.length>1 ? "var "+arrName+" = "+descriptor.get.join(".")+";\n" : "")+
                                    "for (var "+itName+" = 0; "+itName+" < "+arrName+".length; "+itName+"++) \n"+
                                    "var "+inner.inputName+" = "+arrName+"["+itName+"];\n"+
                                    inner.statement+
                                    "\n"
                    ;
                ;
        , function(_, cb) 
            return cb([]);
        )(105, function(res) 
            var item = joinSetters(res);
            return 
                inputName: item.inputName,
                statement: (item.statement||"")+outputName+".push("+item.output+");\n"
            ;
        );
        return 
            statement: "var "+outputName+" = [];\n"+loops.statement,
            output: outputName,
            inputName: loops.inputName
        ;
    
    function joinSetters(descriptors) 
        if (descriptors.length == 1 && descriptors[0].set.length == 1)
            return make(descriptors[0]);
        var paths = multiGroupBy(descriptors, function(d) return d.set[1] || console.error("multiple assignments on "+d.set[0], d); );
        var statements = [],
            inputName;
        var props = Object.keys(paths).map(function(p) 
            var d = joinSetters(paths[p].map(function(d) 
                var names = d.set.slice(1);
                names[0] = d.set[0]+"_"+names[0];
                return set:names, get:d.get, cross:d.cross;
            ));
            inputName = d.inputName;
            if (d.statement)
                statements.push(d.statement)
            return JSON.stringify(p) + ": " + d.output;
        );
        return 
            inputName: inputName,
            statement: statements.join(""),
            output: ""+props.join(",")+""
        ;
    
    var code = joinSetters(structure);
    return new Function(code.inputName, code.statement+"return "+code.output+";");

所以这就是你最终会得到的:

> var example = compile(group(parse("outputModel.items[].name = items[].name;outputModel.items[].sku = items[].skus[].num;")))
function(items) 
    var outputModel_items = []; 
    for (var i = 0; i < items.length; i++) 
        var item = items[i];
        var skus = item.skus;
        for (var j = 0; j < skus.length; j++) 
            var sku = skus[j];
            outputModel_items.push("name": item.name,"sku": sku.num);
        
    
    return "items": outputModel_items;

> var flatten = compile(group(parse("as[]=bss[][]")))
function(bss) 
    var as = []; 
    for (var i = 0; i < bss.length; i++) 
        var bs = bss[i];
        for (var j = 0; j < bs.length; j++) 
            var b = bs[j];
            as.push(b);
        
    
    return as;

> var parallelRecords = compile(group(parse("x.as[]=y[].a; x.bs[]=y[].b")))
function(y) 
    var x_as = []; 
    for (var i = 0; i < y.length; i++) 
        var y = y[i];
        x_as.push(y.a);
    
    var x_bs = []; 
    for (var i = 0; i < y.length; i++) 
        var y = y[i];
        x_bs.push(y.b);
    
    return "as": x_as,"bs": x_bs;

现在您可以轻松地将输入数据传递给动态创建的函数,它会很快被转换:-)

【讨论】:

抱歉,这花了一点时间 :-)

以上是关于Javascript:确定未知数组长度并动态映射的主要内容,如果未能解决你的问题,请参考以下文章

带有 SWIG 未知长度数组的 NumPy C 扩展

当数组的数量和每个数组的长度未知时生成字符组合的所有排列

JavaScript动态更新数组

c++中用new给未知大小的数组分配空间怎么弄?

delphi 如何接收一个函数返回的,长度未知的数组? 麻烦给例子,小弟新手

JavaScript数组