Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

Sorry, you do not have permission to ask a question, You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please type your username.

Please type your E-Mail.

Please choose an appropriate title for the post.

Please choose the appropriate section so your post can be easily searched.

Please choose suitable Keywords Ex: post, video.

Browse

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Navigation

  • Home
  • About Us
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • About Us
  • Contact Us
Home/ Questions/Q 2778

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Latest Questions

Author
  • 61k
Author
Asked: November 26, 20242024-11-26T08:34:09+00:00 2024-11-26T08:34:09+00:00

Grabbing subsets of JS object properties with… GraphQL?

  • 61k

This entry comes from my web wizardry newsletter, where I explore evergreen solutions to common web dev problems (no matter your favorite framework). If you like what you see, go sign up for free 🪄


Messing with JavaScript objects is pretty easy these days. Destructuring syntax is convenient and the spread ... operator helps with merging objects together. But what about grabbing just… a portion of an object?

This question deserves some visuals. Let's jump into the problem we're trying to solve, and a flexible solution we can add to any existing JavaScript project 💪

What we want

Say I have a big document of structured data. Without manually writing a new object by hand, I just want to pull the little slices that I actually care about.

Here's one such scenario:

A side-by-side comparison of 2 JavaScript objects. Under

In this case, we want a copy of our original fridge, but we only care about those isEdible subkeys.

My gut reaction is to reach for some declarative tools in my ES6 arsenal. Object destructuring comes to mind at first:

const whatsInMyFridge = {   weekOldPasta: {   ... } const { weekOldPasta: { isEdible: pastaIsEdible },     panSearedSalmon: { isEdible: panSearedSalmonIsEdible }     } = whatsInMyFridge 
Enter fullscreen mode Exit fullscreen mode

There's a few issues with this:

  • We can't easily destructure keys of the same name. Notice I had to convert each isEdible variable to the verbose pastaIsEdible and panSearedSalmonIsEdible
  • Destructuring leads to some pretty gnarly code as it gets more complex. Just with a few keys, we're already hitting multi-line { curly hell }.

And most of all, we still have to build our new object at the end! Our destructuring statement actually just made some one-off variables for each key in the object. We'll still have to do this:

const whatsEdible = {   weekOldPasta: {     isEdible: pastaIsEdible,   },   panSearedSalmon: {     isEdible: panSearedSalmonIsEdible,   } } 
Enter fullscreen mode Exit fullscreen mode

…which is hardly better than just writing the object from scratch 😢

What we really want is some magical syntax for just the keys we want to retrieve. Something like this really:

whatsInMyFridge.giveMeTheseKeys({     weekOldPasta {         isEdible     },     panSearedSalmon {         isEdible     } }) // -> a beautiful formatted JS object 
Enter fullscreen mode Exit fullscreen mode

📈 Enter: GraphQL

If you've worked with GraphQL before, you probably noticed how close that example comes to a GraphQL query!

A brief rundown for those unfamiliar: GraphQL is a “querying” language originally built for API calls. It was mainly born from the frustrations with REST requests, as API endpoints had to predict all the data a client might want to retrieve.

GitHub recently migrated to GraphQL because of this. Imagine this scenario:

  • User A wants to get information about their GitHub profile. They want to send off a username and get back the accounts name and profile picture
  • User B also wants some GitHub profile info. However, they're looking for a different set of info: the list of user recovery emails and their personal bio.

As you could imagine, User C might want a new combination of fields, as might users D-Z. So instead of returning a massive JSON payload to satisfy everyone, GitHub exposed a GraphQL API for you to describe exactly which fields you want.

Here's how User A might request a name and profile picture as part of their request body
This is from demo purposes, and won't actually work if you send to GitHub

{     userProfile(email: '10xengineer@genius.club') {         name         picture {             src             alt         }     } } 
Enter fullscreen mode Exit fullscreen mode

…And GitHub will “fill in the blanks” by providing values to those requested keys. As you can imagine, this syntax is flexible enough to use on any blob of JSON you want to filter down 👀

📖 Applying GraphQL to reading JSON objects

💡 TLDR: If you want the final solution without all the walkthrough, jump down to the finished product!

Let's figure out how to use that fancy syntax for our use case. The biggest question to resolve is “how do we interpret a GraphQL query in JS land?” Sadly, there isn't a nice “plain JS” solution, so we will be reaching for a library here.

Go ahead and install this graphql-query-to-json package. It does have a fair amount of sub-dependencies like the core graphql package and the complimentary json-to-graphql-query, so if that bothers you… my apologies 😢

Let's see what we get from our old “what's edible in my fridge” request:

const { graphQlQueryToJson } = require("graphql-query-to-json") // or if you prefer: import { graphQlQueryToJson } from 'graphql-query-to-json'  const asJson = graphQlQueryToJson(`{   weekOldPasta {     isEdible   }   panSearedSalmon {     isEdible   } }`) console.log(asJson) /* 👇 {   query: {     weekOldPasta: { isEdible: true },     panSearedSalmon: { isEdible: true }   } } */ 
Enter fullscreen mode Exit fullscreen mode

Neat! Toss in a string, get back a JS object. You'll notice that it wraps our requested object with the query key. This would be useful if we were sending this request to an API, but for our purposes, we'll just ignore that key in our helper function. It also stubs out any unknown key values with true, which we'll use to track down unfilled values later 👀

Traversing our query

With this JS object in hand, it's time to walk through all the keys and figure out which values to fill in. Let's start with a simple example that only goes 1 level of keys deep:

const myFridge = {     numEggs: 5,     pintsOfIceCream: 3,     degreeUnits: 'celsius', } const whatIWant = `{     numEggs     degreeUnits }` // grab the ".query" straight away, since we won't need that nested key const whatIWantAsJson = graphQlQueryToJson(whatIWant).query // 👉 { numEggs: true, degreeUnits: true } 
Enter fullscreen mode Exit fullscreen mode

Now we have our set of keys (numEggs and degreeUnits) each with a value of true. To assign our actual values in place of those true flags, we can

  1. loop through all the object keys in whatIWantAsJson, and
  2. assign values from the same key in myFridge.
// loop over the object keys... for (const key of Object.keys(whatIWantAsJson)) {     // and if that key has a value of "true"...     if (whatIWantAsJson[key] === true) {         // replace "true" with the value from myFridge         whatIWantAsJson[key] = myFridge[key]     } } console.log(whatIWantAsJson) // 👉 { numEggs: 5, degreeUnits: 'celsius' } 
Enter fullscreen mode Exit fullscreen mode

Handling nested objects

This basic loop handles 1 level of nesting. But what if we have a request like this?

{   // level 1   weekOldPasta {     // level 2     isEdible   }   ... } 
Enter fullscreen mode Exit fullscreen mode

For this, we'll need a way to run our loop over Object.keys for every level of keys in our object. Get ready to put on your computer science hat, because we're using recursion 😨

Pay attention to this new else statement we're adding:

// wrap our loop in a function we can call function assignValuesToObjectKeys(whatIWant, myFridge) {     for (const key of Object.keys(whatIWant)) {         if (whatIWant[key] === true) {             whatIWant[key] = myFridge[key]         } else {             // if the value isn't "true", we must have found a nested object             // so, we'll call this same function again, now starting from             // the nested object inside whatIWant             assignValuesToObjectKeys(whatIWant[key], myFridge[key])         }     } } 
Enter fullscreen mode Exit fullscreen mode

This is a classic example of a recursive function. We have 2 clauses here:

  • The base case: When we hit a value of true, we stop looking for nested objects
  • The recursive function call: When we haven't hit the “base” of our nested object, keep drilling down the chain of nested keys using the same function

With this in place, we have a reusable JS function for anywhere in our codebase 🥳

const myFridge = {       weekOldPasta: {           noodleSogginess: “high”,           numMeatballs: 4,           isEdible: false,       },       panSearedSalmon: {           oilUsed: “avocado”,           numSpices: 3,           isEdible: true,       } }  const whatIWant = graphQlQueryToJson(`{   weekOldPasta {     isEdible   }   panSearedSalmon {     isEdible   } }`).query  assignValuesToObjectKeys(whatIWant, myFridge) console.log(whatIWant) /* 👉 {     weekOldPasta: {         isEdible: false,     },     panSearedSalmon: {         isEdible: true,     }, } */ 
Enter fullscreen mode Exit fullscreen mode

Cleaning this up a little

You'll notice that our assignValuesToObjectKeys function doesn't return anything; it just modifies the whatIWant object we passed in. For added readability, we might add a wrapper function to handle the graphQlQueryToJson call and actually return our requested object:

function grabPartialObject(originalObject = {}, query = "") {     const whatIWant = graphQlQueryToJson(query).query     assignValuesToObjectKeys(whatIWant, originalObject)     return whatIWant } ... const whatsEdible = grabPartialObject(myFridge, `{   weekOldPasta {     isEdible   }   panSearedSalmon {     isEdible   } }`) console.log(whatsEdible) // gives us the same object as before! 
Enter fullscreen mode Exit fullscreen mode

Handling arrays

So we've conquered nested objects. But what if we have an array of objects that we want to filter?

For instance, say our fridge data was structured just a bit differently:

const myFridge = {   food: [     {       name: 'Week old pasta',       noodleSogginess: 'high',       numMeatballs: 4,       isEdible: false,     },     {       name: 'Pan Seared Salmon',       oilUsed: 'avocado',       numSpices: 3,       isEdible: true,     },   ], } 
Enter fullscreen mode Exit fullscreen mode

…and we just care about the name and isEdible keys for every object in that array. Following how GraphQL requests normally work, we'd expect this sort of syntax to work:

{     food {         name         isEdible     } } 
Enter fullscreen mode Exit fullscreen mode

In other words, treat food like it's a regular object in the request, and we'll be smart enough to handle arrays of data.

This answer is a bit more involved than our previous examples. So, I'll leave you with a thoroughly commented codeblock:

function assignValuesToObjectKeys(whatIWant, myFridge) {   for (const key of Object.keys(whatIWant)) {     if (whatIWant[key] === true) {       ...       // 👇 If the fridge data happens to be an array...     } else if (Array.isArray(myFridge[key])) {       // first, keep track of the object they requested       const originalRequest = whatIWant[key]       // then, create an array where that request used to be       // for us to "push" new elements onto       whatIWant[key] = []       // loop over the items in our array of food...       for (const fridgeItem of myFridge[key]) {         // make a variable to store the result of assignValuesToObjectKeys         // we use { ...originalRequest } here to create a "copy"         const requestedItem = { ...originalRequest }         // grab the keys we want out of that array element         assignValuesToObjectKeys(requestedItem, fridgeItem)         // then, push our shiny new object onto our running list         whatIWant[key].push(requestedItem)       }     } else {       ...     }   } } 
Enter fullscreen mode Exit fullscreen mode

That's a fair amount of code! To briefly summarize, you'll need to:

  1. Check when our actual data is an array, rather than a simple object
  2. Loop over the actual data and assignValuesToObjectKeys for each one
  3. Push the results onto a running array in whatIWant, with necessary helper variables to keep track of your original request

🚀 The finished product

Here's how our finished product looks! I've renamed myFridge 👉 actualObj and whatIWant 👉 requestedObj so our naming conventions are more universal. I also added a hasOwnProperty check to assert we're requesting a key that actually exists. If not, raise an exception.

Remember, you'll need to add the graphql-query-to-json package package to your project for this to work.

const { graphQlQueryToJson } = require("graphql-query-to-json")  function assignValuesToObjectKeys(requestedObj, actualObj) {   for (const key of Object.keys(requestedObj)) {     if (!actualObj.hasOwnProperty(key)) {         throw `You requested a key that doesn't exist: ${key}`     } else if (requestedObj[key] === true) {       requestedObj[key] = actualObj[key]     } else if (Array.isArray(actualObj[key])) {       // keep track of the object they requested       const originalRequest = requestedObj[key]       // then, create an array where that request used to be       // for us to "push" new elements onto       requestedObj[key] = []       for (const actualItem of actualObj[key]) {         // make a variable to store the result of assignValuesToObjectKeys         // we use { ...originalRequest } here to create a "copy"         const requestedItem = { ...originalRequest }         assignValuesToObjectKeys(requestedItem, actualItem)         requestedObj[key].push(requestedItem)       }     } else {       console.log(requestedObj[key])       // if the value isn't "true", we must have found a nested object       // so, we'll call this same function again, now starting from       // the nested object inside requestedObj       assignValuesToObjectKeys(requestedObj[key], actualObj[key])     }   } }  // 👇 Function you'll actually `export` for others to use function grabPartialObject(actualObj = {}, query = '') {   const requestedObj = graphQlQueryToJson(query).query   assignValuesToObjectKeys(requestedObj, actualObj)   return requestedObj } 
Enter fullscreen mode Exit fullscreen mode

Usage example

const { grabPartialObject } = require('./some/helper/file')  const myFridge = {       weekOldPasta: {           noodleSogginess: “high”,           numMeatballs: 4,           isEdible: false,       },       panSearedSalmon: {           oilUsed: “avocado”,           numSpices: 3,           isEdible: true,       } }  const whatsEdible = grabPartialObject(myFridge, `{   weekOldPasta {     isEdible   }   panSearedSalmon {     isEdible   } }`) console.log(whatsEdible) /* 👉 {     weekOldPasta: {         isEdible: false,     },     panSearedSalmon: {         isEdible: true,     }, } */ 
Enter fullscreen mode Exit fullscreen mode

Learn a little something?

Glad to hear it! If you want more universal solutions like this, you can sign up for the web wizardry newsletter for some bi-weekly web sorcery 🔮

graphqljavascriptwebdev
  • 0 0 Answers
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

Sidebar

Ask A Question

Stats

  • Questions 4k
  • Answers 0
  • Best Answers 0
  • Users 2k
  • Popular
  • Answers
  • Author

    How to ensure that all the routes on my Symfony ...

    • 0 Answers
  • Author

    Insights into Forms in Flask

    • 0 Answers
  • Author

    Kick Start Your Next Project With Holo Theme

    • 0 Answers

Top Members

Samantha Carter

Samantha Carter

  • 0 Questions
  • 20 Points
Begginer
Ella Lewis

Ella Lewis

  • 0 Questions
  • 20 Points
Begginer
Isaac Anderson

Isaac Anderson

  • 0 Questions
  • 20 Points
Begginer

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore, ask, and connect. Join our vibrant Q&A community today!

About Us

  • About Us
  • Contact Us
  • All Users

Legal Stuff

  • Terms of Use
  • Privacy Policy
  • Cookie Policy

Help

  • Knowledge Base
  • Support

Follow

© 2022 Querify Question. All Rights Reserved

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.