Transform JavaScript object by clearing, adding and removing properties - javascript

Here are the instructions for what I am supposed to do:
Implement a function accepting 2 arguments: state and actions. The
function should change the state basing on the given actions
array.
state is an object. You are supposed to add, change, or delete its properties instead of creating a new object
actions is an array of objects. Each object in this array has the next properties:
type contains a string: either 'addProperties', 'removeProperties' or 'clear';
The second property of each object depends on type and may be one of the following:
if type is addProperties, second property is extraData. It contains an object
with key: value pairs to add to the state;
if type is removeProperties, second property is keysToRemove. It contains an array
with the list of property names (keys) to remove from the state; (Not existing
properties should be ignored)
if type is clear you should remove all the properties from the
state. No second property in this case;
Example of usage:
If state is {foo: 'bar', bar: 'foo'}, then
transformState(state, [
{
type: 'addProperties',
extraData: {
name: 'Jim',
hello: 'world',
}
},
{
type: 'removeProperties',
keysToRemove: ['bar', 'hello'],
},
{
type: 'addProperties',
extraData: { another: 'one' },
}
])
should modify the state, doing the following:
add two properties to the state
then remove keys bar and hello from the state
and finally add another one property to the state
After these operations the object state will have the following look
{
foo: 'bar',
name: 'Jim',
another: 'one',
}
Another example:
const state = { x: 1 };
transformState(state, [
{ type: 'addProperties', extraData: { yet: 'another property' } }
{ type: 'clear' },
{ type: 'addProperties', extraData: { foo: 'bar', name: 'Jim' } }
]);
state === { foo: 'bar', name: 'Jim' }
Here is my code so far:
function transformState(state, actions) {
const transformedState = state;
for (const { type } of actions) {
if (type === 'clear') {
Object.keys(transformedState)
.forEach(key => delete transformedState[key]);
} else if (type === 'addProperties') {
for (const { extraData } of actions) {
for (const data in extraData) {
transformedState[data] = extraData[data];
}
}
} else if (type === 'removeProperties') {
for (const { keysToRemove } of actions) {
for (const item in keysToRemove) {
delete transformedState[keysToRemove[item]];
}
}
}
}
return transformedState;
}
It works with almost all scenarios, but it doesn't remove some properties and I can not figure out why. The 2 test it won't pass:
test('Should apply several types', () => {
const state = {
foo: 'bar', bar: 'foo',
};
transformState(state, [
{
type: 'addProperties',
extraData: {
name: 'Jim', hello: 'world',
},
},
{
type: 'removeProperties', keysToRemove: ['bar', 'hello'],
},
{
type: 'addProperties', extraData: { another: 'one' },
},
]);
expect(state)
.toEqual({
foo: 'bar', name: 'Jim', another: 'one',
});
});
test('Should work with a long list of types', () => {
const state = {
foo: 'bar', name: 'Jim', another: 'one',
};
transformState(state, [
{
type: 'removeProperties', keysToRemove: ['another'],
},
{ type: 'clear' },
{ type: 'clear' },
{ type: 'clear' },
{
type: 'addProperties', extraData: { yet: 'another property' },
},
{ type: 'clear' },
{
type: 'addProperties',
extraData: {
foo: 'bar', name: 'Jim',
},
},
{
type: 'removeProperties', keysToRemove: ['name', 'hello'],
},
]);
expect(state)
.toEqual({ foo: 'bar' });
});
In the first test in the add properties section, if I change the order of "hello" and "name", it still only removes name and I can't figure out why. Here is the test output:
● Should apply several types
expect(received).toEqual(expected) // deep equality
- Expected
+ Received
Object {
"another": "one",
"foo": "bar",
- "name": "Jim",
+ "hello": "world",
}
196 |
197 | expect(state)
> 198 | .toEqual({
| ^
199 | foo: 'bar', name: 'Jim', another: 'one',
200 | });
201 | });
at Object.<anonymous> (src/transformState.test.js:198:6)
● Should work with a long list of types
expect(received).toEqual(expected) // deep equality
- Expected
+ Received
Object {
"foo": "bar",
+ "yet": "another property",
}
229 |
230 | expect(state)
> 231 | .toEqual({ foo: 'bar' });
| ^
232 | });
233 |
at Object.<anonymous> (src/transformState.test.js:231:6)

Related

Merge two objects and enhance result by adding a new key

I have two objects at the moment (one JSON Schema and one as a response from our API) which I want to merge for better mapping and usage.
The Schema looks like this:
// schema
{
key: {
description: "foo",
properties: {
values: {
title: "foo",
type: "Array"
},
type: "string"
},
type: "object"
},
foo: {
title: "title",
description: "bar"
},
bar: {
title: "title",
description: "who"
}
}
And my response object is similar to this:
// response
{
key: {
values: [0, 1]
type: "point"
},
foo: null,
bar: "some string"
}
I would simply like to merge those two objects, but using const mergedObject = {...schema, ...response} would cause overriding the values.
So my desired outcome would contain a new object prop called value or something which contains the values of the response object:
{
key: {
value: {
values: [0, 1],
type: "point",
},
description: "foo",
properties: {
values: {
title: "foo",
type: "Array"
},
type: "string"
},
type: "object"
},
foo: {
value: null,
title: "title",
description: "bar"
},
bar: {
value: "some string",
title: "title",
description: "who"
}
}
Is this doable using the spread operator? I couldn't find a decent solution here since lodashs assign or assignIn don't provide that functionality either.
I tried this function as well:
function merge (...objs) =>
[...objs].reduce(
(acc, obj) =>
Object.keys(obj).reduce((a, k) => {
acc[k] = acc.hasOwnProperty(k) ? [].concat(acc[k]).concat(obj[k]) : obj[k];
return acc;
}, {}),
{}
);
but it gives me
{
bar: [
{
title: "title",
description: "who"
},
"some string",
],
foo: [
{
title: "title",
description: "bar",
},
null
],
key: [
{
description: "foo",
properties: {
values: {
title: "foo",
type: "Array"
},
type: "string"
},
type: "object"
},
{
values: [0, 1]
type: "point"
}
]
}
which is also not what i want.
Any help is appreciated, thanks!
You can combine Object.keys(...) and Spread Operator:
const objA = {
key: {
description: "foo",
properties: {
values: {
title: "foo",
type: "Array"
},
type: "string"
},
type: "object"
},
foo: {
title: "title",
description: "bar"
},
bar: {
title: "title",
description: "who"
}
}
const objB = {
key: {
values: [0, 1],
type: "point"
},
foo: null,
bar: "some string"
}
function mergeObjects (objectA, objectB) {
const mergedObject = {};
Object.keys(objectA).forEach((key) => {
mergedObject[key] = {
...objectA[key],
value: typeof objectB[key] === 'object' && objectB[key] !== null
? { ...objectB[key] }
: objectB[key]
}
})
return mergedObject;
}
console.log(mergeObjects(objA, objB));
You need to look into this
const data = {
key: {
description: 'foo',
properties: {
values: {
title: 'foo',
type: 'Array',
},
type: 'string',
},
type: 'object',
},
foo: {
title: 'title',
description: 'bar',
},
bar: {
title: 'title',
description: 'who',
},
};
const res = {
key: {
values: [0, 1],
type: 'point',
},
foo: null,
bar: 'some string',
};
const output = { ...data };
Object.keys(res).forEach((r) => {
const isPresent = !!(data[r]);
if (isPresent) {
const responseValues = res[r];
output[r] = { responseValues, ...data[r] };
} else {
output[r] = res[r];
}
});
console.log(output);

Mocha Chai: Deep include an array of objects, but only have part of expected object

I'm using the assert syntax of chai for this.
I know that if I want to check an array of objects for a specific object, I can do this:
assert.deepInclude(
[
{ name: 'foo', id: 1 },
{ name: 'bar', id: 2 }
],
{ name: 'foo', id: 1 }
)
Which should pass.
But what if I only have 1 property in the object that I'm checking for...? Like this:
assert.deepInclude(
[
{ name: 'foo', id: 1 },
{ name: 'bar', id: 2 }
],
{ name: 'foo' }
)
I still want this to pass, but it's telling me it's failing because that exact object does not exist.
Using chai-subset this can be done pretty easily:
const chai = require('chai');
const chaiSubset = require('chai-subset');
chai.use(chaiSubset);
it('should contain subset', () => {
const actual = [
{ name: 'foo', id: 1 },
{ name: 'bar', id: 2 },
];
expect(actual).to.containSubset([{ name: 'foo' }]);
});
Afaik there's no way to do this with chai alone, but you could write your own function:
function containsPartialObj(arr, obj) {
return arr.some(entry => {
const keys = Object.keys(obj);
return keys.every(key => obj[key] === entry[key]);
});
}
it('should contain subset', () => {
const actual = [
{ name: 'foo', id: 1 },
{ name: 'bar', id: 2 },
];
expect(containsPartialObj(actual, { name: 'foo' })).to.be.true;
});

With Crossfilter how do I return an array of all the id values for a specific type

Forgive me, I'm not sure I'm approaching this problem correctly.
I have some data (many thousands of elements) with a type and an ID:
const data = [
{ type: 'foo', id: 1 },
{ type: 'foo', id: 3 },
{ type: 'foo', id: 5 },
{ type: 'baz', id: 8 },
{ type: 'baz', id: 10 },
{ type: 'bar', id: 11 },
{ type: 'bar', id: 13 },
{ type: 'bar', id: 17 },
...
];
With crossfilter, I want to filter by a type and return an array of all their ids.
For example: all the type 'bar' should return [10, 11, 13, 17]
My attempt was to group reduce. But I didn't get very far with it:
let ndx = crossfilter(data);
let d = ndx.dimension(d => d.type);
let reduceAdd = (p, v) => p.push(v);
let reduceRemove = (p, v) => p.filter(i => i !== v);
let reduceInitial = () => ([]);
And then something like:
d.group().reduce(reduceAdd, reduceRemove, reduceInitial)
You should use filter method in combination with map and destructing assignment.
const data = [ { type: 'foo', id: 1 }, { type: 'foo', id: 3 }, { type: 'foo', id: 5 }, { type: 'baz', id: 8 }, { type: 'baz', id: 10 }, { type: 'bar', id: 11 }, { type: 'bar', id: 13 }, { type: 'bar', id: 17 }, ], type = 'bar';
console.log(data.filter(elem => elem.type == type).map(({id}) => id));
What you've got looks pretty much correct with me. You just have to query your group by saving it to a variable
var grp = d.group().reduce(reduceAdd, reduceRemove, reduceInitial)
and then query it like
grp.top(Infinity)
This will return an array of objects. The key of one of the objects will be bar and the value of that object will be the array of records where type is bar.
Using a single forEach() is more efficient in this case instead of using filter() and then map() because you have complexity of O(n) where n is the number of objects but using filter() and then map() there will be complexity of O(n+m) where m is the number of filtered records on which you do map():
const data = [
{ type: 'foo', id: 1 },
{ type: 'foo', id: 3 },
{ type: 'foo', id: 5 },
{ type: 'baz', id: 8 },
{ type: 'baz', id: 10 },
{ type: 'bar', id: 11 },
{ type: 'bar', id: 13 },
{ type: 'bar', id: 17 },
];
let type = 'bar';
var res = [];
data.forEach((obj)=> {
if(obj.type===type){
res.push(obj.id);
}
});
console.log(res);
If there are 8 objects then you are iterating 8 times in filter and then lets say you get 4 records in filter you will then iterate 4 times to get the id value in the resultant array. Total of 12 iterations. So, in such cases I prefer to support the usage of forEach() in which there will be only 8 iterations to get the same set of array.
const data = [
{ type: 'foo', id: 1 },
{ type: 'foo', id: 3 },
{ type: 'foo', id: 5 },
{ type: 'baz', id: 8 },
{ type: 'baz', id: 10 },
{ type: 'bar', id: 11 },
{ type: 'bar', id: 13 },
{ type: 'bar', id: 17 },
];
let type = 'bar';
let result = data.reduce((acc, {type, id})=>{
if(!acc[type]) acc[type]=[];
acc[type].push(id);
return acc
},{})
console.log(result[type]);
You can also use Array.reduce here.
const data = [
{ type: 'foo', id: 1 },
{ type: 'foo', id: 3 },
{ type: 'foo', id: 5 },
{ type: 'baz', id: 8 },
{ type: 'baz', id: 10 },
{ type: 'bar', id: 11 },
{ type: 'bar', id: 13 },
{ type: 'bar', id: 17 }
];
const filteredArray = data.reduce((result, obj) => {
if (obj.type === "bar") {
result.push(obj.id)
}
return result;
}, []);
console.log(filteredArray)

How to get a subset of a Javascript object with nested properties?

I'm developing with Angular and I have the following Typescript array of objects:
docs = [
{
id: '1',
type: {
id: 1
desc: 'Category 1',
}
title: 'Foo",
date: '2018-06-21',
attachments: [
{ id: 51, filename: 'foo.pdf', title: 'Foo' },
{ id: 20, filename: 'bar.doc', title: 'Bar' }
]
},
{
id: '2',
type: {
id: 2
desc: 'Category 2',
}
title: 'Bar",
date: '2018-06-21',
attachments: [
{ id: 15, filename: 'foobar.xls', title: 'Foobar' },
{ id: 201, filename: 'example.doc', title: 'Example' }
]
}
]
I need to get only a subset of the properties, something like this:
docs = [
{
id: '1',
type: {
id: 1
desc: 'Category 1',
}
attachments: [
{ id: 51 },
{ id: 20 }
]
},
{
id: '2',
type: {
id: 2
desc: 'Category 2',
}
attachments: [
{ id: 15 },
{ id: 201 }
]
}
]
How can I achieve this?
Have I to create a parser or does exist any smart way (such as Lodash) to extract a lite version of the array?
var docs = [{"id":"1","type":{"id":1,"desc":"Category 1"},"title":"Foo","date":"2018-06-21","attachments":[{"id":51,"filename":"foo.pdf","title":"Foo"},{"id":20,"filename":"bar.doc","title":"Bar"}]},{"id":"2","type":{"id":2,"desc":"Category 2"},"title":"Bar","date":"2018-06-21","attachments":[{"id":15,"filename":"foobar.xls","title":"Foobar"},{"id":201,"filename":"example.doc","title":"Example"}]}];
const result = docs.map(({id,type,attachments})=>{
let doc={id,type};
doc.attachments=attachments.map(({id})=>({id}));
return doc;
});
console.log(result);
have a look at this. this works perfectly!
You can use array.map and object destructuring to extract only the wanted properties.
Also use JSON.parse and JSON.stringify to make a copy and avoid side effetcs.
docs2 = JSON.parse(JSON.stringify(docs)).map(
({id, type, attachements}) =>
({ id,
type,
attachements: attachements.map(({id}) => ({id})
})
)
You can use Array.map with object spreading, something like this:
const mapSubset = ({ id, type, attachments }) => {
return { id, type, attachments: attachments.map( {id} => id ) };
};
const subset = docs.map( mapSubset );
I was looking for a non-specific way to accomplish this or any other similar cases, so far I've thought of the following:
Have an IMapping<T> type, that defines the way to map each property.
Have an IMappingFunction<T> interface, that determines how to map a specific thing:
The following code demonstrates it:
type IMapping<T> = {
[P in keyof T]: IMapping<T[P]> | IMappingFunction<T[P]>;
}
interface IMappingFunction<T>{
(t: T): T | Partial<T>
}
class Person{
name: string;
lastName: string;
}
const obj: IMapping<Person> = {
name: s => s.toUpperCase(),
lastName: s => s
}
function map<T>(obj: T, mapping: IMapping<T>) {
return Object.keys(obj)
.map(prop => {
const propMapping = mapping[prop];
return {
key: prop,
value: typeof propMapping === 'function' ?
propMapping(obj[prop]) :
map(obj, propMapping)
};
})
.reduce((acc, prop) => ({...acc, [prop.key]: prop.value}), { });
}
console.log(map({ name: 'Name', lastName: 'LastName'}, obj));
For a runnable snippet check here
do you need to leave the original array intact? If not you can iterate through the list of objects using a for loop and use the 'delete' operator to delete the properties you no longer want.
For example:
var Employee = {
firstname: "Mohammed",
lastname: "Haddad"
}
delete Employee.firstname;
console.log(Employee);
// expected output: { lastname: "Haddad" }

Concat array from Object from Array

I'm currently trying to retrieve a list of metadata stored as an array, inside an object, inside an array. Here's a better explanatory example:
[
{
name: 'test',
metadata: [
{
name: 'Author',
value: 'foo'
},
{
name: 'Creator',
value: 'foo'
}
]
},
{
name: 'otherTest',
metadata: [
{
name: 'Created',
value: 'foo'
},
{
name: 'Date',
value: 'foo'
}
]
},
{
name: 'finalTest'
}
]
Now, my objective is to retrieve a list of metadata (by their name) without redundancy. I think that .map() is the key to success but I can't find how to do it in a short way, actually my code is composed 2 for and 3 if, and I feel dirty to do that.
The expected input is: ['Author', 'Creator', 'Created', 'Date']
I'm developping in Typescript, if that can help for some function.
You can use reduce() and then map() to return array of names.
var data = [{"name":"test","metadata":[{"name":"Author","value":"foo"},{"name":"Creator","value":"foo"}]},{"name":"otherTest","metadata":[{"name":"Created","value":"foo"},{"name":"Date","value":"foo"}]},{"name":"finalTest"}]
var result = [...new Set(data.reduce(function(r, o) {
if (o.metadata) r = r.concat(o.metadata.map(e => e.name))
return r
}, []))];
console.log(result)
You could use Set for unique names.
var data = [{ name: 'test', metadata: [{ name: 'Author', value: 'foo' }, { name: 'Creator', value: 'foo' }] }, { name: 'otherTest', metadata: [{ name: 'Created', value: 'foo' }, { name: 'Date', value: 'foo' }] }, { name: 'finalTest' }],
names = new Set;
data.forEach(a => (a.metadata || []).forEach(m => names.add(m.name)));
console.log([...names]);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var data = [{"name":"test","metadata":[{"name":"Author","value":"foo"},{"name":"Creator","value":"foo"}]},{"name":"otherTest","metadata":[{"name":"Created","value":"foo"},{"name":"Date","value":"foo"}]},{"name":"finalTest"}]
data
.filter(function(obj){return obj.metadata != undefined})
.map(function(obj){return obj.metadata})
.reduce(function(a,b){return a.concat(b)},[])
.map(function(obj){return obj.name})
A hand to hand Array.prototype.reduce() and Array.prototype.map() should do it as follows;
var arr = [
{
name: 'test',
metadata: [
{
name: 'Author',
value: 'foo'
},
{
name: 'Creator',
value: 'foo'
}
]
},
{
name: 'otherTest',
metadata: [
{
name: 'Created',
value: 'foo'
},
{
name: 'Date',
value: 'foo'
}
]
},
{
name: 'finalTest'
}
];
result = arr.reduce((p,c) => c.metadata ? p.concat(c.metadata.map(e => e.name))
: p, []);
console.log(result);

Categories

Resources