为任意JSON创建一个“解码器”

时间:2016-06-18 00:16:20

标签: scala graphql finch circe sangria

我正在使用Finch,Circe和Sangria为API构建GraphQL端点。在GraphQL查询中遇到的variables基本上是一个任意的JSON对象(我们假设没有嵌套)。例如,在我的测试代码String中,这里有两个例子:

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

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

我尝试了很多方法,但到目前为止还未能在Circe中为此写Decoder。任何帮助表示赞赏。

3 个答案:

答案 0 :(得分:5)

  

Sangria API需要Map [String,Any]

的类型

事实并非如此。在sangria中执行的变量可以是任意类型getLocation: 150.0 100.0 getLocationOnScreen: 150.0 100.0 ,这是唯一要求它具有T类型类实例的要求。所有编组集成库都为对应的JSON AST类型提供InputUnmarshaller[T]的实例。

这意味着sangria-circe定义了InputUnmarshaller,您可以使用InputUnmarshaller[io.circe.Json]导入它。

以下是一个小型且自包含的示例,说明如何将circe import sangria.marshalling.circe._用作变量:

Json

正如您在此示例中所看到的,我使用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) 作为执行变量。执行将产生以下结果JSON:

io.circe.Json

答案 1 :(得分: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,然后相当简单地转换对象中的值。

答案 2 :(得分:0)

虽然上述答案适用于桑格利亚的具体情况,但我对原始问题感兴趣:什么是Circe的最佳方法(通常假设所有类型都是预先知道的)任意大块的Json?

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

我玩过的解决方案:

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

  2. 将自由格式块编码/解码为Map[String, Json]。这是最简单的方法,并且在Circe中开箱即用。但是现在Json序列化逻辑已泄露到你的API中(通常你会想要保持Json的东西完全被包装,所以你可以在以后交换其他非json格式。)

  3. String进行编码/解码,其中字符串必须是有效的Json块。至少你还没有将你的API锁定到一个特定的Json库中,但是要让用户以这种手动方式创建Json块并不是很好。

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

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