-1

I'd like to transform an object based on a dot notation string in another object. For instance:

const objToTransform = {
  "a": "aValue",
  "b": {
      "c": "cValue",
      "d": "dValue"
  }
};

const filteringObj = {
  "field1": "a",
  "field2": {
    "subfield1": "b.c",
    "subfield2": "b.d"
  }
};

const filteredObj = myFunc(objToTransform, filteringObj);
// expect outputs to be: 
//  {
//   "field1": "aValue",
//   "field2": {
//      "subfield1": "cValue",
//      "subfield2": "dValue"
//    }
//  }

I've been working on this seemingly simple thing for hours and I still can't get it to work. I've found this topic, which shows you how to get a nested object value with a dot notation string but couldn't get any further sadly.

Thanks in advance for your help!

Kévin HERRERO
  • 229
  • 2
  • 10

5 Answers5

0

You can do that in following steps using recursion:

  • Make a copy of the filteringObj.
  • Create helper function getByPath which takes objects and path and return value at that path from nested object
  • Create a function transform which loops over the keys of filteringObj(I named it pattern in function)
  • In side the loop check if value is an object then call the transform function recursively on that subobject
  • If its not a object then get the value from the original object objectToTransform by using getByPath function

const objToTransform = {
  "a": "aValue",
  "b": {
      "c": "cValue",
      "d": "dValue"
  }
};

const filteringObj = {
  "field1": "a",
  "field2": {
    "subfield1": "b.c",
    "subfield2": "b.d"
  }
};

function getByPath(obj, path){
  //console.log(path)
  return path.split('.').reduce((ac,a) => ac[a], obj);
}

function transform(obj, pattern){
  for(let key in pattern){
    if(typeof pattern[key] === "object"){
      transform(obj, pattern[key]);
    }
    else{
      pattern[key] = getByPath(obj, pattern[key]);
    }
  }
}
const copy = JSON.parse(JSON.stringify(filteringObj))
transform(objToTransform, copy);
console.log(copy)
Maheer Ali
  • 35,834
  • 5
  • 42
  • 73
0

You could create recursive function using reduce method that will create recursive call every time when the value is of type object.

const objToTransform = {
  "a": "aValue",
  "b": {
    "c": "cValue",
    "d": "dValue"
  }
};

const filteringObj = {
  "field1": "a",
  "field2": {
    "subfield1": "b.c",
    "subfield2": "b.d"
  }
};

function transform(a, b) {
  return Object.entries(b).reduce((r, [k, v]) => {
    r[k] = typeof v !== 'object' ?
      v.split('.').reduce((r, e) => r[e], a) :
      transform(a, v)

    return r;
  }, {})
}

const result = transform(objToTransform, filteringObj)
console.log(result)
Nenad Vracar
  • 118,580
  • 15
  • 151
  • 176
0

I have done a recursive algo:

const getRefVal=(ePath, eData)=>ePath.split('.').reduce((r,x)=>r[x], eData);

function myFunc(oData, oModel) {
  let ret = {};
  setKeyVal(oModel,ret);

  function setKeyVal(src,res) {              // recursive function
    for (let [k,v] of Object.entries(src)) {
      if (typeof src[k]==='object') { res[k] = {}; setKeyVal(v,res[k]) } 
      else                          { res[k] = getRefVal(v,oData) }
    }
  }
  return ret
}

const objToTransform= { a: 'aValue', b: { c: 'cValue', d: 'dValue' } }
  ,   filteringObj =  { field1: 'a', field2: { subfield1: 'b.c', subfield2: 'b.d' } };

// test 
const filteredObj = myFunc(objToTransform, filteringObj);
// proof
console.log( filteredObj )
Mister Jojo
  • 20,093
  • 6
  • 21
  • 40
0

Lodash allows you to use the dot notation very easily. Here is exactly what you want (<10 lines) :

import _ from "lodash";

const objToTransform = {
    a: "aValue",
    b: {
        c: "cValue",
        d: "dValue"
    }
};

const filteringObj = {
    field1: "a",
    field2: {
        subfield1: "b.c",
        subfield2: "b.d"
    }
};

const filter = (model, data, filtered = {}) => {
    for (const field in model) {
        filtered[field] =
            typeof model[field] === "object"
                ? filter(model[field], data, model[field])
                : _.get(objToTransform, model[field]);
    }
    return filtered;
};

console.log(filter(filteringObj, objToTransform))
Kevin Gilbert
  • 903
  • 8
  • 21
0

If you are using (or interested in using) Ramda (disclaimer: I'm one if its primary authors), this might work for you. Ramda does not have a deepMap function, but it's easy to write one. And using that, your transformation function is a one-liner:

const deepMap = (fn) => (obj) => 
  is (Object, obj) ? map (deepMap (fn)) (obj) : fn (obj)

const transform = compose(applySpec, deepMap (compose (path, split('.'))))

const objToTransform = {a: "aValue", b: {c: "cValue", d: "dValue"}}
const filteringObj = {field1: "a", field2: {subfield1: "b.c", subfield2: "b.d"}}

console .log (transform (filteringObj) (objToTransform))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script> const {is, map, compose, applySpec, path, split } = R       </script>

The key point here is applySpec, which you could use directly with a slightly different format of filteringObj. This would yield your desired results:

applySpec ({
  field1: path (["a"]), 
  field2: {
    subfield1: path (["b", "c"]), 
    subfield2: path (["b", "d"])
  }
}) (objToTransform)

The remainder of the transform function is simply to convert the filteringObj into that form above.

If you want to keep calling it as asked in the question, that is as transform (objToTransform, filteringObj) rather than trasform (filteringObj) (objToTransform), it's only slightly more complex:

const transform = (objToTransform, filteringObj) => 
  applySpec(deepMap (compose (path, split('.'))) (filteringObj)) (objToTransform)

You can write your own versions of those Ramda functions, it's mostly easy. But a version of applySpec which works on nested properties would be a bit more difficult.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103