JavaScript: How to map two objects to get the output which map the Id of first object to the name of other object?

Viewed 665

I am trying to make a function that maps the data2 object value to the name of the data1.

I tried to iterate data2 object2 with data1 but it is not working properly. I am able to map them but not getting the value of data1 when it is not there in data2.

Is there any way I can map properly to get the desired output mentioned in the code?

let data1 = {
    attributes: [{
            Id: 'test1',
            Name: 'Test1',
            Type: 'date'
        },
        {
            Id: 'test2',
            Name: 'Test2',
            Type: 'string'
        },
        {
            Id: 'test3',
            Name: 'Test3',
            Type: 'string'
        },
        {
            Id: 'test4',
            Name: 'Test4',
            Type: 'boolean'
        }
    ]
};

let data2 = {
    value: [{
            test1: '10-12-2021',
            test2: '4',
            dummy: 'ignore me'
        },
        {
            test3: '3',
            test4: true,
            abc: 'ignore me'
        },
        {
            test1: '12-12-2023',
            test3: '42',
            dummy1: 'ignore me'
        }
    ]
};

//output

let ouput = {
    rows: [{
            Test1: '10/12/2021',
            Test2: '4',
            Test3: '',
            Test4: ''
        },
        {
            Test1: '',
            Test2: '',
            Test3: '3',
            Test4: 'Y'
        },
        {
            Test1: '12/12/2023',
            Test2: '',
            Test3: '42',
            Test4: ''
        }
    ]
};

//function

function mapper(data1, data2) {
    let formattedValue = [];
    data2.value.forEach(val => {
        let data = {};
        for (let prop in val) {
            let name;
            const filter = data1.attributes.filter(el => el.Id === prop)[0];
            if (filter) {
                name = filter.Name;
                switch (filter.Type) {
                    case 'boolean':
                        data[name] =
                            val[prop] === true ? 'Y' : val[prop] === false ? 'N' : '';
                        break;
                    case 'date':
                        data[name] = new Date(val[prop]).toLocaleDateString();
                        break;
                    default:
                        data[name] = val[prop];
                        break;
                }
            }
        }
        formattedValue.push(data);
    });

    return formattedValue;
}

console.log(mapper(data1, data2));

same if I pass data2 as empty value I am looking to get below output

let data1 = {
  attributes: [
    {
      Id: 'test1',
      Name: 'Test1',
      Type: 'string'
    },
    {
      Id: 'test2',
      Name: 'Test2',
      Type: 'string'
    },
    {
      Id: 'test3',
      Name: 'Test3',
      Type: 'string'
    },
    {
      Id: 'test4',
      Name: 'Test4',
      Type: 'boolean'
    }
  ]
};

let data2 = {
  value: [
  ]
};

//output

let ouput = {
  rows: [
    {
      Test1: '',
      Test2: '',
      Test3: '',
      Test4: ''
    },
  ]
};

4 Answers

const data1 = {attributes:[{Id:'test1',Name:'Test1',Type:'date'},{Id:'test2',Name:'Test2',Type:'string'},{Id:'test3',Name:'Test3',Type:'string'},{Id:'test4',Name:'Test4',Type:'boolean'}]}
      data2 = {value:[{test1:'10-12-2021',test2:'4'},{test3:'3',test4:true},{test1:'12-12-2023',test3:'42'}]};
      data3 = {value: []}

const mapper = (x, y) => {
  const row = x.attributes.map(e => [e.Id, e.Name])
        format = e => { switch (true) {
            case typeof e === 'boolean':
              return e ? 'Y' : 'N'
            case isNaN(e) && !isNaN(Date.parse(e)):
              return new Date(e).toLocaleDateString()
            default: return e
          }}
  const rows = (y.value.length ? y.value : [1]).map(e => 
     Object.fromEntries(row.map(([k, v]) => [v, format(e[k]) ?? '']))
  )
  return { rows }
} 

console.log(mapper(data1, data2))
console.log(mapper(data1, data3))
.as-console-wrapper { max-height: 100% !important; top: 0; }

Explanation:

y.value.length ? y.value : [1]

Checks if data.value is empty, if not returns data.value. If yes returns array with 1 element (can be any value, it is just needed to do 1 iteration to meet your requirement about empty data, so value doesn't really matter here.

Rest of code should be pretty clear, if not, let me know.

UPDATE

because you want minimum one set of values even data2.values is empty. My already given solution will work if we add empty object when array is empty, so adding below will fix it.

// I had to add below lines to get your desired result
// if `data2.value` is empty array
const arrayToLoop = [...data2.value];  
if(arrayToLoop.length === 0) {
  arrayToLoop.push({});
}

and now we will loop variable arrayToLoop rather than data2.value

also wrote small function used to convert the value as per given type, you can extend it as per your own need

function parseValue(type, value){
    if(value.length==0){
      return '';
    }
    switch(type.toLowerCase()){
      case 'boolean':
        return value === true ? 'Y' : value === false ? 'N' : '';
      case 'date':
        return new Date(value).toLocaleDateString();        
      default:
        return value;
    }
}

first I will get all the key and label mapping, and will look all the values for each key. notice how I done below.

let me know if you face any prob.

function parseValue(type, value){
    if(value.length==0){
      return '';
    }
    switch(type.toLowerCase()){
      case 'boolean':
        return value === true ? 'Y' : value === false ? 'N' : '';
      case 'date':
        return new Date(value).toLocaleDateString();        
      default:
        return value;
    }
}

function BusinessObjectMapper(data1, data2){
  const arrayToLoop = [...data2.value];  
  if(arrayToLoop.length === 0) {
      arrayToLoop.push({});
  }

  const rows = arrayToLoop.map(v=>{
     let r = {};
     data1.attributes.forEach(o=>{
      let {Id, Name, Type} = o;
      r[Name]=parseValue(Type, v[Id]||'');
     })
     return r;
  })
  
  return {
    rows
  };
}

let data1 = {
  attributes: [
    {
      Id: 'test1',
      Name: 'Test1',
      Type:'string'
    },
    {
      Id: 'test2',
      Name: 'Test2',
      Type:'boolean',
    },
    {
      Id: 'test3',
      Name: 'Test3',
      Type:'date'
    },
    {
      Id: 'test4',
      Name: 'Test4',
      Type:'un-known',
    }
  ]
};

let data2 = {
  value: [
    {
      test1: '1',
      test2: true
    },
    {
      test3: '02/02/2020',
      test4: '42'
    },
    {
      test1: 'deepak',
      test3: '01/01/2021'
    }
  ]
};

let data2_empty = {
  value: []
}

console.log(BusinessObjectMapper(data1, data2));
console.log(BusinessObjectMapper(data1, data2_empty));

I solved the object type problem by using the spread operator and Array.prototype.reduce().

const data1 = {
      attributes: [
        {
          Id: "test1",
          Name: "Test1",
          Type: "date",
        },
        {
          Id: "test2",
          Name: "Test2",
          Type: "string",
        },
        {
          Id: "test3",
          Name: "Test3",
          Type: "string",
        },
        {
          Id: "test4",
          Name: "Test4",
          Type: "boolean",
        },
      ],
    };

    const data2 = {
      value: [
        {
          test1: "10-12-2021",
          test2: "4",
        },
        {
          test3: "3",
          test4: true,
        },
        {
          test1: "12-12-2023",
          test3: "42",
        },
      ],
    };

    //function

    const data11 = {
      attributes: [
        {
          Id: "test1",
          Name: "Test1",
          Type: "string",
        },
        {
          Id: "test2",
          Name: "Test2",
          Type: "string",
        },
        {
          Id: "test3",
          Name: "Test3",
          Type: "string",
        },
        {
          Id: "test4",
          Name: "Test4",
          Type: "boolean",
        },
      ],
    };

    const data12 = {
      value: [],
    };

    function mapper(data1, data2) {
      const columns = data1.attributes.reduce((prev, item) => {
        prev[item["Id"]] = item;
        return prev;
      }, {});
      const emptyValues = Object.keys(columns).reduce((prev, item) => {
        prev[columns[item].Name] = "";
        return prev;
      }, {});
      const getValueByType = (key, v) => {
        switch (columns[key].Type) {
          case "boolean":
            return v ? "Y" : "N";
          case "date":
            return new Date(v).toLocaleDateString();
          default:
            return v;
        }
      };

      return {
        rows:
          (data2.value &&
            (data2.value.length == 0
              ? [emptyValues]
              : data2.value.map((row) =>
                  Object.entries(row).reduce(
                    (rlt, [key, v]) => {
                      rlt[columns[key].Name] = getValueByType(key, v);
                      return rlt;
                    },
                    { ...emptyValues }
                  )
                ))) ||
          [],
      };
    }

    const result1 = mapper(data1, data2);
    console.log(result1.rows);
    const result2 = mapper(data11, data12);
    console.log(result2);

@ulou's answer is great!. It can be done using reduce. I updated a source a little.

const mapper = (arr1, arr2) => {
    const keys = arr1.attributes.map(item => ({ Id: item.Id, Name: item.Name }));
    const format = e => {
        switch (true) {
            case typeof e === 'boolean': return e ? 'Y' : 'N'
            case isNaN(e) && !isNaN(Date.parse(e)):
                return new Date(e).toLocaleDateString()
            default: return e
        }
    }

    const mapped = (arr2.value ?? [0]).map(item => keys.reduce((prev, cur) => (
        { ...prev, [cur.Name]: format(item[cur.Id] ?? '') }
    ), {}));

    return { rows: mapped };
}

console.log(mapper(data1, data2));
console.log(mapper(data1, []));
Related