Skip to main content
Stack Overflow
  1. About
  2. For Teams

Return to Revisions

1 of 2
philolegein
  • 1.6k
  • 15
  • 50

constructing find / reduce / "recursion" to get deeply nested object

I'm trying to work around the fact that Datocms doesn't support a where filter in their GraphQL schema. Since there isn't that much data, I figured I could query all of it, and do the find on my end, but ... I'm not succeeding :)

What I get back when I query all of the data looks like this:

"foo": {
 "data": {
 "allGiveawayLandingPages": [
 {
 "lpSection": [
 {},
 {},
 {},
 {},
 {},
 {},
 {},
 {
 "id": "34525949",
 "products": [
 {
 "__typename": "PurchaseCardRecord",
 "discountAmount": 50,
 "discountAmountPct": null,
 "discountEndDate": "2022年11月01日T23:00:00+00:00",
 "id": "44144096"
 },
 {
 "__typename": "PurchaseCardRecord",
 "discountAmount": null,
 "discountAmountPct": null,
 "discountEndDate": null,
 "id": "44144097"
 }
 ]
 }
 ]
 }
 ]
 }
}

I need to find the object down in the "products" array by "id". This general question has been asked and answered lots of times, but somehow I can't get any of the previous answers to work — which I think boils down to my not understanding how Array.find or Array.reduce work.

I'm doing this inside of a try/catch block, which I mention because it seems to be making this hard to debug (I'll come back to this):

export default async function createPaymentIntentHandler(req, res) {
 const body = JSON.parse(req.body);
 const {
 productId,
 productType
 } = body;
 let data;
 if ('POST' === req.method) {
 try {
 switch (productType) {
 case 'SeminarRecord':
 data = await request({ query: singleSeminarQuery(productId) });
 productObjName = 'seminar';
 break;
 default:
 data = await request({ query: singleProductQuery(productId) });
 productObjName = 'product';
 }
 /**
 * Here's where I want to do my query / filtering
 */
 // ... do more stuff and create Stripe paymentIntent
 res.status(200).send({clientSecret: paymentIntent.client_secret})
 } catch (error) {
 logger.error({error}, 'Create Payment Intent error');
 return res.status(400).end(`Create Payment Intent error: ${error.message}`);
 }
 } else {
 res.status(405).end('Method not allowed');
 }
}

My first, naive attempt was

const foo = await request({ query: ALL_PURCHASE_CARDS_QUERY });
const card = foo.data.allGiveawayLandingPages.find((page) => {
 return page.lpSection.find((section) => {
 return section?.products.find((record) => record.id === parentId)
 })
});
logger.debug({card}, 'Got card');

In the abstract, aside from the fact that the above is fairly brittle because it relies on the schema not changing, I'd expect some similar sort of ES6 construction to work. This particular one, however, throws, but not in a particularly useful way:

[08:09:18.690] ERROR: Create Payment Intent error
 env: "development"
 error: {}

That's what I meant by it being hard to debug — I don't know why the error object is empty. But, in any case, that's when I started searching StackOverflow. The first answer which looked promising was this one, which I implemented as

...
const {
 productId,
 productType,
 parentId
} = body;
...
function findCard(parent, id) {
 logger.debug({parent}, 'searching in parent')
 for (const item of parent) {
 if ('PurchaseCardRecord' === item.__typename && item.id === id) return item;
 if (item.children?.length) {
 const innerResult = findCard(item.children, id);
 if (innerResult) return innerResult;
 }
 }
}
if ('POST' === req.method) {
 try {
 ...
 const foo = await request({ query: ALL_PURCHASE_CARDS_QUERY });
 const card = findCard(foo, parentId);
 logger.debug({card}, 'Got card');

This similarly throws unhelpfully, but my guess is it doesn't work because in the structure, not all children are iterables. Then I found this answer, which uses reduce instead of my original attempt at find, so I took a pass at it:

 const card = foo.data.allGiveawayLandingPages.reduce((item) => {
 item?.lpSection.reduce((section) => {
 section?.products.reduce((record) => {
 if ('PurchaseCardRecord' === record.__typename && record.id === parentId) return record;
 })
 })
 })

This is actually the closest I've gotten, because it doesn't throw an error; however, it's also not returning the matching child object, it's returning the first parent object that contains the match (i.e., it's returning the whole "lpSection" object). I'm relatively certain this is the right way to go, but I'm just not understanding his original construction:

arr.reduce((a, item) => {
 if (a) return a;
 if (item.id === id) return item;

I've tried to understand the MDN documentation for Array.reduce, but, I don't know, I must be undercaffeinated or something. The syntax is described as

reduce((previousValue, currentValue) => { /* ... */ } )

and then several variations on the theme. I thought it would return all the way up the stack in my construction, but it doesn't. I also tried

 const card = foo.data.allGiveawayLandingPages.reduce((accumulator, item) => {
 return item?.lpSection.reduce((section) => {
 return section?.products.reduce((record) => {
 if ('PurchaseCardRecord' === record.__typename && record.id === parentId) return record;
 })
 })
 })

but the result was the same. Finally, not understanding what I'm doing, I went back to an older answer that doesn't use the ES6 methods but relies on recursing the object.

...
function filterCards(object) {
 if (object.hasOwnProperty('__typename') && object.hasOwnProperty('id' && ('PurchaseCardRecord' === object.__typename && parentId === object.id))) return object;
 for (let i=0; i<Object.keys(object).length; i++) {
 if (typeof object[Object.keys(object)[i]] == 'object') {
 const o = filterCards(object[Object.keys(object)[i]]);
 if (o != null) return o;
 }
 }
 return null;
}
if ('POST' === req.method) {
 try {
 ...
 const foo = await request({ query: ALL_PURCHASE_CARDS_QUERY });
 const card = filterCards(foo);
 logger.debug({card}, 'Got card');

Alas, this goes back to throwing, but still with only an empty error object.

So, now I'm here asking a question that's been asked and answered 1,000 times. Apologies for that, but ... how do I extract my nested object?

philolegein
  • 1.6k
  • 15
  • 50
lang-js

AltStyle によって変換されたページ (->オリジナル) /