Javascript | Map to object by dot notation [closed] - javascript

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I currently have a string which is like the following author.id.
Essentially what I want to do is convert this string into an object. The first becoming a relationship and then the last being an attribute. However the list could actually become longer example:
author.sites.id which would then become the following object:
{
author: {
relationships: {
sites: {
attributes: {
id: 1
}
}
}
}
}
There will always be a top level property on the object being relationships as that is the purpose of doing this, mapping it to a JSON API Spec POST request.
So the example of author.id would produce a object with the following properties:
{
relationships: {
author: {
attributes: {
id:
}
}
}
}
So essentially all of the properties that are split by the . should be nested within a parent property of relationships and the last should always be attributes.
I know you could probably used reduce for this and I have created the following function based off other questions on Stack Overflow but it doesn't do the nesting correctly any doesn't have attributes as a property for the final object which is output.
This is the method which I made based off of other questions:
function mapToObject(object, index) {
index.split('.').reduce(function (obj, i) {
return obj[i];
});
}
Another example based off real world is I have an array of strings...
[{ key: 'author.id', value: '1'}, { key: 'image.id', value: '1'}]
I am then going to loop through these and add them as an object property to a object which is predefined.
let mappedRelationships = {};
let relationships = [
{
key: 'author.id',
value: 1
},
{
key: 'primaryImage.id',
value: 12
}
];
relationships.forEach((relationship) => {
mappedRelationships = {
...mappedRelationships,
// This is where I would need to do the logic for the reduce
}
});
The value of the relationshipsMapped var would be the following:
{
author: {
attributes: {
id: 1
}
},
primaryImage: {
attributes: {
id: 12
}
}
}

You can reduce and create an object with the key-value pair by splitting key in relationship array.
let relationships = [
{
key: 'author.id',
value: 1
},
{
key: 'primaryImage.id',
value: 12
}
];
let mappedRelationships = relationships.reduce((acc, {key, value}) => {
let [k, v] = key.split('.');
acc[k] = {attributes: {[v]: value}};
return acc
}, {});
console.log(mappedRelationships)
You could use condition if string has 3 params
let relationships = [
{
key: 'author.sites.id',
value: 1
},
{
key: 'primaryImage.id',
value: 12
}
];
let mappedRelationships = relationships.reduce((acc, { key, value }) => {
let arr = key.split('.');
let extra = arr.length > 2 ? arr.shift() : null;
let [k1, k2] = arr;
let relationships = { [k1]: { attributes: { [k2]: value } } };
return extra ? {...acc, [extra]:{relationships}} : {...acc, relationships};
}, {});
console.log(JSON.stringify(mappedRelationships))

You could
create an array of keys with the wanted and dynamic parts,
take the last key,
reduce the keys and assign an object if the property has a falsy value,
finally assign the value by taking the last key.
function create(object, { key, value }) {
const parts = ['relationship', 'attributes'],
keys = key.split('.').flatMap((k, i) => [parts[i], k]),
last = keys.pop();
keys.reduce((o, k) => o[k] = o[k] || {}, object)[last] = value;
return object;
}
var data = [{ key: 'author.id', value: '1' }, { key: 'image.id', value: '1' }],
object = data.reduce(create, {});
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }

UPDATE: Based on question change, Updated to support multipule levels
Using with reduce, split and destructuring.
let relationships = [
{
key: "author.sites.id",
value: 1
},
{
key: "primaryImage.id",
value: 12
}
];
const updated = relationships.reduce((acc, { key, value }) => {
const keys = key.split(".");
let item = { attributes: { [keys.pop()]: value } };
while (keys.length > 0) {
item = {
[keys.pop()]: {...item}
}
};
return {
...acc,
...item
};
}, {});
console.log(updated);

Related

How can I return a dynamic data using methods in JavaScript?

I am having a variable that needs to return data according to the array of objects.
There are 3 dummies which are dynamic in such a way -->
Example:
dummy1_A, dummy1_B, dummy1_C, ...
dummy2_A, dummy2_B, dummy2_C, ...
dummy3_A, dummy3_B, dummy3_C, ...
I want to set 'fieldName' and 'text' while returning.
And this is my code. I have to use JSON.stringify to show the data I need
Also, I am using map method
let a1 = [
{
dummy1_A: 0.5526714707565221,
dummy2_A: 0.5526714707565223,
dummy3_A: 0.5526714707565224,
dummy1_B: 0.5028423429150607,
dummy2_B: 0.5028423429150605,
dummy3_B: 0.5028423429150604,
},
{
dummy1_A: 0.542947572819916,
dummy2_A: 0.4965857885945633,
dummy3_A: 0.4965857885945677,
dummy1_B: 0.4470431086251489,
dummy2_B: 0.3785646261205342,
dummy3_B: 0.3785646261205345,
},
];
let a2 = a1.map((x, i) => {
let seqStr = String(a1.entries); // I need some help here
return {
text: seqStr,
fieldName: seqStr,
};
});
// output
[
{ text: 'dummy1_A', fieldName: 'A' },
{ text: 'dummy2_A', fieldName: 'A' },
{ text: 'dummy1_B', fieldName: 'B' },
{ text: 'dummy2_B', fieldName: 'B' },
];
We can also use forEach but this also need more logic
a1.forEach(obj => {
const key = Object.keys(obj)[0];
const newKey = key[key.length - 1];
obj[newKey] = obj[key];
delete obj[key];
});
I have used console.log(JSON.stringify(a2)); Though I was using map but not got such result
let newHeaders1 = a1.map( i => {
return {
text: i,
fieldName: 'dummy1_'+i,
width: 110
};
});
Use this and create 2 more arrays with 2 and 3 respectively.
Then consolidated all the variable in an array using concat or spread operator

How to convert an array of objects into a single object using reduce?

I have the following array of object.
[
{ claimNumber1: 'R12345', checkNumber1: '' },
{ claimNumber2: 'T1234', checkNumber2: 'abcd' },
{ claimNumber3: 'Z4567', checkNumber3: 'qwer' }
]
Using reduce, I want to convert this to below.
{
claimNumber1:'R12345',
checkNumber1:'',
claimNumber2:'T1234',
checkNumber2:'',
claimNumber3:'Z4567',
checkNumber3:'',
}
I tried below but didn't get what I expected.
.reduce((obj, item) =>{
return {...obj,item}
} ,{});
You should spread the item object, because item is an object
const arr = [
{ claimNumber1: "R12345", checkNumber1: "" },
{ claimNumber2: "T1234", checkNumber2: "abcd" },
{ claimNumber3: "Z4567", checkNumber3: "qwer" },
];
const result = arr.reduce((obj, item, i) => {
return { ...obj, ...item, [`checkNumber${i + 1}`]: "" };
}, {});
console.log(result);
Almost there. You just need to spread the item as well.
.reduce((obj, item) => {
return {
...obj,
...item,
};
}, {});
I think you should spread every item in reducer.
Here is my code.
const res = arr.reduce((prev, item) => {
return { ...prev, ...item };
}, {});
And result is
{
claimNumber1: 'R12345',
checkNumber1: '',
claimNumber2: 'T1234',
checkNumber2: 'abcd',
claimNumber3: 'Z4567',
checkNumber3: 'qwer'
}
I'm not sure of the benefit of using reduce in this situation. A simple loop would be self-documenting, and easier to read.
const data = [
{ claimNumber1: 'R12345', checkNumber1: '' },
{ claimNumber2: 'T1234', checkNumber2: 'abcd' },
{ claimNumber3: 'Z4567', checkNumber3: 'qwer' }
];
const out = {};
// For every object in the array
for (const obj of data) {
// Get an array of its keys and iterate over them
for (const key of Object.keys(obj)) {
// If it's a claim add that value to the output
// object property, otherwise set that property value
// to an empty string.
if (key.startsWith('claim')) {
out[key] = obj[key];
} else {
out[key] = '';
}
}
}
console.log(out);
Additional documentation
Object.keys

How to add a property to a dynamically changing object from an API?

I hope someone can help me with my problem! I didn't find the right thing through the search and maybe someone can give me a hint.
I am calling an API that returns an object that in turn contains nested objects. In these nested objects there are two properties "value" and "scale". I want to divide these two properties and write them as properties in the same object.
The data I get from the API is dynamic, which means it is constantly changing.
Example:
// call api
const apiCall = callApi(...);
// return object
console.log(apiCall);
{
id: '3454353458764389759834534534',
json_data: {
persons: {
de: {
name: 'Bob',
data: {
scale: 100,
value: 2459,
},
},
be: {
name: 'Alice',
data: {
scale: 10000,
value: 1459,
},
},
},
url: 'https://stackoverflow.com/',
timestamp: '2021-10-23T12:00:11+00:00',
disclaimer: 'Some bla bla',
},
}
// targed object
const objTarged = {
id: '3454353458764389759834534534',
json_data: {
persons: {
de: {
name: 'Bob',
data: {
scale: 100,
value: 2459,
result: 24.59 // value / scale = result
},
},
be: {
name: 'Alice',
data: {
scale: 10000,
value: 1459,
result: 0.1459 // value / scale = result
},
},
},
url: 'https://stackoverflow.com/',
timestamp: '2021-10-23T12:00:11+00:00',
disclaimer: 'Some bla bla',
},
};
My thoughts:
do I need to map the object into a new object?
how can I do this if the source object is constantly changing (Object.values?)
How can I write the result of Value / Scale as a new property in the same object each time I call the API?
Big thanks in advance :)
It might be helpful to decompose the problem into first finding the nested objects with the keys you're interested in. Having done that, it will be easy to augment those objects with the desired calculation.
Below is a sort of generic function that finds a nested object based on it having a particular key. With that, fixMyApiData writes itself...
// return an array of objects that are nested in the passed object which contain the passed key
function objectsContainingKey(object, key) {
let results = [];
Object.keys(object).forEach(k => {
if (k === key) results.push(object);
if (object[k] && typeof object[k] === 'object')
results = results.concat(objectsContainingKey(object[k], key));
});
return results;
}
// find the nested objects we care about and augment them with the value/scale calculation
function fixMyApiData(apiData) {
objectsContainingKey(apiData, 'scale').forEach(data => {
if (data.value) data.result = data.value / data.scale;
})
}
let apiData = {
id: '3454353458764389759834534534',
json_data: {
persons: {
de: {
name: 'Bob',
data: {
scale: 100,
value: 2459,
},
},
be: {
name: 'Alice',
data: {
scale: 10000,
value: 1459,
},
},
},
url: 'https://stackoverflow.com/',
timestamp: '2021-10-23T12:00:11+00:00',
disclaimer: 'Some bla bla',
},
};
fixMyApiData(apiData);
console.log(apiData);
I would create a mapValues() function that takes an object, and creates a new object by passing each of the object's values in a transforming function.
Whenever the api call returns a new object, we recreate the new object with the result property according to the structure.
How does the mapValues function works?
Whenever an object (or array) is passed to mapValues, it's converted to an array of [key, value] pairs. The pairs are then mapped to new [key, pair] entries by applying transformFn to the value. The transform array of pairs is then converted back to an using Object.fromEntries().
const mapValues = (transformFn, obj) => Object.fromEntries(
Object.entries(obj)
.map(([key, value]) => [key, transformFn(value)])
)
const apiCall = {"persons":{"de":{"name":"Bob","scale":100,"value":2459},"be":{"name":"Alice","scale":10000,"value":1459}}}
const result = mapValues(
val => mapValues(v => ({
...v,
result: v.value / v.scale,
}), val),
apiCall
)
console.log(result)
If you have multiple nested levels with properties you don't want to transform, we can also pass the key to the transformFn for a more granular change. Now we can create a recursive function to traverse the tree, and only update objects which have a specific key.
const mapValues = (transformFn, obj) => Object.fromEntries(
Object.entries(obj)
.map(([key, value]) => [key, transformFn(value, key)])
)
const fn = obj => mapValues(
(val, key) => {
// if the key is data create a new object with a result property
if(key === 'data') return ({
...val,
result: val.value / val.scale,
})
// if it's object pass it to the recursive function
if(typeof val === 'object') return fn(val)
return val
},
obj
)
const apiCall = {"id":"3454353458764389759834534534","json_data":{"persons":{"de":{"name":"Bob","data":{"scale":100,"value":2459}},"be":{"name":"Alice","data":{"scale":10000,"value":1459}}},"url":"https://stackoverflow.com/","timestamp":"2021-10-23T12:00:11+00:00","disclaimer":"Some bla bla"}}
const result = fn(apiCall)
console.log(result)

Update fields in nested objects in Typescript / Javascript

In Firestore you can update fields in nested objects by a dot notation (https://firebase.google.com/docs/firestore/manage-data/add-data?authuser=0#update_fields_in_nested_objects). I wonder how to make that work in Typescript / Javascript.
For example the following object:
const user = {
id: 1
details: {
name: 'Max',
street: 'Examplestreet 38',
email: {
address: 'max#example.com',
verified: true
}
},
token: {
custom: 'safghhattgaggsa',
public: 'fsavvsadgga'
}
}
How can I update this object with the following changes:
details.email.verified = false;
token.custom = 'kka';
I already found that Lodash has a set function:
_.set(user, 'details.email.verified', false);
Disadvantage: I have to do this for every change. Is their already a method to update the object with an object (like firestore did)?
const newUser = ANYFUNCTION(user, {
'details.email.verified': false,
'token.custom' = 'kka'
});
// OUTPUT for newUser would be
{
id: 1
details: {
name: 'Max',
street: 'Examplestreet 38',
email: {
address: 'max#example.com',
verified: false
}
},
token: {
custom: 'kka',
public: 'fsavvsadgga'
}
}
Does anyone know an good solution for this? I already found more solutions if I only want to change one field (Dynamically set property of nested object), but no solution for more than one field with one method
I think you are stuck with using a function but you could write it yourself. No need for a lib:
function set(obj, path, value) {
let parts = path.split(".");
let last = parts.pop();
let lastObj = parts.reduce((acc, cur) => acc[cur], obj);
lastObj[last] = value;
}
set(user, 'details.email.verified', false);
if what you want to do is merge 2 objects then it is a bit trickier:
function forEach(target, fn) {
const keys = Object.keys(target);
let i = -1;
while (++i < keys.length) {
fn(target[keys[i]], keys[i]);
}
}
function setValues(obj, src) {
forEach(src, (value, key) => {
if (value !== null && typeof (value) === "object") {
setValues(obj[key], value);
} else {
obj[key] = value;
}
});
}
let obj1 = {foo: {bar: 1, boo: {zot: null}}};
let obj2 = {foo: {baz: 3, boo: {zot: 5}}};
setValues(obj1, obj2);
console.log(JSON.stringify(obj1));
One solution in combination with lodash _.set method could be:
function setObject(obj, paths) {
for (const p of Object.keys(paths)) {
obj = _.set(obj, p, paths[p]);
}
return obj;
}

How to deeply remove keys in object?

I have this json object returned from an API that has a few quirks, and I'd like to normalize it so I can process the input the same for every response. These means getting rid of superfluous keys:
Response:
{
_links: {...},
_embedded: {
foo: [
{
id: 2,
_embedded: {
bar: []
}
}
]
}
}
So I'd like to remove all the _embedded keys and flatten it, like so:
{
_links: {...},
foo: [
{
id: 2,
bar: []
}
]
}
This is what I have at the moment, but it only works for the top level and I don't think it'll play well with arrays.
_.reduce(temp1, function(accumulator, value, key) {
if (key === '_embedded') {
return _.merge(accumulator, value);
}
return accumulator[key] = value;
}, {})
Loop in recursion on all of your keys, once you see a key which start with _
simply remove it.
Code:
var
// The keys we want to remove from the Object
KEYS_TO_REMOVE = ['_embedded'],
// The data which we will use
data = {
_links: {'a': 1},
_embedded: {
foo: [
{
id: 2,
_embedded: {
bar: []
}
},
{
id: 3,
_embedded: {
bar: [
{
id: 4,
_embedded: {
bar: []
}
}
]
}
}
]
}
};
/**
* Flatten the given object and remove the desired keys if needed
* #param obj
*/
function flattenObject(obj, flattenObj) {
var key;
// Check to see if we have flatten obj or not
flattenObj = flattenObj || {};
// Loop over all the object keys and process them
for (key in obj) {
// Check that we are running on the object key
if (obj.hasOwnProperty(key)) {
// Check to see if the current key is in the "black" list or not
if (KEYS_TO_REMOVE.indexOf(key) === -1) {
// Process the inner object without this key
flattenObj[key] = flattenObject(obj[key], flattenObj[key]);
} else {
flattenObject(obj[key], flattenObj);
}
}
}
return flattenObj;
}
console.log(flattenObject(data));
So, basically you already have almost all of the code you need. All we have to do is wrap it in a function so we can use recursion. You'll see we only add a check to see if it is an object, if it is, we already have a function that knows how to flatten that object, so we'll just call it again with the key that we need to flatten.
function flatten(temp1) { // Wrap in a function so we can use recursion
return _.reduce(temp1, function(accumulator, value, key) {
if (key === '_embedded') {
return _.merge(accumulator, value);
} else if (value !== null && typeof value === 'object') // Check if it's another object
return _.merge(accumulator, flatten(value)) // Call our function again
return accumulator[key] = value;
}, {})
}
I'll be able to test it in a bit, but this should be what you need.
Got it!
function unEmbed(data) {
return _.reduce(data, function(accumulator, value, key) {
const returnableValue = _.isObject(value) ? unEmbed(value) : value;
if (key === 'embedded') {
return _.merge(accumulator, returnableValue);
}
accumulator[key] = returnableValue;
return accumulator;
}, {});
}
Problem before I was returning return accumulator[key] = returnableValue, which worked out to be return returnableValue.

Categories

Resources