Javascript overwrite part of a nested object with object merging - javascript

I'm working on a JS project where I need to override some values in my object which contains nested objects.
I'd normally use the following:
const merged = { ...application, ...customer }
So that any data inside of customer can override the application.
However, in my example, customer is overriding the entire applicant nested object and I just need to override the name within that object?
I've put together a JS fiddle which can be found here, what am I missing?
const customer = {
applicant: {
first_name: 'john'
}
}
const application = {
applicant: {
first_name: 'edward',
age: 50
},
income: {
salary: 500
}
}
const merged = { ...application, ...customer }
console.log(merged)
In merged I expect the first_name to be "John" whilst everything else remains in tact.

The properties you want to replace via spreading have to be at the top level of the object. In this case you take the top-level properties of application, which are applicant and income, and then replace the applicant property with that from customer. If you want the name to be replaced you would need something like
const merged = {
...application,
applicant: {...application.applicant, ...customer.applicant}
};

You can do this easily with lodash
const merged = _.merge(customer, application)
https://lodash.com/docs/4.17.15#merge

Related

Render nested string inside of object

I'm trying to render a dynamic list of fields from a JSON file.
Some fields have to go through this accountFieldMap object I created for key renaming purposes.
For example it finds the key userFirstName1 from the JSON and renders the value of it as firstName at the component.
const accountFieldMap = {
firstName: "userFirstName1",
lastName: "userLastName1",
ID: "userID",
location: `userLocation.city`,
};
The only issue is with the location field.
How can I let JavaScript know that it should render that city nested field and show it as location?
If I understand you correctly, location.city is a path to some value in object.
There are some libraries for this like lodash, which have inbuilt functions that can resolve that, but if you want to do it in vanilla js, you can do it by splitting this string by dot and going through that array to get a value.
const getByPath = (path, obj) => {
const splittedPath = path.split(".");
return splittedPath.reduce((acc, curr) => {
acc = obj[curr];
return acc;
}, obj)
}
So in this case if you have object like
const testObj = {
location: {city: "Kyiv"},
firstName: "Oleg"
}
It will return you "Kyiv" if you will pass into getByPath "location.city" as path. And it will also work in case if there is no nesting, so
getByPath("firstName", testObj)
will return you "Oleg"
you only have to map the array and create a new object;
import fileData from "../path/to/json";
const people = fileData.arrayName.map(person => ({
firstName: person.userFirstName1,
lastName: person.userLastName1,
ID: person.userID,
location: person.userLocation.city,
}));

React set state to nested object value

I need to set state on nested object value that changes dynamically Im not sure how this can be done, this is what Ive tried.
const [userRoles] = useState(null);
const { isLoading, user, error } = useAuth0();
useEffect(() => {
console.log(user);
// const i = Object.values(user).map(value => value.roles);
// ^ this line gives me an react error boundary error
}, [user]);
// This is the provider
<UserProvider
id="1"
email={user?.email}
roles={userRoles}
>
The user object looks like this:
{
name: "GGG",
"website.com": {
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
I need to grab the roles value but its parent, "website.com" changes everytime I call the api so i need to find a way to search for the roles.
I think you need to modify the shape of your object. I find it strange that some keys seem to be fixed, but one seems to be variable. Dynamic keys can be very useful, but this doesn't seem like the right place to use them. I suggest that you change the shape of the user object to something like this:
{
name: "GGG",
site: {
url: "website.com",
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
In your particular use case, fixed keys will save you lots and lots of headaches.
You can search the values for an element with key roles, and if found, return the roles value, otherwise undefined will be returned.
Object.values(user).find(el => el.roles)?.roles;
Note: I totally agree with others that you should seek to normalize your data to not use any dynamically generated property keys.
const user1 = {
name: "GGG",
"website.com": {
roles: ["SuperUser"],
details: {}
},
friends: {},
otherData: {}
}
const user2 = {
name: "GGG",
friends: {},
otherData: {}
}
const roles1 = Object.values(user1).find(el => el.roles)?.roles;
const roles2 = Object.values(user2).find(el => el.roles)?.roles;
console.log(roles1); // ["SuperUser"]
console.log(roles2); // undefined
I would recommend what others have said about not having a dynamic key in your data object.
For updating complex object states I know if you are using React Hooks you can use the spread operator and basically clone the state and update it with an updated version. React hooks: How do I update state on a nested object with useState()?

How to get the name and props of global Objects in Javascript

I understand that variable names are not intrinsic properties of an object and thus cannot be retrieved when passed through functions. But here is my use-case and I'd like to create a mapping between members of UniversityEnums and displayStrings to get the display string.
const UniversityEnums = {
studentStatus: {Enrolled: 'Enrolled', OnHold: 'OnHold', Expelled: 'Expelled'},
professorStatus: {FullTime: 'FullTime', PartTime: 'PartTime', Emeritus: 'Emeritus', Expelled: 'Expelled'}
};
and
const displayStrings = {
studentStatus_Enrolled: 'Student is enrolled in the program',
studentStatus_OnHold: 'Student decided not to participate',
studentStatus_Expelled: 'Student was expelled',
professorStatus_FullTime: 'Staff member is hired fulltime',
professorStatus_PartTime: 'Staff member is hired parttime',
professorStatus_Emeritus: 'Staff member is retired',
professorStatus_Expelled: 'Staff member was expelled'};
My goal is to write a function that grabs a member of UniversityEnums and returns the corresponding display string, for example:
const expelledStudentDispStr = getDispString(UniversityEnums.studentStatus.Expelled);
console.log(expelledStudentDispStr);
// Student was expelled
The code I currently have has two input arguments and is like const expelledStudentDispStr = getDispString('studentStatus', UniversityEnums.studentStatus.Expelled); which needs the name of the enum to get the value but I am looking for an even smarter way!
Thanks in advance.
Note: that I can manipulate the enum object (for example define it with different variable names (or maybe, maybe, append other properties to it or its children). However, I CANNOT change their values because those values are used to compare those statuses against other variables. Also, the displayStrings is coming from a third party source and modifying them for me is not doable (at least easily!)
Possible workaround:
One way that comes to my mind is to modify my enum object to have names that match displayStrings keys:
const UniversityEnums = {
studentStatus: {
studentStatus_Enrolled: 'Enrolled',
studentStatus_OnHold: 'OnHold',
studentStatus_Expelled: 'Expelled'},
professorStatus: {
professorStatus_FullTime: 'FullTime',
professorStatus_PartTime: 'PartTime',
professorStatus_Emeritus: 'Emeritus',
professorStatus_Expelled: 'Expelled'}
};
Your
getDispString(UniversityEnums.studentStatus.Expelled)
is the same as
getDispString("Expelled")
and it would still be the same with your new object as
getDispString(UniversityEnums.studentStatus.studentStatus_Expelled)
you don't give it more information, on the other hand you could transform your enum like that
const UniversityEnums = {
studentStatus: {
Enrolled: {
status: 'Enrolled',
entity: 'student'
},
OnHold: {
status: 'OnHold',
entity: 'student'
},
Expelled: {
status: 'Expelled',
entity: 'student'
}
}
};
so you would give the extra information you need
you could do something like
for (const kind in UniversityEnums)
for (const value in UniversityEnums[kind])
UniversityEnums[kind][value] = {
kind: kind,
value: value
}
from Bergi's answer
Note that I can manipulate the enum object
In that case, it's easy: just put unique values in the enums so that you can distinguish them properly. For example,
for (const kind in UniversityEnums)
for (const value in UniversityEnums[kind])
UniversityEnums[kind][value] = kind + '_' + value;
With that you can write
function getDispString(enumValue) {
return displayStrings[enumValue];
}

Excluding missing keys from JSON comparison

I have two JSON documents that I want to assert equal for Jest unit testing. They should be equal, except the second one has one more key: _id.
Example:
doc1.json
{
username: 'someone',
firstName: 'some',
lastName: 'one',
}
doc2.json
{
_id: '901735013857',
username: 'someone',
firstName: 'some',
lastName: 'one',
}
My code currently looks like this:
const result = await activeDirectoryUserCollection
.findOne({username: testUser1.username});
expect(result).toBe(testUser1);
Obviously this gives the error that they are not equal, just because of that one value.
I'm looking for an alternative to .toBe() that doesn't completely compare the docs, but checks if one is a subset of another. (or something like that).
Alternatively I would appreciate someone to point me to a module that could help me out.
I would checkout Lodash module's .isMatch function.
It performs a partial deep comparison between object and source to determine if object contains equivalent property values.
https://lodash.com/docs/4.17.11#isMatch
Example:
var object = { 'a': 1, 'b': 2 };
_.isMatch(object, { 'b': 2 });
// => true
_.isMatch(object, { 'b': 1 });
// => false
You can iterate through one Object and use the key to assert value in both Objects. Read More for...in
const result = await activeDirectoryUserCollection
.findOne({username: testUser1.username});
for (const prop in testUser1) {
if (testUser1[prop]) {
expect(result[prop]).toBe(testUser1[prop]); // or use .toEqual
}
}
I don't think you need to look outside jest for this. You can use expect.objectContaining(), which is described in the docs as:
expect.objectContaining(object) matches any received object that recursively matches the expected properties. That is, the expected object is a subset of the received object. Therefore, it matches a received object which contains properties that are present in the expected object.
You could use it like:
test('objects', () => {
expect(doc2).toEqual(
expect.objectContaining(doc1)
);
});

Create new object from existing object with new values Angular / Ionic 3

I am working on an Ionic 3 project,which uses Angular.
I have a JSON object like below called person. However, I have a Ionic toggle button which enables
various sections based on whats returned from person.
person = { name: "peter", job: "programmer", home: "somewhere"};
person_checked_values = {}
In order to update my toggles I need to pass a boolean. The keys are the same. How can I dynamically build a new object off of
whats returned from person KEYs, but set the value as true so person_checked_values results like below?
person_checked_values = { name: true, job: true, home: true};
I tried to foreach loop person and create a new object from that, but keep getting undefined and stumped. FWIW - I am using _lodash as well so if there is possibly someway to use help from that library its available.
You can use Object.keys to get all of the keys. You can then combine that with the .reduce function of arrays to build an object.
let person = {
name: "peter",
job: "programmer",
home: "somewhere"
};
let result = Object.keys(person).reduce((obj, key) => {
obj[key] = true;
return obj;
}, {})
console.log(result);

Categories