The resolve callback on a GraphQL field gets invoked with "root" as the first argument, which can be used to calculate the data for the field. Is there anyway to use data from a parent object to compute a field?
Take this example:
This returned from the underlying api
[
{
"id": 123,
"buyer": {
"id": 234,
"preferredCurrency": "EUR"
},
"item": {
"title": "item title",
"price": {
"convertedAmounts": {
"USD": 100,
"EUR": 90,
"GBP": 80
}
}
},
...
}
]
I'd like to write a query like this:
{
item {
price {
buyerPreferredAmount {
amount
currency
}
}
}
}
Which returns:
[
{
"item": {
"price": {
"buyerPreferredAmount": {
"amount": 90,
"currency": "EUR"
}
}
}
}
]
Where the schema is defined like this:
const CurrencyAmount = new graphql.GraphQLObjectType({
name: 'CurrencyAmount',
fields: {
amount: {type: graphql.GraphQLFloat},
currency: {type: graphql.GraphQLString},
}
});
const FinancialAmount = new graphql.GraphQLObjectType({
name: "FinancialAmount",
fields: {
buyerPreferredAmount: {
type: CurrencyAmount,
resolve(root, args, info) {
// How can I do something like this?
// const buyerCurrency = parent.parent.parent.buyer.preferredCurrency
return { currency: buyerCurrency, amount: root.convertedAmounts[buyerCurrency] };
}
}
}
});
const Item = new graphql.GraphQLObjectType({
name: "Item",
fields: {
price: {type: FinancialAmount}
}
});
const Order = new graphql.GraphQLObjectType({
name: "Order",
fields: {
item: {type: Item}
}
});
How can I implement the resolve method of the buyerCurrency field on FinancialAmount?
So there are a few ways to do this.
If you just care about a single user (probably the user making the query), you could treat that as being implicit in the query. At FB, we do this all the time; we could do username(name: "zuck"){isViewerFriend} to ask if the logged in viewer is friend's with Zuck, for example, which implicitly assumes the viewer.
If you care about different user's depending on the data (for example, an order has a buyer and an item, which looks like the case here), it gets trickier. In particular, I think the key question here is whether an item has a buyer, or whether an item is independent of the buyer, and it is only associated with the buyer in the order (I've reduced the depth of the stack here for response clarity, but the logic can be applied to whether a financial amount has a buyer as well).
buyerPreferredAmount depending on which part of the query we are in, which could mess with caching. So instead of having buyerPreferredAmount on item, I would suggest hoisting that field to the first level that has both the buyer and the item; in this case, that is probably the order.So the answer to the metaquestion about getting access to the parent is that GraphQL does not allow that in general; we want the objects returned in the query to be identical no matter where they were queried from so that we can cache them clearly; if an item in a "store" and an item in an "order" and an item in a "sale list" are all the same item, then we shouldn't be able to change the data on the item by looking at the parent. On the other hand, a lot of times an object in a query _as part of its data model_ contains a parent pointer; within Facebook, you can't get the parent post from a comment through GraphQL by traversing parent pointers... but a comment as part of its data model has a getParentPost method that GraphQL can expose like any other field. Finally, when a parent pointer doesn't exist on the data model (by design) but we need access to the parent data for a field, that's usually a sign that the field is on the wrong object, and it should actually live at a higher point in the tree, where the underlying object has access to all of the data it needs.
Hope this helps!
Thanks for the very helpful response. I hadn't considered the caching implications. I think hoisting the fields to the higher level is the best approach.
Most helpful comment
So there are a few ways to do this.
If you just care about a single user (probably the user making the query), you could treat that as being implicit in the query. At FB, we do this all the time; we could do
username(name: "zuck"){isViewerFriend}to ask if the logged in viewer is friend's with Zuck, for example, which implicitly assumes the viewer.If you care about different user's depending on the data (for example, an order has a buyer and an item, which looks like the case here), it gets trickier. In particular, I think the key question here is whether an item has a buyer, or whether an item is independent of the buyer, and it is only associated with the buyer in the order (I've reduced the depth of the stack here for response clarity, but the logic can be applied to whether a financial amount has a buyer as well).
buyerPreferredAmountdepending on which part of the query we are in, which could mess with caching. So instead of havingbuyerPreferredAmounton item, I would suggest hoisting that field to the first level that has both the buyer and the item; in this case, that is probably the order.So the answer to the metaquestion about getting access to the parent is that GraphQL does not allow that in general; we want the objects returned in the query to be identical no matter where they were queried from so that we can cache them clearly; if an item in a "store" and an item in an "order" and an item in a "sale list" are all the same item, then we shouldn't be able to change the data on the item by looking at the parent. On the other hand, a lot of times an object in a query _as part of its data model_ contains a parent pointer; within Facebook, you can't get the parent post from a comment through GraphQL by traversing parent pointers... but a comment as part of its data model has a
getParentPostmethod that GraphQL can expose like any other field. Finally, when a parent pointer doesn't exist on the data model (by design) but we need access to the parent data for a field, that's usually a sign that the field is on the wrong object, and it should actually live at a higher point in the tree, where the underlying object has access to all of the data it needs.Hope this helps!