Transform/map json to another format - javascript

Have long JSON object and small part is below:
const valueObject = {zipCode: 12345, street1: 'street 1', street2: 'street 2',city:'cityname', uniqueNum: '123456789'};
const mappingObject = {address: {pinCode: zipCode, addressLine1: 'street1', addressLine2:'street2', id: 'uniqueNum', city: city}
const formatObject = {address: {pinCode: '', addressLine1: '', addressLine2:'', id: '0', city: ''}
Trying to transform valueObject to formatObject using mappingObject.
(if valueObject has no value then use default value from formatObject)
It doesn't get work. Seems have a object and not an array. Not sure what is wrong here, Or does it need to merge.
Object.keys(formatObject).map((k)=>{k: valueObject[mappingObject[k]] || mappingObject[k]});
Output expected:
{address: {pinCode: 12345, addressLine1: 'street 1', addressLine2: 'street 2',city:'cityname', id: '123456789'};
In case, any better option through ES6 (
Else case, using for loop and prepare object. )

You actually do not need the formatObject here because the mappingObject gives the full transformation schema, i.e. the relation between two structures. formatObject merely serves as an example of what the possible output looks like.
A feasible implementation would be:
function transform(schema, x) {
return Object.fromEntries(Object.entries(schema).map(([k, v]) => {
if (typeof v === 'string') return [k, x[v]]
else if (typeof v === 'object') return [k, transform(v, x)]
else throw new TypeError(`Schema entry cannot have type ${typeof v}`)
}))
}
transform(valueObject, mappingObject)
that works recursively through the schema to transform a flat-structured object (in this case valueObject) to a nested-structured object.

Related

Join object string values that exist and in a custom order

I am being passed an object containing address data (in alphabetical order) that I am wanting to extract a sub-set of to display, but in a different order.
Not all of the keys will have data in all of the time, sometimes having an empty string instead of nothing at all.
const address = {
buildingName: '',
company: 'My org',
county: 'My County',
postCode: 'My Postcode',
streetName: 'My street',
townCity: 'My Town'
};
I know that I can get all of the present values out by doing:
Object.keys(address).filter(Boolean).join(', ')
However I am wanting the order of the information being output to be: company, buildingName, streetName, townCity, county, postCode.
Is there any way in which I can alter my current solution, or do I need a different approach entirely?
I managed to get a solution combining Jan Pfeifer and Gog's solutions to some extent, along with my own experimentation.
It also transpired that fields with no value weren't being returned as empty strings, but weren't included, so that made things a bit easier.
const ADDRESS_ORDER = ['company', 'buildingName', 'streetName', 'townCity', 'county', 'postCode'];
return const addressString = ADDRESS_ORDER
.filter(detail => Object.keys(address).includes(detail))
.map(key => address[key])
.join(', ');
const ADDRESS_ORDER = ['company', 'buildingName', 'streetName', 'townCity', 'county', 'postCode'];
const address = {
company: 'My org',
county: 'My County',
postCode: 'My Postcode',
streetName: 'My street',
townCity: 'My Town'
};
const result = ADDRESS_ORDER
.filter(detail => Object.keys(address).includes(detail))
.map(key => address[key])
.join(', ');
console.log({result});
If this was any longer or more complex, I would consider tidying up by combining the fileter and map into a reduce, but I don't think it warrants this at the moment.
Because you need custom order you will have to create that object manually. You can use template array, but that is more or less the same solution.
const address = {
buildingName: '',
company: 'My org',
county: 'My County',
postCode: 'My Postcode',
streetName: 'My street',
townCity: 'My Town'
};
const tmp = ["company", "buildingName", "streetName", "townCity", "county", "postCode"];
let s = "";
tmp.forEach(key => {
const v = address[key];
if(v) {
if(s) s += ", ";
s += v;
}
});
console.log(s);
You could create an addressOrder array, to specify the order of the various address components, then use Array.reduce() to add to the output address components.
We'd only add the address to the output if it's present in the address object.
Finally we'd join the address components using the provided delimiter.
const address = {
buildingName: 'Nakatomi Towers',
company: 'Nakatomi Corp.',
county: 'LA County',
postCode: '90067',
streetName: 'Century City',
townCity: 'LA'
};
function formatAddress(address, addressOrder, delimiter = ', ') {
return addressOrder.reduce((outputFields, field) => {
if (address[field]) {
outputFields.push(address[field]);
}
return outputFields;
}, []).join(delimiter);
}
const order1 = ['company','buildingName','streetName','townCity','county','postCode']
const order2 = ['buildingName','streetName','townCity','postCode']
console.log('Address:', formatAddress(address, order1))
console.log('\nAddress (alt. format):\n' + formatAddress(address, order2, '\n'))
.as-console-wrapper { max-height: 100% !important; }

Refactoring a destructuring into an object destructuring

I am refactoring my code, which involves converting a big list of let statements into an object called personDetails:
personDetails = {
firstName: '',
lastName: '',
zipcode: 'xyz',
age: 20,
gender: 'm'
}
Currently, I am destructuring the values returned from my array like this:
[firstName, lastName] = getNames(zipcode, age, gender)
This works fine. But now that I am switching to an object, how do I update that object with the returned values? I will be passing in the object as an argument like this:
getNames(personDetails)
Do I have to do something like this?
personDetails = getNames(personDetails)
The called function might look something like this (abbreviated):
const getNames(personDetails) => {
personDetails.firstname = 'Jack'
personDetails.lastName = 'Jones'
}
1) Your arrow function had a typo, you must declare it with an = before the argument, like this:
const getNames = (personDetails) => { // Correct
const getNames(personDetails) => { // Incorrect
2) Inside your function, you weren't modifying an object key, but creating a new one instead. Remember that objects keys differs if you use upper or lowercase letters, firstName and firstname are not the same key.
3) Last, when you create an argument in your function, do not declare it with the same name of the global object, since it could create unexpected results. Then, you don´t need to destructure your object, just return the complete object.
let personDetails = { // Using let
firstName: '',
lastName: '',
zipcode: 'xyz',
age: 20,
gender: 'm'
};
const getNames = (obj) => { // obj is the argument
obj.firstName = 'Jack';
obj.lastName = 'Jones';
return obj; // Return complete object
}
personDetails = getNames(personDetails);
console.log(personDetails);
If you want to destructure the object, you can do it too the same way you do it with the array, but I wouldn´t recommend it because it makes the code less clear:
const personDetails = { // Using const
firstName: '',
lastName: '',
zipcode: 'xyz',
age: 20,
gender: 'm'
};
const getNames = (obj) => { // obj is the argument
obj.firstName = 'Jack';
obj.lastName = 'Jones';
return [obj.firstName, obj.lastName]; // Return part of object as an array
}
[personDetails.firstName, personDetails.lastName] = getNames(personDetails);
console.log(personDetails);

Cannot remove underscores mapping over an object using regex

I've run into questions similar to this one, but they generally pertain on how to remove an underscore whereas I am having the issue of removing the underscore in a for loop. When console logging in my function it will show that the fields I want changed do in fact change but when I return the object it only returns an object where all keys have snake case. I think this may be a scope issue but after playing around with the different placement of variables I keep getting stuck. Below is an example of the original object I would like to change:
const originalObj = {
accountNumber: '12345',
company: [
{
address: {
line1: '123',
line2: 'Spring Street'
}
}
],
ownerInfo: [
{
firstName: 'John',
lastName: 'Doe'
enrollment: '2015-10-15'
}
],
tax: {
code: '12345'
}
}
Using the npm package change-case-object I am able to successfully change all keys with camel case to snake case. However, I do not want any key that has a numeric value to include an underscore (like with the cases of line1 and line2). With this function
import changeCaseObject from 'change-case-object';
export function restructure(originalObj) {
const regex = /\d/g;
let newObj;
if(typeof originalObj === "object"){
newObj = changeCaseObject.snakeCase(originalObj);
for(const key in newObj){
if(typeof newObj[key] === "object" && newObj[key] !== null){
restructure(newObj[key])
//if the value is an object use recursion
} else {
if(regex.test(key)){
let newKey = key.replace(/_/g, '');
newObj.newKey = newObj.key;
delete newObj.key;
}
}
}
}
return newObj;
}
When I add a console log in the case where the key matches the regex, it will show that "line_1" does in fact change to "line1" along with "line_2". However when returning newObj it just returns the object in which all values now have snake case (as shown below)
{
account_number: '12345',
company: [
{
address: {
line_1: '123',
line_2: 'Spring Street'
}
}
],
owner_info: [
{
first_name: 'John',
last_name: 'Doe'
enrollment: '2015-10-15'
}
],
tax: {
code: '12345'
}
}
I feel like there is something very simple that I am overlooking but defining newObj outside of the if else statement has not led to success either. Thank you for your time.

better way to validate the fields in the javascript object

I want to validate the object so that the object is not null and few of the fields in that object is not empty.
For example, consider the following object:
address ={
block : '2134',
street : 'berly street',
county : 'someCountry'
postal : '876546'
}
I am using es6. I found the solution to do this using lodash
I want to do this in plain JS.
Please let me know if there is a better way to do this than below:
const isValidAddress = (address) => {
const addressFields = ['block', 'county', 'postal', 'street'];
return addressFields.every((element) => address[element] && address[element].length > 0);
};
You can try checking every value with Array.prototype.every like this.
const invalidAdress ={
block: '',
street: 'berly street',
county: 'someCountry',
postal: '876546'
};
const validAdress ={
block: '1234',
street: 'berly street',
county: 'someCountry',
postal: '876546'
};
// .every will return true if and only if every property in the object is a truthy value
const isValidAddress = (address) => Object.keys(address).every(x => address[x]);
console.log(isValidAddress(invalidAdress));
console.log(isValidAddress(validAdress));

Destructing and/or mass assigning in ES6

I have 2 sources of data. One of the sources is the "template" to what is acceptable for the data. However, the second source may have a large amount of data that I don't care about (100+ properties in the JSON). Here are the schemas:
// Only store the data we care about. Only a small subset of
// data that I need for this particular dataset.
state = {
isDirty: false,
data: {
name: '',
address: '',
city: '',
state: ''
}
}
The second source will have the 4 attributes in the data schema above (plus many many more I don't care about). Currently, I am assigning them like this:
let updatedData = {};
for(const key in this.state.data) {
updatedData[key] = someDataSource[key];
}
this.state.data = updatedData;
Using ES6, and perhaps destructing, is there a better way to mass assign variables like this?
Thanks again!
EDIT
Added for clarification the assignment after the loop.
Lodash pick can be used to pick specific keys, or helper function can be used for same purpose:
const pick = (obj, keys) => Object.keys(obj)
.filter((key) => keys.indexOf(key) >= 0)
.reduce(
(newObj, key) => Object.assign(newObj, { [key]: obj[key] }),
{}
);
This is already suggested in many related questions. The thing that is specific to this question is:
this.state.data = pick(someDataSource, Object.keys(this.state.data));
Properties can be excluded and modified in the JSON.parse reviver :
var o = JSON.parse('{"a":1, "b":2}', (k, v) => k === 'a' ? void 0 : k === 'b' ? 3 : v)
console.log( o )
A trick you can do (trick because it requires to swallow an error) is to use an non extensible object, using the Object.preventExtensions and then use Object.assign to fill it with data (in a try/catch block).
// Only store the data we care about. Only a small subset of
// data that I need for this particular dataset.
state = {
isDirty: false,
data: {
name: '',
address: '',
city: '',
state: ''
}
}
const newData = {
name:'name',
address:'address',
city:'city',
state:'state',
phone:'phone',
zip:'zip'
}
const updatedData = Object.preventExtensions({...state.data});
try{
Object.assign(updatedData, newData);
} catch(throwaway){};
console.log(updatedData);
And as a function for reuse
function schemaMerge(schema, data) {
const mergedData = Object.preventExtensions({...schema});
try {
Object.assign(mergedData, data);
} catch (throwaway) {};
return ({...mergedData}); // create a new object from the merged one so that it no longer is extensionless
}
// Only store the data we care about. Only a small subset of
// data that I need for this particular dataset.
state = {
isDirty: false,
data: {
name: '',
address: '',
city: '',
state: ''
}
}
const newData = {
name: 'name',
address: 'address',
city: 'city',
state: 'state',
phone: 'phone',
zip: 'zip'
}
const updatedData = schemaMerge(state.data, newData);
state.data = updatedData;
console.log(state.data);

Categories

Resources