3

我正在使用 Finch、Circe 和 Sangria 为 API 构建 GraphQL 端点。GraphQLvariables查询中通过的基本上是任意 JSON 对象(假设没有嵌套)。所以举个例子,在我的测试代码Strings中,这里有两个例子:

val variables = List(
  "{\n  \"foo\": 123\n}",
  "{\n  \"foo\": \"bar\"\n}"
)

Sangria API 需要这些类型的Map[String, Any].

我已经尝试了很多方法,但到目前为止还无法Decoder在 Circe 中为此编写一个。任何帮助表示赞赏。

4

3 回答 3

6

Sangria API 需要 Map[String, Any] 的类型

这不是真的。sangria 中执行的变量可以是任意类型T,唯一的要求是你有一个InputUnmarshaller[T]类型类的实例。所有编组集成库都InputUnmarshaller为对应的 JSON AST 类型提供了一个实例。

这意味着sangria-circe定义InputUnmarshaller[io.circe.Json]并且您可以使用import sangria.marshalling.circe._.

这是一个小而独立的示例,说明如何将 circeJson用作变量:

import io.circe.Json

import sangria.schema._
import sangria.execution._
import sangria.macros._

import sangria.marshalling.circe._

val query =
  graphql"""
    query ($$foo: Int!, $$bar: Int!) {
      add(a: $$foo, b: $$bar)
    }
  """

val QueryType = ObjectType("Query", fields[Unit, Unit](
  Field("add", IntType,
    arguments = Argument("a", IntType) :: Argument("b", IntType) :: Nil,
    resolve = c ⇒ c.arg[Int]("a") + c.arg[Int]("b"))))

val schema = Schema(QueryType)

val vars = Json.obj(
  "foo" → Json.fromInt(123),
  "bar" → Json.fromInt(456))

val result: Future[Json] =
  Executor.execute(schema, query, variables = vars)

正如您在此示例中所看到的,我将io.circe.Json其用作执行的变量。执行将产生以下结果 JSON:

{
  "data": {
    "add": 579
  }
}
于 2016-06-18T15:04:59.073 回答
1

这是一个有效的解码器。

type GraphQLVariables = Map[String, Any]

val graphQlVariablesDecoder: Decoder[GraphQLVariables] = Decoder.instance { c =>
  val variablesString = c.downField("variables").focus.flatMap(_.asString)
  val parsedVariables = variablesString.flatMap { str =>
    val variablesJsonObject = io.circe.jawn.parse(str).toOption.flatMap(_.asObject)
    variablesJsonObject.map(j => j.toMap.transform { (_, value: Json) =>
      val transformedValue: Any = value.fold(
        (),
        bool => bool,
        number => number.toDouble,
        str => str,
        array => array.map(_.toString),
        obj => obj.toMap.transform((s: String, json: Json) => json.toString)
      )
      transformedValue
    })
  }
  parsedVariables match {
    case None => left(DecodingFailure(s"Unable to decode GraphQL variables", c.history))
    case Some(variables) => right(variables)
  }
}

我们基本上解析 JSON,将其转换为JsonObject,然后相当简单地转换对象中的值。

于 2016-06-18T06:03:18.317 回答
0

尽管上述答案适用于 Sangria 的特定情况,但我对原始问题感兴趣:Circe 中处理任意 Json 块的最佳方法是什么(通常假设所有类型都是预先知道的)?

在编码/解码 Json 时,指定 95% 的 Json 是相当常见的,但最后 5% 是某种类型的“附加属性”块,可以是任何 Json 对象。

我玩过的解决方案:

  1. 将自由格式的块编码/解码为Map[String,Any]. 这意味着您必须为 引入隐式编码器/解码器Map[String, Any],这可以做到,但很危险,因为隐式可能会被拉到您不想要的地方。

  2. 将自由格式的块编码/解码为Map[String, Json]. 这是最简单的方法,并且在 Circe 中得到了开箱即用的支持。但是现在 Json 序列化逻辑已经泄露到您的 API 中(通常您希望将 Json 内容完全包装好,以便以后可以换成其他非 json 格式)。

  3. 编码/解码为 a String,其中字符串必须是有效的 Json 块。至少您还没有将您的 API 锁定到特定的 Json 库中,但是不得不要求您的用户以这种手动方式创建 Json 块感觉不太好。

  4. 创建一个自定义特征层次结构来保存数据(例如sealed trait Free; FreeInt(i: Int) extends Free; FreeMap(m: Map[String, Free] extends Free; ...)。现在您可以为其创建特定的编码器/解码器。但是您真正所做的是复制 Circe 中已经存在的 Json 类型层次结构。

我更倾向于选项 3。因为它是最灵活的,并且会在 API 中引入最少的依赖关系。但没有一个是完全令人满意的。还有其他想法吗?

于 2016-08-14T19:44:01.640 回答