`toKeyedObject` with `$value` results in `undefined`

When I try to use $value as the 2nd parameter to the toKeyedObject formatter, I’m getting an object with undefined values.

Is it expected or is it a bug?

Given this array:

dmx.app.set('arr', [{k:'a', v:1}, {k:'b', v:2}, {k:'c', v:3}]) 

when I want to turn it into a lookup table (aka map, hash-map, object, dictionary)

dmx.parse("arr.toKeyedObject('k', '$value')")

I get

{a: undefined, b: undefined, c: undefined}

but I was expecting

{a: {k:'a', v:1}, b: {k:'b', v:2}, c: {k:'c', v:3}}

since

dmx.parse("arr.map('v')")
[1, 2, 3]

dmx.parse("arr.map('$value')")
[{k:'a', v:1}, {k:'b', v:2}, {k:'c', v:3}]

and

dmx.parse("arr.toKeyedObject('k', 'v')")
{a: 1, b: 2, c: 3}

groupBy almost yields what I expect, but the values are wrapped into an undesired array (since we know that values of k is unique, by definition):

dmx.parse("arr.groupBy('k')")
{a: [{k:'a', v:1}], b: [{k:'b', v:2}], c: [{k:'c', v:3}]}

Here is a custom formatter just for this purpose (based on the implementation of groupBy), but I have the feeling, that toKeyedObject should already provide this behaviour:

    dmx.Formatter('array', 'indexBy', function (arr, key_expr) {
      const scope = this, key_is_prop = dmx.propCheck.test(key_expr)
      return arr.reduce(
        (o, item) => {
          const key = key_is_prop
            ? item[key_expr]
            : dmx.parse(key_expr, new dmx.DataScope(item, scope))
          o[key] = item
          return o
        }, {})
    })

After some sleep, I found a slightly more understandable indexBy implementation:

    dmx.Formatter('array', 'indexBy', function (arr, key_expr) {
      const scope = this
      return arr.reduce(
        dmx.propCheck.test(key_expr)
          ? (o, item) => (o[item[key_expr]] = item, o)
          : (o, item) => (o[item[dmx.parse(key_expr, new dmx.DataScope(item, scope))]] = item, o),
        {})
    })

The AppConnect 2.0 implementation of toKeyedObject can be modified like this:

  dmx.Formatter("array", 'toKeyedObject',
   function(e, t, a) {
      const r = dmx.propCheck.test(t)
        , s = dmx.propCheck.test(a)
      return e.reduce(
        ((e, d) => (
          e[r ? d[t] : dmx.parse(t, dmx.DataScope(d, this))] =
            s ? d[a] : dmx.parse(a, dmx.DataScope({...d, $value: d}, this)),
            e)),
        {})
    }
 )

instead of the origianl

...
            s ? d[a] : dmx.parse(a, dmx.DataScope(d, this)),
...

to support

dmx.parse('[{a:"attr1", b:2}, {a:"attr2", b:20}].toKeyedObject("a", "$value")')

returning

{
    "attr1": {
        "a": "attr1",
        "b": 2
    },
    "attr2": {
        "a": "attr2",
        "b": 20
    }
}
2 Likes

Thank you @onetom_at_gini, implemented it your way but also added $key and $index.

  toKeyedObject (array, key, value) {
    const staticKey = dmx.propCheck.test(key);
    const staticVal = dmx.propCheck.test(value);

    return array.reduce((obj, item, index) => {
      const scope = { ...item, $index: index, $key: index, $value: item };
      const k = staticKey ? item[key] : dmx.parse(key, dmx.DataScope(scope, this));
      obj[k] = staticVal ? item[value] : dmx.parse(value, dmx.DataScope(scope, this));
      return obj;
    }, {});
  },
1 Like

Added in Wappler 6.5.1

1 Like

This topic was automatically closed after 47 hours. New replies are no longer allowed.