I stumbled upon a hack for using scalars with a se...
# graphql-kotlin
j
I stumbled upon a hack for using scalars with a sealed class for a geojson geometry. You can find our Geometry sealed class here (oss, feedback welcome): https://github.com/jillesvangurp/geogeometry/blob/3.1.19/src/commonMain/kotlin/com/jillesvangurp/geojson/geojson.kt I used a schema hook (code below) to register a scalar for this. So we simply return objects with a geometry field and it actually serializes correctly. The hack here is that I let the Geometry object coerce to it self. After that, toString gets called apparently. If you check the linked implementation above, I simply implemented that using kotlinx.serialization so it serializes to json. Two questions and a couple of suggestions: 1) is this something I should rely on to continue to work or is this something that can be done differently? One of my issues here is that the coordinates field in a geometry is shaped differently between the different classes. So I need to do something custom here. This bypasses the whole logic. In my query I simply ask for a Geometry and get back a json object, which parses fine. 2) it strikes me that this is a nice way to add scalars and something that potentially also works for parameters even though graphql currently does not support union types or interfaces there. As you can see, I did not implement the parse methods yet but looks like it could be doable. Which of the parse functions actually gets called? There appear to be several. And a suggestions: • Maybe add the
@Component
to the example for custom scalars. Without that, it doesn't work for obvious reasons. • The documentation suggests this should only work with things like strings, which seems to be not the case • Maybe generalize the approach below to have kotlinx.serialization and maybe jackson strategies for coercion. Mostly that should work and users can simply reuse their existing serialization/deserialization logic that way. Or if that is supported some other way, maybe document that?
Copy code
@Component
class GeometryGraphqlScalar : SchemaGeneratorHooks {
    override fun willGenerateGraphQLType(type: KType): GraphQLType? {
        return when (type.classifier as? KClass<*>) {
            Geometry::class -> geometryScalar
            else -> null
        }
    }
}

private val geometryScalar = GraphQLScalarType
    .newScalar()
    .name("Geometry")
    .description("json serialized geometry")
    .coercing(GeometryCoercing)
    .build()

object GeometryCoercing : Coercing<Geometry, Geometry?> {
    override fun serialize(input: Any): Geometry {
        // little hack, causes toString to be called, which we implemented using Json.encodeToString ...
        return  input as Geometry
    }

    override fun parseValue(input: Any): Geometry {
        error("we don't support parameter scalars currently but we could ${input::class.qualifiedName}")
    }

    override fun parseLiteral(input: Any): Geometry {
        error("we don't support parameter scalars currently but we could ${input::class.qualifiedName}")
    }
}
d
custom scalars can be anything and they can represent objects (e.g. common use cases are something
BigInteger
and various
Datetime
implementations, see https://github.com/graphql-java/graphql-java-extended-scalars) but they have to serialize as Strings (https://spec.graphql.org/June2018/#sec-Scalars)
the downside of the custom scalars is that your clients have to know what they represent as otherwise they will treat it as a string
there is a proposal to graphql-spec (already implemented by graphql-java and graphql-js) to support additional metadata about the scalars -> https://github.com/graphql-java/graphql-java/pull/1772
as for coercing values I recommend to take a look at https://www.graphql-java.com/documentation/v16/scalars/ which describes the process pretty well
j
OK thanks. So why does this work? Because I'm definitely producing valid json here.
d
you mean you serialize your scalar to a JSON?
i think it might be breaking the spec but unsure if graphql-java enforces it
if you are producing a json why not use actual object type?
for output types should work fine (and I personally would recommend to do that) but I guess it wouldn't work for inputs as unions/interfaces are not supported
j
Yes that is what I do. Seems to actually work 🙂
The issue with geojson seems to be that subtypes overlap in incompatible ways. The coordinates field is a multi dimensional double array. But the number of dimensions depends on the particular subtype. For a point it's 1. For a line it's 2. For a polygon it's 3 (1 outer polygon + hole polygons) and for a multi polygon it's 4. When I tried that, I got an error about different list shapes. So that's why I tried a custom scalar. My initial thought was to simply serialize the whole thing to a json string. But then I accidentally discovered it actually works fine without the quotes. I assume this is a bit of a grey area. For inputs, we'll probably have to use a string regardless. But thanks for all the feedback. I'd recommend looking at geojson as a format because it represents a nice edge case for what people might want to legitimately want to do with graphql and kotlin that is currently hard to do.
👍 1
d
Pretty sure you could model output types as union of point/line/polygon etc, but again union/interfaces are not supported for inputs (yet)