Overview
To iterate over and simplify even the most complex JSON responses, we have a series of methods we can employ. Let's jump into some common use cases!
In this lesson, we will:
- Introduce and apply methods such as
first
andentries
Why methods?
Our GraphQL schemas allow clients to write clear and intuitive queries for data across our applications. But when consuming data from REST APIs, we'll commonly encounter responses that differ enormously from our desired shape. In these situations, we may need to do more to our JSON responses than just alias fields or access nested properties. When we introduce methods to our mapping syntax, we have a lot more power to reshape our responses exactly the way we want.
Method syntax
We're ready to explore a handful of methods—but let's first go over the syntax that brings them to life.
We start by specifying the property that we want to apply a transformation to. This is followed by an arrow, written as ->
(a dash and a greater-than symbol). Then we can specify the method we're going to call.
For some methods, we'll provide an additional selection in our connector: for instance, when we select specific properties from a transformed object. Other times, we can call a method to return a single value.
Depending on the value that we're returning, and where it is in our method, we need to provide an output key: the name that we want our transformed value to be returned as. Let's walk through a few examples so we can see what this means.
Applying methods
Let's take a peek at the rest of our products data. We'll find several properties we haven't yet brought into our Results: such as variants
. Inside variants
we'll find a bunch more data, from pricing and inventory to shipping. Lots of data to unpack here!
We can start by envisioning the shape of the data we want to return to clients. Here we've added three fields to our Product
type: price
, quantity
, and specification
. (We've also included a new Specification
type!)
type Product {"The unique identifier for the product"id: ID!"The product's name"name: String!"The at-a-glance description for a product"tagline: String!"The original, non-discounted price for the product"price: Float!"The number of units of this product in stock"quantity: Int!"Specifications about the product regarding size, material, etc."specifications: [Specification!]!}type Specification {"The particular specification being defined"name: String!"The value assigned to the specification"value: String!}
Looking back at our JSON data, we'll find that a product's variants
property includes much of the data that we'd want to surface here in our schema: such as price
and quantity
available.
But here's our first problem: variants
is an array, potentially containing multiple variants of a particular product. Let's keep things simple—and focus only on the first object in the variants
property. We'll do this with the first
method!
first
With first
, we can select just the first item in a list and return it on its own.
Let's jump back into our mapping syntax to see first
in action. Just under the selection braces ({}
) for tags
, we'll add the variants
property for each product object.
$.products {idnametagline: descriptiontags {id: tagIdname}variants}
We should see the variants
array for each product appear in our Results panel. Now let's apply the first
method; we'll add our arrow (->
) followed by first
.
variants->first
Hmm...nothing happens. Nothing that we want, anyway: instead, we see errors!
Though we've indicated that we want to return the first object in the array, we haven't specified the properties we want to select from that object. We can do that by adding curly braces ({}
) after our method name. Inside, let's add that property that we're after: price
.
variants->first {price}
Now we should see some promising results! Each product in our output array should have its own price
property, returning an object with lots of price-specific data.
For the purposes of returning basic price information, let's return just the original
property from the price
object for now. If we update price
to price.original
, the playground gives us a helpful error: we need to specify the output key we want our value to be returned under.
variants->first {price.original // ❌ Error!}
Back in our schema, we've specified that each Product
object should have a price
field. So let's indicate here in our mapping that our original price value should be returned under a price
key. We can do that by adding price
, followed by a colon (:
) to the start of this line.
variants->first {price: price.original}
Great! Plain and simple price information for each of our products.
Notice that the variants
key does not appear in any of our results. We applied a method to each product's variants
array, but we did not specify an output key for it. In its place, we accessed the price.original
property and returned its value under a key called price
. We've discarded the outer object, along with the array that contained it, and returned just the data point we were after.
That's one of our missing properties taken care of. Next up: how can we return each product's quantity
?
Take a stab at this yourself—then compare with our answer below!
$.products {idnametagline: descriptiontags {id: tagIdname}variants->first {price: price.originalquantity: inventory.quantity}}
entries
There's another field we want to account for on our Product
type—specifications
. And getting to that data looks a bit more complicated.
For this field, we want to return a list of Specification
types: these objects include the name
of some product attribute, along with the value
assigned to that attribute. These could be things like weight, product dimensions, or what the product is made of.
type Specification {"The particular specification being defined"name: String!"The value assigned to the specification"value: String!}
When we glance back at our data, however, we'll see that our REST response structure hardly resembles the shape in our schema. For one thing, the specifications for each product are different! The specifications
property is an object which includes any number of arbitrary keys, such as material
, size
, diameter
and more.
We want to take our collection of key-value pairs, and turn them into objects with consistent keys. To do this, we'll use the entries
method!
With entries
, we can capture key-value pairs and return each pair as an object in a list. The original key and value will be stored under new properties in the resulting object whose names we define.
This will let us take a key-value pair like "diameter": "10 inches"
and return it in an object with standard, predictable keys—like name
and value
, for instance!
{"name": "diameter", // the particular specification type"value": "10 inches" // the value assigned to it}
This will allow us to capture all of the key-value pairs in a product's specifications
and return them as objects that satisfy our schema's Specification
type.
type Product {# ... other fields"Specifications about the product regarding size, material, etc."specifications: [Specification!]!}type Specification {"The particular specification being defined"name: String!"The value assigned to the specification"value: String!}
Applying entries
We'll find our product's specifications
inside the first object in variants
. This means that we can add our new property to the same selection as price
and quantity
.
$.products {idnametagline: descriptiontags {id: tagIdname}variants->first {price: price.originalquantity: inventory.quantityspecifications}}
We should see our Results update immediately: we've got specifications!
What we want to see here is an array of objects, not just one big specification object. So let's apply entries
to our specifications
property.
First we'll add a colon, indicating that we want to use the name specifications
as our output key. Next, we'll repeat specifications
(the property we want to apply the transform to) and add ->entries
.
specifications: specifications->entries
After entries
, we'll add a set of curly braces ({}
). Within the curly braces, we define the keys we want our new object to contain. By default, the entries
method uses the variables key
and value
to refer to the original data from each key-value pair.
If we add key
and value
to the curly braces, each on its own line, we'll see the original values reflected directly in our Results panel:
specifications: specifications->entries {keyvalue}
Note: This returns the variable key
under an output key also called key
. Think of this syntax as shorthand for the longer expression key: key
. The same is true for value
in this example.
{"key": "number_of_pieces","value": "350"}
Based on our schema's Specification
type, we actually want our resulting objects to contain the keys name
and value
. We can alias the key
property to name
in the same way that we've seen before: by providing an output key. Let's preface key
in our selection mapping with our preferred property, name
. Our value
property can remain just as it is!
specifications: specifications->entries {name: keyvalue}
Note: In this example, we're still using shorthand to return the variable value
under a key called value
. In contrast, the original key
value is now being returned under the property name
.
That's better! Our specifications
now match our GraphQL schema perfectly.
Practice
entries
method useful for?Key takeaways
first
andentries
are just two of several methods available to use in the connectors mapping syntax.- With
first
, we can select and return the first item in a list. - With
entries
, we can transform more complicated structures into a flatter list of key-value pairs.
Up next
We've covered some basic use cases for transforming JSON data into the properties we defined in our schema; next up, we'll see how we can match and nest values.
Share your questions and comments about this lesson
This course is currently in
You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.