supunbatagoda
supunbatagoda

Reputation: 93

Find by key get value and replace by second json value in nested json object

I have an json object(firstObj) It can be nested and I have an second object containing key/value pair. I want to replace the second object's value by first one by matching value and do it's operation.

let firstObj = {
    amount_money: {
       amount: {
           mapped_field: 'payment_amt',
           operation: '/10'
       },
       currency: {
          mapped_field: 'payment_cur',
          operation: null
       }
    },
   source_id: {
          mapped_field: 'request_id',
          operation: null
   },
  ship: [ 
            { mapped_field: 'ship_country[0]', operation: null },
            { mapped_field: 'ship_country[1]', operation: null } 
        ]
 };
       

my second object

let secondObj = {
     payment_amt: 100,
     payment_cur: 'USD',
     request_id: '123ASD',
     ship_country: [ 
                     { code: 'USA', Title: 'America' }, 
                     { code: 'UK', Title: 'England' } 
                   ] 
    };

I want something like this

{
  amount_money: {
     amount: 10
     currency: 'USD'
  },
  source_id: '123ASD',
  ship: [ {America: 'USA'}, {England: 'UK'}]
}

Really appreciate your kind help, Thank you!

Upvotes: 3

Views: 2195

Answers (2)

vincent
vincent

Reputation: 2171

To address the updated question

.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/[email protected]/lib/index.min.js';

const myTemplate = { amount_money: { amount: { mapped_field: 'payment_amt', operation: '/10' }, currency: { mapped_field: 'payment_cur', operation: null } }, source_id: { mapped_field: 'request_id', operation: null }, ship: [{ mapped_field: 'ship_country[0]', operation: null }, { mapped_field: 'ship_country[1]', operation: null }] };
const myVariables = { payment_amt: 100, payment_cur: 'USD', request_id: '123ASD', ship_country: [{ code: 'USA', Title: 'America' }, { code: 'UK', Title: 'England' }] };

const apply = (input, operation) => {
  if (operation === null) {
    return input;
  }
  const action = /(?<op>[/+])(?<v>\d+)/g.exec(operation);
  if (action === null) {
    throw new Error(`Unknown operation: ${operation}`);
  }
  if (action.groups.op === '/') {
    return input / action.groups.v;
  }
  // action.groups.op === '+'
  return input + action.groups.v;
};

const compile = objectScan(['**.mapped_field'], {
  rtn: 'count',
  filterFn: ({ gparent, gproperty, parent, value, context }) => {
    const data = objectScan([value], { rtn: 'value', abort: true })(context);
    if (data === undefined) {
      return false;
    }
    gparent[gproperty] = apply(data, parent.operation);
    return true;
  }
});

console.log(compile(myTemplate, myVariables));
// => 5

console.log(myTemplate);
// => { amount_money: { amount: 10, currency: 'USD' }, source_id: '123ASD', ship: [ { code: 'USA', Title: 'America' }, { code: 'UK', Title: 'England' } ] }
</script>

Disclaimer: I'm the author of object-scan


Old Answer (pre edit of question)

Here is a generic solution using object-scan.

.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/[email protected]/lib/index.min.js';

const template = { amount_money: { amount: { mapped_field: 'payment_amt', operation: '/10' }, currency: { mapped_field: 'payment_cur', operation: null } }, source_id: { mapped_field: 'request_id', operation: null } }
const values = { payment_amt: 100, payment_cur: 'USD', request_id: '123ASD' };

const apply = (input, operation) => {
  if (operation === null) {
    return input;
  }
  const action = /(?<op>[/+])(?<v>\d+)/g.exec(operation);
  if (action === null) {
    throw new Error(`Unknown operation: ${operation}`);
  }
  if (action.groups.op === '/') {
    return input / action.groups.v;
  }
  // action.groups.op === '+'
  return input + action.groups.v;
};

const compile = objectScan(['**.*.mapped_field'], {
  rtn: 'count',
  filterFn: ({ gparent, gproperty, parent, value, context }) => {
    if (value in context) {
      gparent[gproperty] = apply(context[value], parent.operation);
      return true;
    }
    return false;
  }
});

console.log(compile(template, values));
// => 3

console.log(template);
// => { amount_money: { amount: 10, currency: 'USD' }, source_id: '123ASD' }
</script>

Disclaimer: I'm the author of object-scan

Important:

  • Note that this does modify the template object. You could clone it, if that is not desired.
  • You could use eval, but it's considered dangerous. It's a bit more work, but you should probably implement all supported operations yourself as I've started in the "apply" function.
  • You might be able to use eg safe-eval, but I have no experience with it and don't know how safe it is. If you are worried about any sort of injection attack, don't use it.

Edit (as requested in comment)

How this works:

  • apply is a helper function, taking the input and an operation. We use a regex to determine the operator and then apply it against the input
  • compile is the core logic. We target the field with **.*.mapped_field, where the single * ensures that we are targeting an object and not an array
  • in filterFn we check if the value is known in context (which is values). If that is the case update the the value using the grandparent (gparent) and grandproperty (gproperty). All the parameters of filterFn are relative to the target (in this case mapped_field)
  • count is just the return value (in this case how often true was returned from filterFn

This is all documented n great detail in object-scan. Have a look there. It might also greatly help your understanding to put in come log statements!

Upvotes: 1

Duy Quoc
Duy Quoc

Reputation: 181

Update

If you don't know how many nested nodes are there. There is a more overall solution using recursion.

Here is the solution:

const firstObj = {
  source_name: {
    mapped_field: 'request_name',
    operation: null,
  },
  amount_money: {
    amount: {
      mapped_field: 'payment_amt',
      operation: '/10',
    },
    currency: {
      mapped_field: 'payment_cur',
      operation: null,
    },
  },
  source_id: {
    mapped_field: 'request_id',
    operation: null,
  },
  nested: {
    nested: {
      nested: {
        nested: {
          mapped_field: 'mapping_nested',
          operation: null,
        },
      },
    },
  },
};

let secondObj = {
  payment_amt: 100,
  payment_cur: 'USD',
  request_id: '123ASD',
  request_name: 'Dollar',
  mapping_nested: 'Hello',
};

const procedure = (firstObj, parentObj = {}, nestedObj = {}) => {
  for (const [key, value] of Object.entries(firstObj)) {
    if (value.hasOwnProperty('mapped_field') && value.hasOwnProperty('operation')) {
      nestedObj[key] = value.operation
        ? eval(secondObj[value.mapped_field] + value.operation)
        : secondObj[value.mapped_field];
    } else {
      nestedObj[key] = {};
      procedure(value, parentObj, nestedObj[key]);
    }
  }
  return (parentObj = { ...nestedObj });
};

const result = procedure(firstObj);
console.log(JSON.stringify(result));
// {"source_name":"Dollar","amount_money":{"amount":10,"currency":"USD"},"source_id":"123ASD","nested":{"nested":{"nested":{"nested":"Hello"}}}}

Old answer

I don't know how many nested in your firstObj. But this code below can solve the example which you give.

I replace the second object's value by first one by matching value.

If operation not equal null, use eval() function evaluates JavaScript code represented as a string.

const result = {};
for (const [parentKey, parentValue] of Object.entries(firstObj)) {
  result[parentKey] = {};
  for (const [childKey, childValue] of Object.entries(parentValue)) {
    result[parentKey][childKey] = childValue.operation
      ? eval(secondObj[childValue.mapped_field] + childValue.operation)
      : secondObj[childValue.mapped_field];
  }
}
console.log(result); //{ amount_money: { amount: 10, currency: 'USD' } }

Note: If firstObj have only one key like amount_money, you can make your code precise by using only one loop.

Upvotes: 1

Related Questions