3

Finch、Circe、Sangria を使用して、API の GraphQL エンドポイントを構築しています。GraphQLvariablesクエリで取得される は、基本的に任意の JSON オブジェクトです (ネストがないと仮定しましょう)。たとえば、Strings としての私のテスト コードでは、2 つの例を次に示します。

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

Sangria API は、これらの の型を想定していますMap[String, Any]

私はたくさんの方法を試しましたが、これまでのところDecoderCirce でこれを書くことができませんでした。どんな助けでも感謝します。

4

3 に答える 3

6

Sangria API は、これらの Map[String, Any] の型を想定しています。

本当じゃない。サングリアで実行するための変数は、任意の型にすることができます。これは、型クラスTのインスタンスを持っているという唯一の要件です。InputUnmarshaller[T]すべてのマーシャリング統合ライブラリはInputUnmarshaller、対応する JSON AST タイプのインスタンスを提供します。

これは、sangria-circeが定義しInputUnmarshaller[io.circe.Json]、 でインポートできることを意味しますimport sangria.marshalling.circe._

Json以下は、変数としてcirce を使用する方法の小さな自己完結型の例です。

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 の特定のケースで機能しますが、元の質問に興味があります: Json の任意のチャンクを処理するための Circe での最良のアプローチは何ですか?

Json をエンコード/デコードするときに、Json の 95% が指定されるのはかなり一般的ですが、最後の 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 型階層を複製することです。

私はオプション 3 に傾倒しています。これは最も柔軟性が高く、API の依存関係が最も少ないためです。しかし、どれも完全に満足できるものではありません。他のアイデアはありますか?

于 2016-08-14T19:44:01.640 に答える