3

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!

supunbatagoda
  • 73
  • 1
  • 10
  • Check [_get](https://lodash.com/docs/4.17.15#get) and [_set](https://lodash.com/docs/4.17.15#set) of lodash. It can help you. – Vijay Rathore May 28 '21 at 08:19
  • 2
    `operation: '/10'` is not ideal, unless you also have a parser that can determine what mathematical operation should be performed. e.g. `/10` => `{ operator: 'divide', value: 10 }` etc – andy mccullough May 28 '21 at 08:23
  • @andymccullough yes, you are correct something like that much better. I just want to show here there can be operation should use when creating final object. – supunbatagoda May 28 '21 at 08:27
  • @VijayRathore I'll looking into it, but the thing is we don't know first object key, it can be changed it can be deep nested. we only know it has mapped_field and operator.any idea to resolve this problem? – supunbatagoda May 28 '21 at 08:36

2 Answers2

1

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.

Duy Quoc
  • 171
  • 1
  • 8
  • First of all thanks for the solution, but this solution doesn't work for given any json object(firstObj), we don't know how many nested node are there, only know it has "mapped_field" & "operator". That's the thing – supunbatagoda May 28 '21 at 11:22
  • 1
    @supunmanaram oh, I think 'recursion' can help you in this case, I just updated my answer, hope it helps you. – Duy Quoc May 29 '21 at 01:34
  • @DuyQuac this seems work fine.. I really appreciate it. Thanks! – supunbatagoda May 31 '21 at 13:33
  • @supunmanaram You’re welcome, you might consider accepting the answer to helping others with the same problem as you can find a solution easily – Duy Quoc Jun 01 '21 at 02:42
  • @DuyQuac yeah I know, but I have little problem, what happened firstObj has array something like below 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 }, lines: [ { line_id: { mapped_field: 'req_id', operation: null, } } ] } – supunbatagoda Jun 03 '21 at 06:34
1

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/object-scan@18.1.2/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/object-scan@18.1.2/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!

vincent
  • 1,953
  • 3
  • 18
  • 24
  • I'm surprised, magically this works but I don't have any clue about how object-scan works. If you have free time only if, Could you please explain how this works.. – supunbatagoda May 31 '21 at 04:20
  • @supunmanaram For sure. Updated the answer. Let me know if you have any questions! – vincent Jun 01 '21 at 04:36
  • thanks for updating answer. I have quick little problem, let's assume firstObj is a json array object, then how to implemented this using object-scan package? – supunbatagoda Jun 04 '21 at 05:56
  • @supunmanaram It should just work? Do you have an example of what firstObj looks like? – vincent Jun 04 '21 at 15:07
  • const firstObj = { amount_money: { amount: { mapped_field: 'payment_amt', operation: '/10' }, currency: { mapped_field: 'payment_cur', operation: null } }, source_id: { mapped_field: 'source_id', operation: null }, ship: { [ { mapped_field: 'ship_country[0]', operation: null }, { mapped_field: 'ship_country[1]', operation: null } ] }; const secondObj = { payment_amt: 100, payment_cur: 'USD', request_id: '123ASD', ship_country: [ { code: 'USA', Title: 'America' }, { code: 'UK', Title: 'England' } ] }; – supunbatagoda Jun 09 '21 at 06:43
  • results would be { amount_money: { amount: 10 currency: 'USD' }, source_id: '123ASD', ship: [ {America: 'USA'}, {England: 'UK'}] } – supunbatagoda Jun 09 '21 at 06:45
  • 1
    @supunbatagoda You are probably better off creating a new question. Making big changes to old questions is frowned upon and is unlikely to get you an answer. I'll update my answer here, but please keep that in mind going forward. – vincent Jun 09 '21 at 16:52
  • yeah will do next time, thanks & really appreciate your time. – supunbatagoda Jun 10 '21 at 09:23
  • Feel free to accept this answer if it solves your question! – vincent Jun 10 '21 at 14:22