Custom map keys in GraphQL response
Asked Answered
N

1

7

I've been looking into GraphQL as a replacement for some REST APIs of mine, and while I think I've wrapped my head around the basics and like most of what I see so far, there's one important feature that seems to be missing.

Let's say I've got a collection of items like this:

{
    "id": "aaa",
    "name": "Item 1",
    ...
}

An application needs a map of all those objects, indexed by ID as such:

{
    "allItems": {
        "aaa": {
            "name": "Item 1",
            ...
        },
        "aab": {
            "name": "Item 2",
            ...
        }
    }
}

Every API I've ever written has been able to give results back in a format like this, but I'm struggling to find a way to do it with GraphQL. I keep running across issue 101, but that deals more with unknown schemas. In my case, I know exactly what all the fields are; this is purely about output format. I know I could simply return all the items in an array and reformat it client-side, but that seems like overkill given that it's never been needed in the past, and would make GraphQL feel like a step backwards. I'm not sure if what I'm trying to do is impossible, or I'm just using all the wrong terminology. Should I keep digging, or is GraphQL just not suited to my needs? If this is possible, what might a query look like to retrieve data like this?

I'm currently working with graphql-php on the server, but I'm open to higher-level conceptual responses.

Nonrigid answered 9/1, 2017 at 21:47 Comment(3)
"and would make GraphQL feel like a step backwards" -- in your case, perhaps you know what each and every client of the Web service will need. Otherwise, you are making the assumption that a map is what the client needs. Clients that need the opposite of what the Web service returns have to do some sort of conversion. IMHO, the decision of whether to return a map or an array-style collection is a wash, as it's 50:50 whether the client wants one or the other.Opiate
@Opiate the API I have in mind at the moment is only used internally to an organization, so yes, I can say with certainty that all clients need that data in the format they've always been getting it in. This is called on page load as a quick index for local search purposes, and extended item details are retrieved (in a format easily represented by GraphQL) as needed. The question is simply whether what I'm trying to do is possible.Nonrigid
"all clients need that data in the format they've always been getting it in" -- converting a collection to a map takes 1-3 lines of code in most modern programming languages. "is GraphQL just not suited to my needs?" -- if you are requiring the Web service to return a specific JSON structure, then GraphQL is not suitable. Even if you got past this case (and I don't know of a solution), you'll hit something else. You don't have absolute control over the structure of the JSON, which is dictated by the schema, and the schema isn't designed to handle arbitrary structures.Opiate
J
14

Unfortunately returning objects with arbitrary and dynamic keys like this is not really a first-class citizen in GraphQL. That is not to say you can't achieve the same thing, but in doing so you will lose many of the benefits of GraphQL.

If you are set on returning an object with id keys instead of returning a collection/list of objects containing the ids and then doing the transformation on the client then you can create a special GraphQLScalarType.

const GraphQLAnyObject = new GraphQLScalarType({
  name: 'AnyObject',
  description: 'Any JSON object. This type bypasses type checking.',
  serialize: value => {
    return value;
  },
  parseValue: value => {
    return value;
  },
  parseLiteral: ast => {
    if (ast.kind !== Kind.OBJECT) {
      throw new GraphQLError("Query error: Can only parse object but got a: " + ast.kind, [ast]);
    }
    return ast.value;
  }
});

The problem with this approach is that since it is a scalar type you cannot supply a selection set to query it. E.G. if you had a type

type MyType implements Node {
  id: ID!
  myKeyedCollection: AnyObject
}

Then you would only be able to query it like so

query {
  getMyType(id: abc) {
    myKeyedCollection  # note there is no { ... }
  }
}

As others have said, I wouldn't recommend this because you are losing a lot of the benefits of GraphQL but it goes to show that GraphQL can still do pretty much anything REST can.

Hope this helps!

Julee answered 10/1, 2017 at 0:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.