map object keys from API using ES6 lodash - vue.js

I'm using Vue 3 for sending POST data to my API. The objects look like
const externalResults: ref(null)
const resource = ref({
id: null,
name: null,
state: {}
})
Before sending the data to the API I'm parsing the resource object to avoid sending a nested object related to state property. So the payload sent looks like
{
id: 1,
name: 'Lorem ipsum',
state_id: 14
}
The API returns a 422 in case of missing/wrong data
{
"message":"Some fields are wrong.",
"details":{
"state_id":[
"The state_id field is mandatory."
]
}
}
So here comes the question: how can I rename object keys in order to remove always the string _id from keys?
Since I'm using vuelidate I have to "map" the returned error details to model property names. Now I'm doing this to get details once the request is done
externalResults.value = e.response.data.details
but probably I will need something like
externalResults.value = e.response.data.details.map(item => { // Something here... })
I'd like to have a 1 line solution, no matter if it uses ES6 or lodash.
Please note that state_id is just a sample, there will be many properties ended with _id which I need to remove.
The expected result is
externalResults: {
"state":[
"The state_id field is mandatory."
]
}

I don't know how long you allow your one-liners to be, but this is what I come up with in ECMAScript, using Object.entries() and Object.fromEntries() to disassemble and reassemble the object:
const data = {
id: 1,
name: 'Lorem ipsum',
state_id: 14
};
const fn = (x) => Object.fromEntries(Object.entries(x).map(([k, v]) => [k.endsWith('_id') ? k.slice(0, -3) : k, v]));
console.log(fn(data));
You can shorten it a little more by using replace() with a regex:
const data = {
id: 1,
name: 'Lorem ipsum',
state_id: 14
};
const fn = (x) => Object.fromEntries(Object.entries(x).map(([k, v]) => [k.replace(/_id$/, ''), v]));
console.log(fn(data));
If you use lodash, you can go shorter still by using the mapKeys() function:
const data = {
id: 1,
name: 'Lorem ipsum',
state_id: 14
};
const fn = (x) => _.mapKeys(x, (v, k) => k.replace(/_id$/, ''));
console.log(fn(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

Related

Mapping raw sql result to graphql compatible structure in typeorm

post resolver fetches posts from postgres database and return them in response to graphql request.
The return value is array of Post entity objects. Post entity structure is given below.
{
id: 29,
title: 'Turkey Earthquake kills 1000 plus people',
text: "Body is saying and don't go to the dark path",
points: 0,
creatorId: 44,
createdAt: 2023-02-07T06:53:39.453Z,
updatedAt: 2023-02-08T07:01:54.409Z,
creator: User {
id: 44,
username: 'prashant',
email: 'prashant#gmail.com',
password: '$argon2id$v=19$m=65536,t=3,p=4$LqsF/Gb3E8J4+vm5TszyWQ$d+LhZ6mNOE4eLqWvdwItQ3qYMZr17+CfvPt9l0Iqk3M',
createdAt: 2023-02-06T07:32:11.717Z,
updatedAt: 2023-02-06T07:32:11.717Z
}
}
Now I have a subquery in SELECT section of the query.
#Query(() => PaginatedPosts)
async posts(
#Arg('limit', () => Int) limit: number,
#Arg('cursor', () => String, { nullable: true }) cursor: string | null,
#Ctx() { req, dataSource }: MyContext
): Promise<PaginatedPosts> {
const realLimit = Math.min(30, limit)
const realLimitPlusOne = realLimit + 1
const qb = dataSource
.createQueryBuilder()
.select('p')
.from(Post, 'p')
.innerJoinAndSelect('p.creator', 'u')
.orderBy('p.createdAt', 'DESC')
.take(realLimitPlusOne)
if (req.session.userId)
qb.addSelect((qb) => {
return qb
.subQuery()
.select('d.value')
.from(Updoot, 'd')
.where('"userId" = :userId AND "postId" = p.id', {
userId: req.session.userId
})
}, 'voteStatus')
else qb.addSelect('null', 'voteStatus')
if (cursor)
qb.where('p.createdAt < :cursor', {
cursor: new Date(parseInt(cursor))
})
const posts = await qb.getMany() // this is returning the above structure without `voteStatus` field in it.
console.log('posts: ', posts[0])
return {
posts: posts.slice(0, realLimit),
hasMore: posts.length === realLimitPlusOne
}
}
But instead of using await qb.getMany() if I use
const posts = await qb.getRawMany()
I get the data in following structure
{
p_id: 29,
p_title: 'Turkey Earthquake kills 1000 plus people',
p_text: "Body is saying and don't go to the dark path",
p_points: 0,
p_creatorId: 44,
p_createdAt: 2023-02-07T06:53:39.453Z,
p_updatedAt: 2023-02-08T07:01:54.409Z,
u_id: 44,
u_username: 'prashant',
u_email: 'prashant#gmail.com',
u_password: '$argon2id$v=19$m=65536,t=3,p=4$LqsF/Gb3E8J4+vm5TszyWQ$d+LhZ6mNOE4eLqWvdwItQ3qYMZr17+CfvPt9l0Iqk3M',
u_createdAt: 2023-02-06T07:32:11.717Z,
u_updatedAt: 2023-02-06T07:32:11.717Z,
voteStatus: -1
}
Which is not compatible with the graphql type I intend to respond with. So I am confused whether there is another way I can use queryBuilder to return the result with the compatible type or do I have to explicitly create a mapping function to map each object to desired structure of Post entity.

vue 3 reactivity shorthand?

So I have an Object that is empty and after the user submits the value, it is saved.
let state = reactive({
id: '',
name: '',
status: 0,
}
after the submit, I would like to save my values.
onResult(() => {
state.id = '1'
state.name = 'Name'
state.status = 1
})
I would like to swap the complete object like I would do it in a ref, like:
state.value = {
id: '1,
name: 'Name',
state: 1
};
Is there any shorthand when using reactive?
This is not specific to reactive objects and would require to clear an object, which is inefficient.
This is the case for a ref. It can be unwrapped in a script if needed to skip value access:
const stateRef = ref({...})
const stateObj = shallowReadonly(stateRef);

Item filtering but keeping track of filtered out items

Let's say I have a list of items like below and I would like to apply a list of filters onto it with ramda.
const data = [
{id: 1, name: "Andreas"},
{id: 2, name: "Antonio"},
{id: 3, name: "Bernhard"},
{id: 4, name: "Carlos"}
]
No biggie: pipe(filter(predA), filter(predB), ...)(data)
The tricky part is I would like to define my filters with a key for tracking what items have been filtered out by which filter.
const filterBy = (key, pred) => subs => {
const [res, rej] = partition(pred, subs)
return [{[key]: rej.map(prop('id'))}, res]
}
This all screams monad chaining or a transducer, but I can't get my head around it how to put it all together.
Let's say I have a 2 predicates:
const isEven = filterBy('id', i => i % 2 === 0)
const startsWithA = filterBy('name', startsWith('A'))
I would like to get a result that looks like this tuple with a rejection map and a list of "accepted" items (isEven threw out 1 and 3 and startsWithA rejected 3 and 4):
[
{
id: [1, 3],
name: [3, 4]
},
[{id: 2, name: "Antonio"}]
]
Vanilla JS version
I'm bothered by using the field name to describe the predicate. What happens if we also have, say, const nameTooLong = ({name}) => name .length < 8. Then how could we distinguish the two predicates in the output? So I would prefer to use descriptive predicate names, for instance,
[
{isEven: [1, 3], startsWithA: [3, 4]},
[{id: 2, name: "Antonio"}]
]
So that's what I do in this code:
const process = (preds) => (xs) => {
const rej = Object .fromEntries (Object .entries (preds)
.map (([k, v]) => [k, xs .filter (x => !v (x)) .map (x => x .id)])
)
const excluded = Object .values (rej) .flat()
return [rej, data .filter (({id}) => !excluded .includes (id))]
}
const data = [{id: 1, name: "Andreas"}, {id: 2, name: "Antonio"}, {id: 3, name: "Bernhard"}, {id: 4, name: "Carlos"}]
console .log (process ({
isEven: ({id}) => id % 2 === 0,
startsWithA: ({name}) => name .startsWith ('A')
}) (data))
.as-console-wrapper {max-height: 100% !important; top: 0}
It would not be overly difficult to alter this to return something like your requested format.
Using Ramda
The question was tagged Ramda, and I wrote this initially using Ramda tools, with a version that looks like this:
const process = (preds) => (xs) => {
const rej = pipe (map (flip (reject) (xs)), map (pluck ('id'))) (preds)
const excluded = uniq (flatten (values (rej)))
return [rej, reject (pipe (prop ('id'), flip (includes) (excluded))) (data)]
}
And we could continue to hack away at this until we made it entirely point-free. I just don't see any reason for that.
I'm a founder of Ramda and a big fan, but I don't see this as any more readable than the vanilla version. There is one exception: Ramda's map working on a plain object is much nicer than the Object .entries -> map -> Object .fromEntries dance in the vanilla code. I might use that feature and leave the rest in vanilla, though.
Ok so after some fiddling I came up with this kind of solution. Implementing a new monad seemed unnecessary and overwriting fantasy-land/filter was also a bad idea, as my predicates are basically tagged.
This seems to have a good mix of readability and returns basically an extended array for further processing.
class Partition extends Array {
constructor(items, filtered = {}) {
super(...items)
this.filtered = filtered
}
filterWithKey = (key, pred) => {
const [ok, notOk] = partition(pred, this.slice())
const filtered = mergeDeepWith(concat, this.filtered, {[key]: notOk})
return new Partition(ok, filtered)
}
filter = pred => this.filterWithKey("", pred)
}
const res = new Partition([
{id: 1, name: "Andreas"},
{id: 2, name: "Antonio"},
{id: 3, name: "Bernhard"},
{id: 4, name: "Carlos"}
])
.filterWithKey('id', ({id}) => id % 2 === 0)
.filterWithKey('name', ({name}) => name.startsWith('A'))
const toIds = map(prop('id'))
const rejected = map(toIds, res.filtered)
const accepted = [...res]
console.log(rejected, accepted)

How to compare results of two vuejs computed properties

Our application has events that users can apply to, as well as blog posts written about different events. We want to show users all of the blog posts for events where they have applied.
Each post has an eventId and each application object contains event.id. We want to show all of the posts where the the eventId is equal to one of the application.event.id's.
Here are our computed properties...
computed: {
...mapState(['posts', 'currentUser', 'applications']),
myApplications: function() {
return this.applications.filter((application) => {
return application.user.id === this.currentUser.uid
})
},
myEventPosts: function() {
return this.posts.filter((post => {
post.eventId.includes(this.myApplications.event.id)
})
}
How can we change meEventPosts to get the show the correct results?
Thanks!
This question is mostly related to JS, not Vue and calculated properties. It will be better if you create such a code snippet the next time, as I did below.
const posts = [{eventId: 1, name: 'Post 1'}, {eventId: 2, name: 'Post 2'}, {eventId: 3, name: 'Post 3'}];
const myApplications = [{eventId: 2, name: 'App 2'}];
const myEventPosts = function () {
const eventsIds = myApplications.map((app) => app.eventId);
return posts.filter((post) => eventsIds.includes(post.eventId));
}
console.log('posts:', myEventPosts());
So your myEventPosts computed property should look like:
myEventPosts: function() {
const eventsIds = this.myApplications.map((app) => app.eventId);
return this.posts.filter((post) => eventsIds.includes(post.eventId));
}

Transform objects pointfree style with Ramda

Given the function below, how do I convert it to point-free style? Would be nice to use Ramda's prop and path and skip the data argument, but I just can't figure out the proper syntax.
const mapToOtherFormat = (data) => (
{
'Name': data.Name
'Email': data.User.Email,
'Foo': data.Foo[0].Bar
});
One option would be to make use of R.applySpec, which creates a new function that builds objects by applying the functions at each key of the supplied "spec" against the given arguments of the resulting function.
const mapToOtherFormat = R.applySpec({
Name: R.prop('Name'),
Email: R.path(['User', 'Email']),
Foo: R.path(['Foo', 0, 'Bar'])
})
const result = mapToOtherFormat({
Name: 'Bob',
User: { Email: 'bob#example.com' },
Foo: [{ Bar: 'moo' }, { Bar: 'baa' }]
})
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.22.1/ramda.min.js"></script>
Here's my attempt:
const mapToOtherFormat = R.converge(
(...list) => R.pipe(...list)({}),
[
R.pipe(R.view(R.lensProp('Name')), R.set(R.lensProp('Name'))),
R.pipe(R.view(R.compose(R.lensProp('User'), R.lensProp('Email'))), R.set(R.lensProp('Email'))),
R.pipe(R.view(R.compose(R.lensProp('Foo'), R.lensIndex(0), R.lensProp('Bar'))), R.set(R.lensProp('Foo')))
]
)
const obj = {Name: 'name', User: {Email: 'email'}, Foo: [{Bar: 2}]}
mapToOtherFormat(obj)
Ramda console
[Edit]
We can make it completely point-free:
const mapToOtherFormat = R.converge(
R.pipe(R.pipe, R.flip(R.call)({})),
[
R.pipe(R.view(R.lensProp('Name')), R.set(R.lensProp('Name'))),
R.pipe(R.view(R.compose(R.lensProp('User'), R.lensProp('Email'))), R.set(R.lensProp('Email'))),
R.pipe(R.view(R.compose(R.lensProp('Foo'), R.lensIndex(0), R.lensProp('Bar'))), R.set(R.lensProp('Foo')))
]
)
Ramda console