4. Transforming data with methods
6m

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 and entries

Why methods?

Our 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 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.

A diagram showing complicated input flowing into a method and emerging flatter and more readable

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.

A diagram showing the method syntax

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.

A diagram showing the method syntax with an output key

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 to our Product type: price, quantity, and specification. (We've also included a new Specification type!)

schema.graphql
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.

The variants property in one of the product objects highlighted

But here's our first problem: variants is an array, potentially containing multiple 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.

A diagram showing the first method applied to a list of products, returning the first in the list

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 {
id
name
tagline: description
tags {
id: tagId
name
}
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!

The Mapping panel filled with errors from our selection

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.

The Results panel updated to show the price object for each product

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.

The Mapping panel filled with errors from selection price.original

variants->first {
price.original // ❌ Error!
}

Back in our schema, we've specified that each Product object should have a price . 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.

The Mapping and Results panels updated to show correct price information

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.

A diagram showing how the original price is accessed from the first object under variants

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!

Returning each product's quantity
$.products {
id
name
tagline: description
tags {
id: tagId
name
}
variants->first {
price: price.original
quantity: inventory.quantity
}
}

entries

There's another we want to account for on our Product type—specifications. And getting to that data looks a bit more complicated.

For this , we want to return a list of Specification types: these objects include the name of some product , along with the value assigned to that . These could be things like weight, product dimensions, or what the product is made of.

schema.graphql
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.

A diagram showing how each product differs in the specification attributes it includes

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.

A diagram showing entries applied to a dictionary and outputting objects with consistent keys

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.

schema.graphql
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 {
id
name
tagline: description
tags {
id: tagId
name
}
variants->first {
price: price.original
quantity: inventory.quantity
specifications
}
}

We should see our Results update immediately: we've got specifications!

The Mapping and Results panels updated to return 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 key and value to refer to the original data from each key-value pair.

A diagram showing how the keys and values from the input dictionary will be applied to each output objects

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 {
key
value
}

Note: This returns the 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.

Example JSON output
{
"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 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 with our preferred property, name. Our value property can remain just as it is!

specifications: specifications->entries {
name: key
value
}

Note: In this example, we're still using shorthand to return the value under a key called value. In contrast, the original key value is now being returned under the property name.

The Mapping and Results panels updated to return specifications keyed the way we want, with name and value

That's better! Our specifications now match our perfectly.

Practice

What is the entries method useful for?

Key takeaways

  • first and entries 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.

Previous

Share your questions and comments about this lesson

This course is currently in

beta
. Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.

You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.