Merge source JSON string to target with similar keys but different structure in javascript

Viewed 177

I have two JSON strings as shown below:

source = [
  {
    "name": "test1",
    "values": ["User Support"],
    "enabled": false
  },
  {
    "name": "test2",
    "values": ["M"],
    "enabled": true
  },
  {
    "name": "test3",
    "values": ["CA"],
    "enabled": false
  }
]

target = [{
    "name": "test1",
    "values": [{
        "value": "User Support",
        "selected": false
      },
      {
        "value": "Engineering",
        "selected": false
      },
      {
        "value": "Implementation",
        "selected": false
      }
    ],
    "enabled": false
  },
  {
    "name": "test2",
    "values": [{
        "value": "M",
        "selected": false
      },
      {
        "value": "F",
        "selected": false
      }
    ],
    "notEnabled": false
  },
  {
    "name": "test3",
    "values": [{
        "value": "CA",
        "selected": false
      },
      {
        "value": "EN",
        "selected": false
      }
    ],
    "enabled": false
  }
]

I want to merge both these JSON strings into target and the resultant should look like:

target = [{
    "name": "test1",
    "values": [{
        "value": "User Support",
        "selected": true
      },
      {
        "value": "Engineering",
        "selected": false
      },
      {
        "value": "Implementation",
        "selected": false
      }
    ],
    "enabled": false
  },
  {
    "name": "test2",
    "values": [{
        "value": "M",
        "selected": true
      },
      {
        "value": "F",
        "selected": false
      }
    ],
    "enabled": true
  },
  {
    "name": "test3",
    "values": [{
        "value": "CA",
        "selected": true
      },
      {
        "value": "EN",
        "selected": false
      }
    ],
    "enabled": false
  }
]

So, what I am trying to do is search in target string for name as test1, test2.... and then set the selected field as true if the value is found in source JSON string. Same is the case for enabled field.

First thing that comes to my mind is to use nested for each loops and check for the keys. Is there any other better way to do this in Javascript?

Note that there could be other keys present inside target string, but we don't bother about them unless they are present in source string.

2 Answers

If you don't mind lodash:

const _ = require('lodash');

const sourceJSON = '[{"name":"test1","values":["User Support"],"enabled":false},{"name":"test2","values":["M"],"enabled":true},{"name":"test3","values":["CA"],"enabled":false}]';
const targetJSON = '[{"name":"test1","values":[{"value":"User Support","selected":false}, {"value":"Engineering","selected":false},{"value":"Implementation","selected":false}],"enabled":false},{"name":"test2","values":[{"value":"M","selected":false},  {"value":"F","selected":false} ],"notEnabled":false},{ "name":"test3","values": [{"value":"CA","selected":false},{"value":"EN","selected":false}],"enabled":false}]';

const source = JSON.parse(sourceJSON);
const target = JSON.parse(targetJSON);

const sourceNormalized = source.map((obj) => (
  { ...obj, values: [{value: obj.values[0], selected: true}] }
));

const merged = _.defaultsDeep(sourceNormalized, target);
console.dir(merged, {depth: null});
// [ { name: 'test1',
//     values: [
//       { value: 'User Support', selected: true },
//       { value: 'Engineering', selected: false },
//       { value: 'Implementation', selected: false }
//     ],
//     enabled: false
//   },
//   { name: 'test2',
//     values: [ 
//       { value: 'M', selected: true }, 
//       { value: 'F', selected: false } 
//     ],
//     enabled: true,
//     notEnabled: false
//   },
//   { name: 'test3',
//     values: [
//       { value: 'CA', selected: true },
//       { value: 'EN', selected: false }
//     ],
//     enabled: false} ] 

result = JSON.stringify(merged);

The previous answer using lodash is a very good approach. In case you are in a situation such as myself (stuck working on a code-base where packages, including lodash, are difficult to get approved-for-use), here is an approach using Vanilla-javascript (+ JSON.stringify):

const delta = [{
    "name": "test1",
    "values": ["User Support"],
    "enabled": false
  },
  {
    "name": "test2",
    "values": ["M"],
    "enabled": true
  },
  {
    "name": "test3",
    "values": ["CA"],
    "enabled": false
  }
];

const orig = [{
    "name": "test1",
    "values": [{
        "value": "User Support",
        "selected": false
      },
      {
        "value": "Engineering",
        "selected": false
      },
      {
        "value": "Implementation",
        "selected": false
      }
    ],
    "enabled": false
  },
  {
    "name": "test2",
    "values": [{
        "value": "M",
        "selected": false
      },
      {
        "value": "F",
        "selected": false
      }
    ],
    "notEnabled": false
  },
  {
    "name": "test3",
    "values": [{
        "value": "CA",
        "selected": false
      },
      {
        "value": "EN",
        "selected": false
      }
    ],
    "enabled": false
  }
];

const getTargetJSON = (delta, orig, debug = true) => { // method signature
  const deltaMapper = delta.reduce((f, i) => ({
    ...f,
    [i.name]: {
      values: [...i.values],
      enabled: i.enabled
    }
  }), {});
  const target = orig.map(itm => ({
    ...itm,
    values: itm.values.map(it2 => ({
      ...it2,
      selected: deltaMapper[itm.name].values.includes(it2.value) || false
    })),
    enabled: deltaMapper[itm.name].enabled || false
  }));

  debug && console.log('Merged Target:\n', target);
  return JSON.stringify(target);
};

getTargetJSON(delta, orig); // method call

Approach / Explanation:

We breakdown the problem into smaller ones.

  • First, generate a map using the source (named 'delta' in code-snippet)
  • Next, iterate through the target (named 'orig') and utilize the map (created above) to determine whether value is 'selected' and item is 'enabled'.
Related