Converting an array of objects in to a single object - javascript

Have an array which contains a no of json .
[{linkValue:"value1"},{linkValue:"value2"},{linkValue:"value3"},{linkValue:"value4"},{linkValue:"value5"}]
Note that each Json have same key . I want to convert this array into a single json like
{linkValue1:"value1",linkValue2:"value2",linkValue3:"value3",linkValue4:"value4",linkValue5:"value5"}
one thing i also need to know . my array is also inside a json how i get this arry from that json ?
My initial json is
{name:"value",age:"value",linkValue:[[{linkValue:"value1"},{linkValue:"value2"},{linkValue:"value3"},{linkValue:"value4"},{linkValue:"value5"}]
]}
I'm expcting my final json look like :
{name:"value",age:"value",linkValue1:"value1",linkValue2:"value2",linkValue3:"value3",linkValue4:"value4",linkValue5:"value5"}
can anyone please help me

Use Array.forEach and add properties to an empty object:
let source = {name:"value",age:"value",linkValue:[[{linkValue:"value1"},{linkValue:"value2"},{linkValue:"value3"},{linkValue:"value4"},{linkValue:"value5"}]]};
// Copy the array in a variable
let yourArray = source.linkValue[0];
// Delete the original array in the source object
delete source.linkValue;
yourArray.forEach((item, index) => {
source["linkValue" + (index + 1)] = item.linkValue
});
console.log(source); // Should have what you want

Using reduce API,
let targetObj = srcArr.reduce((accumulator, value, index)=>{
accumulator['linkValue'+(index+1)] = value.linkValue;
return accumulator;
}, {});

[{linkValue:"value1"},{linkValue:"value2"},{linkValue:"value3"},{linkValue:"value4"},{linkValue:"value5"}]
This is javascript Array contains multiple javascript object.
{linkValue1:"value1",linkValue2:"value2",linkValue3:"value3",linkValue4:"value4",linkValue5:"value5"}
If you need structure like this,Then define a single javascript object and add linkvalue1,linkvalue2 etc. as a member of that object and then add it to javascript Array.

Give this a try.
myObj.linkValue = myObj.linkValue.map((obj, index) => ({ [`linkValue${index + 1}`]: obj.linkValue }))

Solution with reduce
// HELPER FUNCTIONS
// pick properties from object with default value
function pick (props, sourceObj, defaultValue) {
return props.reduce(
(obj, prop) =>
Object.assign(obj, { [prop]: sourceObj[prop] || defaultValue }),
{}
)
}
// get property value or default
function propOr (propName, obj, defaultValue) {
return obj[propName] || defaultValue
}
// flatten nested array
function flattern (nestedArray) {
return nestedArray.reduce((flat, innerArray) => flat.concat(innerArray), [])
}
// LINKS BUILDER based on REDUCE
function buildLinks (linksArray) {
return linksArray.reduce((accumulator, item, index) => {
accumulator['linkValue' + (index + 1)] = item.linkValue
return accumulator
}, {})
}
// TRANSFORMATION FUNCTION - takes json and produce required output
function transform(json) {
return Object.assign(
{},
pick(['name', 'age'], json, null),
buildLinks(flattern(propOr('linkValue', json, [])))
)
}
// EXECUTION
const result = transform(source)
PS> You can use libraries like Lodash or Ramda and replace helper functions defined by me with ones from library

Related

JS using dot, string notation to map nested and assign errors

Ok, this is an odd one that I just can't seem to get right.
I have a large, complex object to send to a backend. After various attempts, I tried using Joi to validate the schema, and I like it, but passing the errors back to the inputs is a nightmare
The body is subdivided into 5 sections, with each subsection containing between 10-30 fields, some of which are string[], interface[], number[], or general nested interfaces.
I tried writing my own custom validation and the complexity grew outta control.
(I know some of you are thinking "your schema is too complex" and you're right, but its not something I can change right now. Clients blah blah.)
The problem: Joi.validate(myBody) gives me a bunch of errors in the following format:
[ // <- error.details
{
context: {},
message: "X is not allowed to be empty",
path:["path","to","property"], // <- this is the key
type: ""
}
]
How can I map error.details to create a new validation object that I can then use for the form items themselves.
For example:
path = ["path","to","property"] // -> map over to create
let newObj = {
path:{
to: {
property: ""
}
}
}
I hope this make sense.
I want to take an array of vallidation errors, and turn them into a validation object that matches the initial object
The simplest approach IMO would be to use reduce to reverse create the object from the array
["path","to","property"].reduceRight((prev, current) => {
const obj = {};
obj[current] = prev
return obj;
}, "");
This will create the object as described in the original question. You need to use reduceRight rather than reduce so that you create the leaf node first otherwise you have having to try and traverse the graph each time you add a new node and you have to handle setting the leaf node to be a string rather than an object.
Extrapolating out what you are trying to achieve I'm assuming a couple of things:
You want to return a single object rather than an array of objects.
The leaf is likely to be the message.
There will only be a single error message for each path (because it makes life easier).
We can expand upon the above with the deep merge solution from here to create an object to return. The code for that would look like:
const errors = [ // <- error.details
{
context: {},
message: "X is not allowed to be empty",
path:["path","to","property"], // <- this is the key
type: ""
},
{
context: {},
message: "X has to be greater than 0",
path:["path","to","another", "property"], // <- this is the key
type: ""
}
]
function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
function mergeDeep(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
mergeDeep(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return mergeDeep(target, ...sources);
}
errors.map((e) => e.path.reduceRight((prev, current) => {
const obj = {};
obj[current] = prev
return obj;
}, e.message)).reduce((previous, current) => mergeDeep(previous, current), {})
The output from running errors through it would be:
{
path: {
to: {
property: 'X is not allowed to be empty',
another: { property: 'X has to be greater than 0' }
}
}
}
Problem statement being used
I want to take an array of vallidation errors, and turn them into a validation object that matches the initial object
Given: an array of strings (each of which is a prop)
Expected result: an object structured based on the array
Code Snippet
// given an array of props
// starting at root-level & ending at the leaf
const pList = ['root', 'lev1', 'lev2', 'propName'];
// method to transform array into an object
const transformToObj = arr => (
arr
.reverse()
.reduce(
(fin, itm) => ({ [itm]: fin ? {...fin} : '' }),
false
)
);
// test with given array
console.log(transformToObj(pList));
// test with user-input array (comma-separated)
console.log(transformToObj([...(
(prompt('Enter comma-separated path like a,b,c,d'))
.split(',')
.map(x => x.trim())
)]));
Explanation
first reverse the array (so the first item is the inner-most prop)
use .reduce to iterate
at each level, add the item as the outer-prop and the value as the existing object
if this is the inner-most prop, simply add an empty string as value

Assign object values conditionally JavaScript

I am receiving a data object from an api where some of its properties may have a value of null. I want to create a function that returns the object but for the properties that have a null value there is a "-" instead. I have tried:
const hyphenOrContent = movie => {
return Object.values(movie).map(val => {
return val === null ? "-" : val
});
}
but this only returns an array of the values. I've read that it's better not to use a for in loop so I would like to avoid using one if possible. Any help appreciated!
Map the object's entries, turning nulls into -s, then use Object.fromEntries to turn the array of entries into an object:
const hyphenOrContent = movie => Object.fromEntries(
Object.entries(movie).map(
([key, val]) => ([key, val === null ? "-" : val])
)
);
Just loop over the Object and alter the properties.
const hyphenOrContent = movie => {
Object.entries(movie).forEach(([key, val]) => {
if (val === null) movie[key] = '-';
});
};
var a = {
foo: null,
bar: '123',
baz: null
}
hyphenOrContent(a);
console.log(a);
If you do not want to alter the original, you can clone it.

Role hierarchy mapper / generator (recursive)

I would like to create an object of arrays converting the single level key - (string) value relation to key - (array) keys collection.
Basically, the code must collect other keys and their values recursively starting from collecting self. At the end the object must be like this;
{
ROLE_SUPER_ADMIN: [
'ROLE_SUPER_ADMIN',
'ROLE_ADMIN',
'ROLE_MODERATOR',
'ROLE_AUTHOR'
]
}
What i have achieved yet is;
export const roles = {
ROLE_SUPER_ADMIN: 'ROLE_ADMIN',
ROLE_ADMIN: 'ROLE_MODERATOR',
ROLE_MODERATOR: 'ROLE_AUTHOR',
ROLE_AUTHOR: null,
ROLE_CLIENT: null
}
export function roleMapper() {
const roleArray = {}
const mapper = (key) => {
roleArray[key] = [key];
if (!roles[key] || Array.isArray(roles[key])) {
return;
} else if (!roles[roles[key]]) {
roleArray[key].push(roles[key])
} else {
if (roleArray.hasOwnProperty(key)) {
Object.keys(roles).filter(r => r !== key).forEach((role) => {
roleArray[key].push(mapper(role))
})
}
}
}
Object.keys(roles).forEach((key) => {
mapper(key)
});
console.log(roleArray);
}
I have completely lost solving this. Please help, thanks.
I would use a function generator for this, taking advantage of the easy recursion approach and taking advantage of Object.entries combined with Array.map.
The below method acquires all the siblings of a defined key from an object, assuming that each key value may be the child of the said key.
As a side note, you could technically do that in many other ways (without relying on function generators), I just think that the generator approach is clever and easier to maintain. Moreover, it allows you to re-use the method later and allows you to eventually iterate the values.
Code explanation is directly in the code below.
const roles = {
ROLE_SUPER_ADMIN: 'ROLE_ADMIN',
ROLE_ADMIN: 'ROLE_MODERATOR',
ROLE_MODERATOR: 'ROLE_AUTHOR',
ROLE_AUTHOR: null,
ROLE_CLIENT: null
}
// Acquire all the siblings, where a sibling is a key whose value is the value of another key.
function* getSiblings(v, source) {
// if the desired key exists in source..
if (source[v]) {
// yield the value, which is a role in that case.
yield source[v];
// next, yield all the siblings of that value (role).
yield* [...getSiblings(source[v], source)];
}
}
// Map all roles by its siblings.
const res = Object.entries(roles).map(([key, role]) => {
// key is the main role, whereas role is the "child" role.
// Technically, [key] is not exactly a child role of [key], so we're injecting it manually below to avoid polluting the getSiblings method.
return {
[key]: [key, ...getSiblings(key, roles)] // <-- as mentioned above, the array is build by starting from the main role (key) and appending the child roles (siblings). [key] is a shorthand to set the key.
}
});
console.log(res);
I would separate out the recursive call necessary to fetch the list from the code that builds the output. That allows you to make both of them quite simple:
const listRoles = (rolls, name) => name in roles
? [name, ... listRoles (rolls, roles [name] )]
: []
const roleMapper = (roles) => Object .assign (
... Object.keys (roles) .map (name => ({ [name]: listRoles (roles, name) }))
)
const roles = {ROLE_SUPER_ADMIN: 'ROLE_ADMIN', ROLE_ADMIN: 'ROLE_MODERATOR', ROLE_MODERATOR: 'ROLE_AUTHOR', ROLE_AUTHOR: null, ROLE_CLIENT: null}
console .log (
roleMapper (roles)
)
Here listRoles is the recursive bit, and it simply takes a roles object and a name and returns all the descendant names, so
listRoles(roles, 'ROLE_MODERATOR') //=> ["ROLE_MODERATOR", "ROLE_AUTHOR"]
roleMapper uses that function. It takes the roles object and calls listRoles on each of its keys, combining them into a new object.
Together, these yield the following output:
{
ROLE_SUPER_ADMIN: ["ROLE_SUPER_ADMIN", "ROLE_ADMIN", "ROLE_MODERATOR", "ROLE_AUTHOR"],
ROLE_ADMIN: ["ROLE_ADMIN", "ROLE_MODERATOR", "ROLE_AUTHOR"],
ROLE_MODERATOR: ["ROLE_MODERATOR", "ROLE_AUTHOR"],
ROLE_AUTHOR: ["ROLE_AUTHOR"],
ROLE_CLIENT: ["ROLE_CLIENT"]
}
I see the accepted answer generates a structure more like this:
[
{ROLE_SUPER_ADMIN: ["ROLE_SUPER_ADMIN", "ROLE_ADMIN", "ROLE_MODERATOR", "ROLE_AUTHOR"]},
{ROLE_ADMIN: ["ROLE_ADMIN", "ROLE_MODERATOR", "ROLE_AUTHOR"]},
{ROLE_MODERATOR: ["ROLE_MODERATOR", "ROLE_AUTHOR"]},
{ROLE_AUTHOR: ["ROLE_AUTHOR"]},
{ROLE_CLIENT: ["ROLE_CLIENT"]}
]
(The difference is that mine was a single object, versus this one which was an array of single-property objects.)
While that feels less logical to me, it would be even easier to write:
const roleMapper = (roles) => Object.keys (roles) .map (n => ({ [n]: listRoles (roles, n) }))

Check a key on a JSON object

We are getting JSON string two ways:
"{"phoneNumber":[{"remove":["0099887769"]},{"add":["0099887765"]}]}"
Or
"{"phoneNumber":["0099887765"]}"
We have to convert "{"phoneNumber":[{"remove":["0099887769"]},{"add":["0099887765"]}]}" in "{"phoneNumber":["0099887765"]}" way
Is there any way to write a JavaScript to determine which JSON has "add" key and which one don't have.
When you parse the JSON, you'll have an array with two entries (each objects) if it's the first style, or an array with one entry that's a string. So:
function handle(theJSON) {
let parsed = JSON.parse(theJSON);
if (typeof parsed.phoneNumber[0] === "object") {
parsed.phoneNumber = [parsed.phoneNumber.find(o => o.add).add[0]];
}
console.log(parsed);
}
Live Example:
function handle(theJSON) {
let parsed = JSON.parse(theJSON);
if (typeof parsed.phoneNumber[0] === "object") {
parsed.phoneNumber = [parsed.phoneNumber.find(o => o.add).add[0]];
}
console.log(parsed);
}
handle('{"phoneNumber":[{"remove":["0099887769"]},{"add":["0099887765"]}]}');
handle('{"phoneNumber":["0099887765"]}');
Or if you need an ES5 version:
function handle(theJSON) {
var parsed = JSON.parse(theJSON);
if (typeof parsed.phoneNumber[0] === "object") {
parsed.phoneNumber = [parsed.phoneNumber.find(function(o) { return o.add; }).add[0]];
}
console.log(parsed);
}
You can use some to return true/false if "add" is a property on any object in the array. Here's a general solution:
const json = '{"phoneNumber":[{"remove":["0099887769"]},{"add":["0099887765"]}]}';
const data = JSON.parse(json);
function checkForKey(data, { arrayKey, searchKey }) {
return data[arrayKey].some(obj => obj[searchKey]);
}
const hasAdd = checkForKey(data, {arrayKey: 'phoneNumber', searchKey: 'add' });
console.log(hasAdd);
You can do it by using for..in loop and hasOwnProperty check, finally push only the phoneNumbers with add index. Hope this helps :)
const expected = {'phoneNumber':[]};
let str = '{"phoneNumber":[{"add":["0099844465"],"remove":["0099887769"]},{"add":["0099887765"]}]}';
const st_obj = JSON.parse(str);
for (var k in st_obj['phoneNumber']) {
if (st_obj['phoneNumber'][k].hasOwnProperty('add')) {
expected['phoneNumber'].push(st_obj['phoneNumber'][k]['add']);
}
}
console.log(expected);
use let obj = JSON.parse('{"phoneNumber":[{"remove":["0099887769"]},{"add":["0099887765"]}]}') to convert it to object.
then iterate it and fetch value and push it into new object
just check if add is top level propery
if(obj['add']) {....}
if it is exists then if will be true, if not it will return undefined and if will be false,
if you should check it in array of objects, you can use the same logic with find method from array prototype
phoneNumber.find(obj => obj['add']);
You can check whether the add key exists by converting the JSON into array:
How to convert JSON object to JavaScript array
With that you are able to check if the key exists by checking whether the key is undefined:
obj["phonenumber"]["add"] != undefined

JS rename an object key, while preserving its position in the object

My javascript object looks like this:
const someObj = {
arr1: ["str1", "str2"],
arr2: ["str3", "str4"]
}
In attempting to rename a key (e.g. arr1), I end up deleting the existing key and writing a new key with the original value. The order of obj changes.
someObj = {
arr2: ["str3", "str4"],
renamedarr1: ["str1", "str2"]
}
How do I rename a key while preserving the key order?
In the end it was solved in a js-vanila way rather than a react way.
In case somebody would look for a similar solution, I am posting the code I ended up using. Inspired by Luke's idea:
const renameObjKey = ({oldObj, oldKey, newKey}) => {
const keys = Object.keys(oldObj);
const newObj = keys.reduce((acc, val)=>{
if(val === oldKey){
acc[newKey] = oldObj[oldKey];
}
else {
acc[val] = oldObj[val];
}
return acc;
}, {});
return newObj;
};
You might want to consider reducing the array of keys into a new object.
To do this, you need also to know which key changed to what.
Reduce the array of keys
use a reducer which checks for a key change, and change it if necessary.
add the key to the object with the value
After that you have a Object with the order you had before, and possibly a changed key is still at the same position
Something like this might work (not tested)
const changedKeyMap = {"previousKey": "newKey"};
const keys = Object.keys(this.state.obj);
const content = e.target.value;
const result = keys.reduce((acc, val) => {
// modify key, if necessary
if (!!changedKeyMap[val]) {
val = changedKeyMap[val];
}
acc[val] = content;
// or acc[val] = this.state.obj[val] ?
return acc;
}, {});
As you can see, you need to keep track of how you changed a key (changedKeyMap).
The reduce function just iterates over all keys in correct order and adds them to a newly created object. if a key is changed, you can check it in the changedKeyMap and replace it. It will still be at the correct position

Categories

Resources