Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
An example of custom raw JSON scalar type in sangria. DON'T USE IT! By using it you lose many benefits of GraphQL. This just demonstrates that it is possible. If you tempted to expose it, then definitely think twice before using it.
import sangria.ast
import sangria.execution.Executor
import sangria.marshalling.{InputUnmarshaller, ScalarValueInfo, ArrayMapBuilder, ResultMarshaller}
import sangria.schema._
import sangria.validation.{ValueCoercionViolation, IntCoercionViolation, BigIntCoercionViolation}
import spray.json._
import sangria.macros._
import scala.concurrent.ExecutionContext.Implicits.global
implicit object CustomSprayJsonResultMarshaller extends ResultMarshaller {
type Node = JsValue
type MapBuilder = ArrayMapBuilder[Node]
def emptyMapNode(keys: Seq[String]) = new ArrayMapBuilder[Node](keys)
def addMapNodeElem(builder: MapBuilder, key: String, value: Node, optional: Boolean) = builder.add(key, value)
def mapNode(builder: MapBuilder) = JsObject(builder.toMap)
def mapNode(keyValues: Seq[(String, JsValue)]) = JsObject(keyValues: _*)
def arrayNode(values: Vector[JsValue]) = JsArray(values.toVector)
def optionalArrayNodeValue(value: Option[JsValue]) = value match {
case Some(v) v
case None nullNode
}
def scalarNode(value: Any, typeName: String, info: Set[ScalarValueInfo]) = value match {
case v: String JsString(v)
case v: Boolean JsBoolean(v)
case v: Int JsNumber(v)
case v: Long JsNumber(v)
case v: Float JsNumber(v)
case v: Double JsNumber(v)
case v: BigInt JsNumber(v)
case v: BigDecimal JsNumber(v)
case v: JsValue v
case v throw new IllegalArgumentException("Unsupported scalar value: " + v)
}
def enumNode(value: String, typeName: String) = JsString(value)
def nullNode = JsNull
def renderCompact(node: JsValue) = node.compactPrint
def renderPretty(node: JsValue) = node.prettyPrint
}
implicit object SprayJsonInputUnmarshaller extends InputUnmarshaller[JsValue] {
def getRootMapValue(node: JsValue, key: String) = node.asInstanceOf[JsObject].fields get key
def isListNode(node: JsValue) = node.isInstanceOf[JsArray]
def getListValue(node: JsValue) = node.asInstanceOf[JsArray].elements
def isMapNode(node: JsValue) = node.isInstanceOf[JsObject]
def getMapValue(node: JsValue, key: String) = node.asInstanceOf[JsObject].fields get key
def getMapKeys(node: JsValue) = node.asInstanceOf[JsObject].fields.keys
def isDefined(node: JsValue) = node != JsNull
def getScalarValue(node: JsValue) = node match {
case JsBoolean(b) b
case JsNumber(d) d.toBigIntExact getOrElse d
case JsString(s) s
case n n
}
def getScalaScalarValue(node: JsValue) = getScalarValue(node)
def isEnumNode(node: JsValue) = node.isInstanceOf[JsString]
def isScalarNode(node: JsValue) = true
def isVariableNode(node: JsValue) = false
def getVariableName(node: JsValue) = throw new IllegalArgumentException("variables are not supported")
def render(node: JsValue) = node.compactPrint
}
case object JsonCoercionViolation extends ValueCoercionViolation("Not valid JSON")
implicit val JsonType = ScalarType[JsValue]("Json",
description = Some("Raw JSON value"),
coerceOutput = (value, _) value,
coerceUserInput = {
case v: String Right(JsString(v))
case v: Boolean Right(JsBoolean(v))
case v: Int Right(JsNumber(v))
case v: Long Right(JsNumber(v))
case v: Float Right(JsNumber(v))
case v: Double Right(JsNumber(v))
case v: BigInt Right(JsNumber(v))
case v: BigDecimal Right(JsNumber(v))
case v: JsValue Right(v)
},
coerceInput = {
case ast.StringValue(jsonStr, _, _)
Right(jsonStr.parseJson)
case _
Left(JsonCoercionViolation)
})
val ProductType = ObjectType("Product", fields[Unit, Unit](
Field("name", StringType, resolve = _ "Rusty Sword"),
Field("withArg", StringType,
arguments = Argument("jsonArg1", JsonType) :: Argument("jsonArg2", JsonType) :: Nil,
resolve = c s"Rusty Sword ${c.arg[Any]("jsonArg1").getClass} ${c.arg[Any]("jsonArg1")} ${c.arg[Any]("jsonArg2").getClass} ${c.arg[Any]("jsonArg2")}"),
Field("attributes", JsonType, resolve = _ JsObject(
"damage" JsNumber(10),
"durability" JsNumber(3),
"magical" JsBoolean(true),
"magicElements" JsArray(
JsObject(
"element" JsString("fire"),
"manaDrainSpeed" JsNumber(1.5)))))
))
val schema = Schema(ObjectType("Query", fields[Unit, Unit](
Field("products", ListType(ProductType), resolve = _ List((), ()))
)))
val vars = """{"foo": {"a": 1, "b": [{"c": true}]}}""".parseJson
val query =
graphql"""
query ($$foo: Json!) {
products {
name
withArg(jsonArg1: "{\"aaa\": \"aaa\", \"bbb\": [{\"a\": 1, \"b\": true}]}", jsonArg2: $$foo)
attributes
}
}
"""
println(Executor.execute(schema, query, variables = vars).await.prettyPrint)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment