Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a default output type for JsValue #217

Closed
apatzer opened this issue Feb 23, 2017 · 9 comments
Closed

Provide a default output type for JsValue #217

apatzer opened this issue Feb 23, 2017 · 9 comments
Labels

Comments

@apatzer
Copy link

apatzer commented Feb 23, 2017

In GraphQL, there are circumstances where you may have a large number of derived classes and you want to output the variance as a raw JSON object. I might, for example want to output a mixed bag of Shape objects, each of which is described in a different format, or having a varying number of points/lines/curves.

case class Shape(name: String, points: JsValue)

At present, GraphQL cannot output such an object as:
Can't find suitable GraphQL output type for Option[play.api.libs.json.JsValue]

It seems like this should be easy to do since JsValue is an "already-marshalled" object.

Any suggestions on how to implement?

@OlegIlyenko
Copy link
Member

Indeed, it is possible to use raw JSON data with custom scalar type, though I would discourage you from using it. By using it you are losing many benefits of GraphQL. In your case, I would suggest using interface or union types to model different shapes, points, lines, etc

@apatzer
Copy link
Author

apatzer commented Feb 23, 2017

Thanks! Great example code as well. Do you really need to implement the entire custom marshall/unmarshalling system, or will just the ScalarType[JsValue] do?

I agree you lose many benefits of GraphQL through this approach, but I'm not sure I see the other way. Our whole system uses Sangria/GraphQL quite successfully (thanks...it's quite well written). However, I am implementing a recursive document store, where (much like HTML/XML) each node may have a common interface but also contains an unknown chunk of data/settings. You will not know apriori which type of node you are getting back.

@OlegIlyenko
Copy link
Member

OlegIlyenko commented Feb 23, 2017

Thanks! Yeah, you need to re-implement marshaller/unmarshaller because the standard one does not support raw JSON AST nodes. It may be possible to add a built-in support, but I still a bit undecided about this one. What concerns me the most is InputUnmarshaller.isScalarNode which generally allows any JSON node to be a scalar node, which is quite dangerous (and without InputUnmarshaller raw JSON value marshalling would be asymmetrical). And, as I mentioned, I would like to discourage the usage of raw JSON types, so making it easier would go in an opposite direction :) But, of course, I always open for ideas and suggestions.

GraphQL type system is quite powerful, so it can get you pretty far. For example, it can handle abstract and recursive types. Here is an example schema:

interface TreeNode {
  id: ID!
  name: String
}

type ParentNode implements TreeNode {
  id: ID!
  name: String 
  children: [TreeNode!]
}

type LeafNode implements TreeNode {
  id: ID!
  name: String 
  color: String
}

type Query {
  root: TreeNode
}

Given this schema you can execute queries like this one:

{
  root {
    name
    ... on ParentNode {
       children {
         id, name
         ... on ParentNode {
           children {
             id
           }
         }
       }
    }
  }
}

If you could describe your data model in more detail, maybe I would be able to suggest something more concrete.

@apatzer
Copy link
Author

apatzer commented Feb 24, 2017

Thanks for the example of recursion using fragments. It is still my understanding that GraphQL cannot handle trees of indefinite depth (see graphql/graphql-spec#91). Is that true of Sangria's implementation as well?

My use case is very similar to the TreeNode example, except TreeNode is polymorphic: each node in a generic "document" is comprised of subdocuments, which are comprised of leaves that could be text, images, tables, charts, etc. Each TreeNode has a common "header" with 'name' and 'id', but each has a 'data' field that's different.

'data' looks like { rows: 4, cols: 5, values: [...] } for a table, and { text: "hello", lang: "en" } for a paragraph.

I'm arguably mis-using GraphQL using a recursive tree of unknown depth and polymorphism, but since the rest of our APIs use GraphQL quite successfully, I've encapsulated the polymorphism as a JsValue. I'm storing a 'nodeType' enum that effectively allows a non-Sangria Input Marshaller to turn the JsValue back into Scala classes.

So, is this polymorphism justification to use raw JsValues, or is there a better approach?

@OlegIlyenko
Copy link
Member

GraphQL/Sangria can handle infinite-depth trees. you just can query only several levels at a time.

In general, it sounds like in your case interfaces + recursive GraphQL types should fit quite well. I guess it comes back to a client. If you can build a client in a way that only asks several levels at a time, then I think you don't need to resort to raw JSON values.

@apatzer
Copy link
Author

apatzer commented Mar 2, 2017

I think on the output side, a union type will work just fine:
union Shape = Circle | Triangle | Square

It appears that official GraphQL does not have any support for "input unions" however (graphql/graphql-js#207). Do you have a recommended approach for handling polymorphic inputs?

This is really the only reason I was tempted to go for raw Json inputs...

@apatzer
Copy link
Author

apatzer commented Mar 2, 2017

Right now, the best alternative to an InputUnion for GraphQL I've come up with is something like:

type ShapeInput {
  shapeType: Enum!  <- Required
  maybeCircle: Circle  <- Not required
  maybeSquare: Square
  maybeTriangle: Triangle
}

Essentially we use shapeType in a switch/match statement and check for a non-null subclass input. Basically we just need to maintain one case class that lists out all subclasses. Not ideal, but still enforces all the required inputs for the subclass itself.

@OlegIlyenko
Copy link
Member

Yeah, this is the issue I faced as well. Would love to see input union or interface types in the spec.

@apatzer apatzer closed this as completed Mar 4, 2017
@kuzkdmy
Copy link

kuzkdmy commented Aug 9, 2018

Hi I will also will be really happy with default output type for JsValue

My case is also little bit out of what GraphQL is all about when we comes to exact data fields fetch.
But we also stay with it and this is how we looks

Having selection for

  • grouping fields (region , country, currency)
  • metric fields(rateUSD)
    I have to respond with data that will be map of map of map..... depending on selection
  • here it should be
    {
    "region 1" : {"country1": {"currency-1": "1.5", "currency-2": "2.5"}}
    "region 2" : {"country3": {"currency-3": "15", "currency-4": "25"}}
    .....
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants