オブジェクトをシンタックス文字列によってフィルタリングする関数を作っています。

obj =
  users: [
    {name:'takeda', age:'18'},
    {name:'kanako', age:'19'},
    {name:'sanada', age:'17'}
  ]

objectFilter(obj, 'users[*].age') # -> {users:[{age:18}, {age:19}, {age:17}]}

なんとか、頑張って以下のフィルタリング関数を作成することが出来ました。

フィルタリング関数

window.objectFilter = (source, syntax)->
  recur = (src, obj, elems)->
    elems = _.cloneDeep(elems)
    # console.log _.cloneDeep(src), _.cloneDeep(obj), _.cloneDeep(elems)
    elem = elems.shift()
    return src if elem == undefined
    [key, idx] = elem.split(':')
    if idx == '*'
      if key == '*'
        for k of src
          obj[k] = obj[k] || []
          for i in [0...src?[k]?.length||0]
            obj[k].push recur(src?[k]?[i], obj[k][i], elems)
      else
        obj[key] = obj[key] || []
        for i in [0...src?[key]?.length||0]
          obj[key][i] = obj[key][i] || {}
          obj[key].push recur(src?[key]?[i], obj[key][i], elems)
    else if idx != undefined
      if key == '*'
        for k of src
          obj[k] = obj[k] || []
          obj[k].push src[k][idx]
      else
        obj[key] = obj[key] || []
        i = parseInt(idx)
        obj[key].push recur(src?[key]?[i], obj[key][i], elems) if src?[key]?[i]
    else
      if key == '*'
        obj = obj || {}
        for k of src
          obj[k] = obj[k] || {}
          obj[k] = recur(src?[k], obj[k], elems)
      else
        obj = obj || {}
        obj[key] = obj[key] || {}
        obj[key] = recur(src?[key], obj[key], elems)
    obj

  result = {}
  for v in syntax.replace(/[ ]/g,'').replace(/\]/g,'').replace(/\[/g,':').split(',')
    recur(source, result, v.split('.'))
  result

そして、このフィルタリング関数をテストするために以下の複雑なオブジェクトを作成し実行します。

フィルタリングするオブジェクト

obj =
  hoge: 'yeah!'
  fuga: {a:1, b:2, c:3}
  piyo: [
    {
      name:'takeshi'
      image: {src:'takeshi.png', info:'Smile of Takeshi'}
    },
    {
      name:'misako'
      image: {src:'misako001.png', info:'Angry of Misako'}
    },
    {
      name:'yusuke'
      image: {src:'3asdf_de.jpg', info:'happy yusuke'}
    }
  ]
  gaga: [{id:1, name:'michael'}, {id:2, name:'sally'}]
  pepe: {a:[1,2,3], b:[4,5,6], c:[7,8,9]}
  tata: {a:{token:'bdef', value:1}, b:{token:'gied', value:2}}
  kaka: {a:[{x:'1',y:'2'},{x:'3',y:'4'}], b:[{x:'5',y:'6'},{x:'7',y:'8'}]}

シンタックスはカンマ区切りで複数指定することができる仕様になっています。

実行

syntax = 'hoge, piyo[2].image.src, gaga[*].name, ora.ora, hena[*], tata.*.value, pepe.*[1], kaka.*[*].x'
objectFilter(obj, syntax)

# 結果
# {
#    hoge: 'yeah!',
#    piyo: [ {image: {src: '3asdf_de.jpg'} ],
#    gaga: [{name:'michael'}, {name:'sally'}],
#    ora: {ora: null},
#    hena: [],
#    pepe: {a:[2], b:[5], c:[8]},
#    tata: {a:{value:1}, b:{value:2}}
#    kaka: {a:[{x:'1'},{x:'3'}], b:[{x:'5'},{x:'7'}]},
# }

恐らく、フィルタリング関数は以下のような条件を満たしているものになります。

  • syntax文字列はカンマ区切りによって複数指定できる
  • piyo[2]のようなシンタックスが指定された場合、配列とみなし3番目の要素にアクセスし、配列の要素数を1にする
  • gaga[*]pepe.*のようなシンタックスが指定された場合コレクションの全要素を対象としてアクセスする
  • ora.oraのような存在しないオブジェクトが指定された場合、空のオブジェクトを作成する
  • hena[*]のような存在しない配列にアクセスが有った場合には空の配列を作成する
  • 指定したシンタックスの値がsourceオブジェクトに存在しなければnullを代入する

ちゃんと動作しているように思えます。

しかし、objectFilter(obj,'piyo[*].*.src')を実行したときのデータが正しく取得できませんでした、コードをなんども睨みつけて格闘しているのですが、どうも上手く動作してくれません。

objectFilter(obj,'piyo[*].*.src')

# 結果
# 要素が3つから4つに増え、nameにsrc:undefinedが無く
# image.srcの値が全て同じにものになってしまっている
#
# {
#   piyo:[
#     {name:{}, image:{src:"3asdf_de.jpg"}},
#     {name:{}, image:{src:"3asdf_de.jpg"}},
#     {name:{}, image:{src:"3asdf_de.jpg"}},
#     {name:{}, image:{src:"3asdf_de.jpg"}}
#   ]
# }

どこがおかしいのでしょうか?