Combine several objects with weird structure into one - javascript

I have an array with a few objects which I'm trying to combine into one single object
[
{ user: { '563D': { pId: '12', seasion: '120', value: true } } },
{ user: { '563D': { pId: '11', seasion: '120', value: false } } },
...
]
pId is unique
seasion is the same for each object (almost never changing)
value can be anything
I want to have something like this:
{
id: '563D',
seasion: '120',
types: {
12: // pId
{
value: true
},
11:
{
value: false
}
}
}
I tried to use reduce and forEach but I wasnt been able to achieve my goals
due to poor understanding of those 2 methods.
EDIT:
forgot about several users input, sorry
[
{
users: {
'563D': [Object],
'4b07': [Object]
}
},
{
users: {
'563D': [Object],
'4b07': [Object]
}
},
{
users: {
'563D': [Object],
'4b07': [Object]
}
}
]

You could use reduce and destructuring to group the objects based on the first key inside user. Then use Object.values() to get the array of each group values:
Get user from each object by destructuring the parameter
Destructure the first entry of the user to get the key (like '563D') and the nested properties separately)
Use the || operator to check if the accumulator already has the id as it's property. If yes, use it. Else assign a new value with { id, seasion, types: {} }. This is using the Shorthand property names.
Update the types with pId as key and { value } as it's value
const input = [{user:{'563D':{pId:'12',seasion:120,value:true}, '4b07':{pId:'12',seasion:120,value:true}}},{user:{'563D':{pId:'11',seasion:120,value:false},'4b07':{pId:'11',seasion:120,value:false}}}]
const output = input.reduce((r, { user }) => {
for(const [id, { pId, seasion, value }] of Object.entries(user)) {
r[id] = r[id] || { id, seasion, types: {} };
r[id].types[pId] = { value };
}
return r;
}, {})
console.log(Object.values(output))
If you have only one unique id in the array, you can simplify the reduce to:
const input = [{user:{'563D':{pId:'12',seasion:120,value:true}}},{user:{'563D':{pId:'11',seasion:120,value:false}}}]
const output = input.reduce((r, { user }) => {
const [id, { pId, seasion, value }] = Object.entries(user)[0];
return { id, seasion, types: { ...r.types, [pId]: { value } } }
}, {})
console.log(output)

Related

Return all values of nested arrays using string identifier

Given an object searchable, is there a simple way of returning all the id values using lodash or underscore.js (or equivalent) where I can define the path to id?
const searchable = {
things: [
{
id: 'thing-id-one',
properties: [
{ id: 'd1-i1' },
{ id: 'd1-i2' },
]
},
{
id: 'thing-id-two',
properties: [
{ id: 'd2-i1' },
{ id: 'd2-i2' },
]
}
]
}
I am looking to see if this is possible in a manner similar to how we can use lodash.get e.g. if we wanted to return the things array from searchable we could do
const things = _.get(searchable, 'things');
I can't seem to find anything similar in the documentation. I am looking for something
that could contain an implementation similar to:
_.<some_function>(searchable, 'things[].properties[].id')
Note: I am well aware of functions like Array.map etc and there are numerous ways of extracting the id property - it is this specific use case that I am trying to figure out, what library could support passing a path as a string like above or does lodash/underscore support such a method.
Found a solution using the package jsonpath
const jp = require('jsonpath');
const result = jp.query(searchable, '$.things[*].properties[*].id')
console.log(result);
// outputs: [ 'd1-i1', 'd1-i2', 'd2-i1', 'd2-i2' ]
you can do it easily in plain js
like this
const searchable = {
things: [
{
id: 'thing-id-one',
properties: [
{ id: 'd1-i1' },
{ id: 'd1-i2' },
]
},
{
id: 'thing-id-two',
properties: [
{ id: 'd2-i1' },
{ id: 'd2-i2' },
]
}
]
}
const search = (data, k) => {
if(typeof data !== 'object'){
return []
}
return Object.entries(data).flatMap(([key, value]) => key === k ? [value]: search(value, k))
}
console.log(search(searchable, 'id'))
_.map and _.flatten together with iteratee shorthands let you expand nested properties. Every time you need to expand into an array, just chain another map and flatten:
const searchable = {
things: [
{
id: 'thing-id-one',
properties: [
{ id: 'd1-i1' },
{ id: 'd1-i2' },
]
},
{
id: 'thing-id-two',
properties: [
{ id: 'd2-i1' },
{ id: 'd2-i2' },
]
}
]
}
// Let's say the path is "things[].properties[].id"
const result = _.chain(searchable)
.get('things').map('properties').flatten()
.map('id').value();
console.log(result);
<script src="https://cdn.jsdelivr.net/npm/underscore#1.13.4/underscore-umd-min.js"></script>

Filter array of objects by value

I want to filter an array of objects, by a specific value within the objects.
In the example i've provided I want to filter the array 'pets' by a value in the array 'characteristics'. For example, where I have called the function with the param 'loyal', i'd only expect the object for the dog value to be returned, as only the dog has that characteristic.
At the moment when I call the function both objects are returned even though only the object for dog has that value in its characteristics array.
const pets = [
{
name: 'dog',
characteristics: [
{
value: 'loyal'
},
{
value: 'big'
}
]
},
{
name: 'cat',
characteristics: [
{
value: 'fluffy'
},
{
value: 'small'
}
]
},
]
function filterPets(pets, characteristic) {
return pets.filter(function(pet) {
return pet.characteristics.filter(o => o.value.includes(characteristic));
})
}
console.log(filterPets(pets, 'loyal'));
That's because for the characteristics check you're using filter, which always returns an array (even if a blank one), and even a blank array is a truthy value, so the outer filter keeps every pet you check. For that inner check, you want some, not filter, so you get a flag for whether any entries matched:
function filterPets(pets, characteristic) {
return pets.filter(function(pet) {
return pet.characteristics.some(o => o.value.includes(characteristic));
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^
});
}
const pets = [
{
name: 'dog',
characteristics: [
{
value: 'loyal'
},
{
value: 'big'
}
]
},
{
name: 'cat',
characteristics: [
{
value: 'fluffy'
},
{
value: 'small'
}
]
},
];
function filterPets(pets, characteristic) {
return pets.filter(function(pet) {
return pet.characteristics.some(o => o.value.includes(characteristic));
});
}
console.log(filterPets(pets, 'loyal'));
Just for what it's worth, I assume characteristics are unique (you can't have "loyal" twice), so you might prefer to keep those in a Set so you can check for them more easily than .some(o => o.includes(characteristic)). For instance:
const pets = [
{
name: "dog",
characteristics: new Set(["loyal", "big"]),
},
{
name: "cat",
characteristics: new Set(["fluffy", "small"]),
},
];
function filterPets(pets, characteristic) {
return pets.filter(function(pet) {
return pet.characteristics.has(characteristic);
});
}
Live Example:
const pets = [
{
name: "dog",
characteristics: new Set(["loyal", "big"]),
},
{
name: "cat",
characteristics: new Set(["fluffy", "small"]),
},
];
function filterPets(pets, characteristic) {
return pets.filter(function(pet) {
return pet.characteristics.has(characteristic);
});
}
console.log(filterPets(pets, "loyal"));
console.log("Don't worry about the {} for characteristics, the Stack Snippets console doesn't know how to display Set objects. Look in the real console if you want to double-check the set.");
function filterPets(list, charValue) {
const filteredPets = []
list.map(function(pet,petIndex,array) {
pet.characteristics.map(function(charac){
if(charac.value === charValue){
return filteredPets.push(array[petIndex])
}
})
})
return filteredPets
}
filterPets(pets,'loyal');

How to return a new array of object from object nested props with reduce in JavaScript

I'm trying to return a new array of objects from the nested properties of another object. This is my current object:
const teams = {
GRYFFINDOR: {
display: 'Gryffindor',
channel: ['#team-gryffindor'],
ui: 'lion',
},
HUFFLEPLUFF: {
display: 'Hufflepuff',
channel: ['#team-hufflepuff'],
ui: '', // empty string on purpose
},
SLYTHERIN: {
display: 'Slytherin',
channel: ['#team-slytherin'],
ui: 'snake',
},
}
Now, I'm trying to return an array of objects like so:
[
{ value: 'lion' },
{ value: '' },
{ value: 'snake' },
]
This is what I have tried:
const teamsUI = [Object.keys(teams).reduce((carry, item) => {
return {...carry, ...{ ['value']: teams[item].ui }}
}, {})];;
However, I get this:
console.log(teamsUI); // returns [ { value: 'snake' }]
What am I doing wrong?
You could do like this:
const teams = {
GRYFFINDOR: {
display: 'Gryffindor',
channel: ['#team-gryffindor'],
ui: 'lion',
},
HUFFLEPLUFF: {
display: 'Hufflepuff',
channel: ['#team-hufflepuff'],
ui: '', // empty string on purpose
},
SLYTHERIN: {
display: 'Slytherin',
channel: ['#team-slytherin'],
ui: 'snake',
},
}
const result = Object.values(teams).map(({ ui }) => ({ value: ui }));
console.log(result);
Beside nested result structure, you could take the values and destructure the wanted property formapping.
const
teams = { GRYFFINDOR: { display: 'Gryffindor', channel: ['#team-gryffindor'], ui: 'lion' }, HUFFLEPLUFF: { display: 'Hufflepuff', channel: ['#team-hufflepuff'], ui: '' }, SLYTHERIN: { display: 'Slytherin', channel: ['#team-slytherin'], ui: 'snake' } },
result = Object.values(teams).map(({ ui: value }) => ({ value }));
console.log(result);
Here is the shortest way to achieve what you want
Object.keys(teams).map(t=>({value: teams[t].ui}))
Now what are you doing wrong?
Take a look at this part
Object.keys(teams).reduce((carry, item) => {
return {...carry, ...{ ['value']: teams[item].ui }}
}, {});
You're reducing your object to a single value with the same key called value. This means that the value of teams[item].ui of the current object in the reduce function will override the previous and hence you'll end up with the last object of that reduce function. You're finally wrapping this last object in an array which gives you what you have.
A better way to achieve that will be to do something like this
Object.keys(teams).reduce((carry, item, index)=>{
carry[index] = { value: teams[item].ui };
return carry;
}, []);

How to return an array of objects in GraphQL, possibly using the same endpoint as the one that returns a single object?

I am making a GraphQL API where I would be able to retrieve a car object by its id or retrieve all the cars when no parameter is provided.
Using the code below, I am successfully able to retrieve a single car object by supplying id as a parameter.
However, in the case where I would expect an array of objects i.e. when I supply no parameter at all, I get no result on GraphiQL.
schema.js
let cars = [
{ name: "Honda", id: "1" },
{ name: "Toyota", id: "2" },
{ name: "BMW", id: "3" }
];
const CarType = new GraphQLObjectType({
name: "Car",
fields: () => ({
id: { type: GraphQLString },
name: { type: GraphQLString }
})
});
const RootQuery = new GraphQLObjectType({
name: "RootQueryType",
fields: {
cars: {
type: CarType,
args: {
id: { type: GraphQLString }
},
resolve(parent, args) {
if (args.id) {
console.log(cars.find(car => car.id == args.id));
return cars.find(car => car.id == args.id);
}
console.log(cars);
//***Problem Here***
return cars;
}
}
}
});
Test queries and their respective results:
Query 1
{
cars(id:"1"){
name
}
}
Query 1 Response (Success)
{
"data": {
"cars": {
"name": "Honda"
}
}
}
Query 2
{
cars{
name
}
}
Query 2 Response (Fail)
{
"data": {
"cars": {
"name": null
}
}
}
Any help would be much appreciated.
A Car and a List of Cars are effectively two separate types. A field cannot resolve to a single Car object one time, and an array of Car object another.
Your query is returning null for the name because you told it the cars field would resolve to a single object, but it resolved to an array instead. As a result, it's looking for a property called name on the array object and since one doesn't exist, it's returning null.
You can handle this in a couple of different ways. To keep things to one query, you can use filter instead of find and change the type of your query to a List.
cars: {
type: new GraphQLList(CarType), // note the change here
args: {
id: {
type: GraphQLString
},
},
resolve: (parent, args) => {
if (args.id) {
return cars.filter(car => car.id === args.id);
}
return cars;
}
}
Alternatively, you could split this into two separate queries:
cars: {
type: new GraphQLList(CarType),
resolve: (parent, args) => cars,
},
car: {
type: CarType,
args: {
id: {
// example of using GraphQLNonNull to make the id required
type: new GraphQLNonNull(GraphQLString)
},
},
resolve: (parent, args) => cars.find(car => car.id === args.id),
}
Check the docs for more examples and options.

Remove parent object of a JSON if it has certain property

I'm getting a JSON structure from an API, that I want to change in my front end. The frontend adds a property to the JSON structure, "isHidden". When I send the modified JSON back, I don't want the object that has "isHidden" to be sent back to the API, but I will still save that internally in my own mongodb.
However, doing this was aperently a bit harder then I thought. I made this function wich works but I think is very ugly:
function removeHiddenObject(data,parent){
for(var property in data){
if(data.hasOwnProperty(property)){
if(property == "isHidden" && data[property] === true){
parent.splice(parent.indexOf(data), 1);
}
else {
if(typeof data[property] === "object") {
removeHiddenObject(data[property], data);
}
}
}
}
return data;
}
It's a recursive method, but I find it way to complex and weird. Is there a way of simplifying my task?
Here is a jsfiddle for you if you'd like to help out: https://jsfiddle.net/vn4vbne8/
use this code to remove it from json string :
myJson=s.replace(/,*\s*"[^"]"\s*\:\s*{(.*?)"isHidden"\:([^}]*)}/gm,"");
Be careful in Regex every character is important so use exactly above code.
It removes every property that have an object that one of its properties is isHidden.
Javascript actually supports non-enumerable public properties. I'm assuming that when you send data back to the server you first stringify it with JSON.stringify which will only stringify public enumerable properties of the object.
You can define a non-enumerable property like this (more on this here):
Object.defineProperty(obj, 'isHidden', {
enumerable: false,
writable: true
});
Where obj is the javascript object you want to add the property to and isHidden is the name of the property you are adding. When done this way, the new property is accessible as obj.isHidden but nevertheless will not show up in JSON.stringify output nor in for loops.
Here is a solution using object-scan. Takes a bit of time to wrap your head around it, but it helps with some heavy lifting around general data processing.
// const objectScan = require('object-scan');
const data = {"user":{"name":"Bob"},"buildingBlockTree":[{"uid":"a875ed6cf4052448f9e0cc9ff092a76d4","_id":"56a7353f682e1cc615f4a6ae","columns":[{"uid":"a0061fdd2259146bb84e5362eeb775186","_id":"56a7353f682e1cc615f4a6b1","buildingBlocks":[{"user":{"name":"lajdi"},"buildingBlockTree":[{"uid":"a875ed6cf4052448f9e0cc9ff092a76d4","_id":"56a7353f682e1cc615f4a6ae","columns":[{"uid":"a0061fdd2259146bb84e5362eeb775186","_id":"56a7353f682e1cc615f4a6b1","buildingBlocks":[]},{"uid":"abbcf3328854840deb96bb6b101df2b39","_id":"56a7353f682e1cc615f4a6af","buildingBlocks":[{"uid":"a931cf745d4b847ba9cdc7f4b830413be","_id":"56a7353f682e1cc615f4a6b0","source":{"limit":1,"itemsListId":1,"offset":0}}]}],"template":{"name":"Xs12Sm12Md6Lg12DotXs12Sm12Md6Lg12"}}],"_id":"56a7353f682e1cc615f4a6ad","source":{"limit":1,"itemsListId":1,"offset":0},"template":{"numberOfColumns":1},"uid":"a5f6a2fd1c80f49c0b97e0c7994400588"}]},{"uid":"abbcf3328854840deb96bb6b101df2b39","_id":"56a7353f682e1cc615f4a6af","buildingBlocks":[{"_id":"56a7353f682e1cc615f4a6b0","source":{"limit":1,"itemsListId":1,"offset":0},"isHidden":true}]}]}],"_id":"56a7353f682e1cc615f4a6ad","source":{"limit":1,"itemsListId":1,"offset":0},"uid":"a5f6a2fd1c80f49c0b97e0c7994400588","metadata":{"title":"sss"},"title":"sss","url":"sss"};
const removeHiddenObjects = (input) => objectScan(['**[*].isHidden'], {
rtn: 'count',
filterFn: ({ value, gparent, gproperty }) => {
if (value === true) {
gparent.splice(gproperty, 1);
return true;
}
return false;
}
})(input);
console.log(removeHiddenObjects(data)); // counts removals
// => 1
console.log(data);
/* => {
user: { name: 'Bob' },
buildingBlockTree: [ {
uid: 'a875ed6cf4052448f9e0cc9ff092a76d4',
_id: '56a7353f682e1cc615f4a6ae',
columns: [ {
uid: 'a0061fdd2259146bb84e5362eeb775186',
_id: '56a7353f682e1cc615f4a6b1',
buildingBlocks: [ {
user: { name: 'lajdi' },
buildingBlockTree: [ {
uid: 'a875ed6cf4052448f9e0cc9ff092a76d4', _id: '56a7353f682e1cc615f4a6ae', columns: [
{ uid: 'a0061fdd2259146bb84e5362eeb775186', _id: '56a7353f682e1cc615f4a6b1', buildingBlocks: [] },
{ uid: 'abbcf3328854840deb96bb6b101df2b39', _id: '56a7353f682e1cc615f4a6af', buildingBlocks: [
{ uid: 'a931cf745d4b847ba9cdc7f4b830413be', _id: '56a7353f682e1cc615f4a6b0', source: {
limit: 1,
itemsListId: 1,
offset: 0
} }
] }
],
template: { name: 'Xs12Sm12Md6Lg12DotXs12Sm12Md6Lg12' }
} ],
_id: '56a7353f682e1cc615f4a6ad',
source: { limit: 1, itemsListId: 1, offset: 0 },
template: { numberOfColumns: 1 },
uid: 'a5f6a2fd1c80f49c0b97e0c7994400588'
} ]
}, { uid: 'abbcf3328854840deb96bb6b101df2b39', _id: '56a7353f682e1cc615f4a6af', buildingBlocks: [] } ]
} ],
_id: '56a7353f682e1cc615f4a6ad',
source: { limit: 1, itemsListId: 1, offset: 0 },
uid: 'a5f6a2fd1c80f49c0b97e0c7994400588',
metadata: { title: 'sss' },
title: 'sss',
url: 'sss'
}
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#16.0.0"></script>
Disclaimer: I'm the author of object-scan

Categories

Resources