2016-06-18 8 views
3

私はFinch、Circe、Sangriaを使ってAPI用のGraphQLエンドポイントを構築しています。 GraphQLクエリーに含まれるvariablesは、基本的には任意のJSONオブジェクトです(入れ子がないものとしましょう)。任意のJSONのための `Decoder`を作成する

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

サングリアのAPIがMap[String, Any]のこれらの型を想定していますので、例えば、String Sとしての私のテストコードでは、ここでは2つの例です。

私はたくさんの方法を試しましたが、今のところCirceではDecoderと書くことができませんでした。どんな助けもありがたい。

答えて

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) 
    } 
} 

私たちは、基本的には、その後かなり単純化してオブジェクト内の値を変換し、JsonObjectにそれを回す、JSONを解析。

5

サングリアのAPIは、地図のこれらの型を期待[文字列、任意]

これは真実ではありません。 sangriaでの実行の変数は、任意のタイプのTであることができます。唯一の要件は、InputUnmarshaller[T]のクラスのインスタンスです。すべてのマーシャリング統合ライブラリは、対応するJSON ASTタイプに対してのインスタンスを提供します。

これはsangria-circeInputUnmarshaller[io.circe.Json]を定義し、import sangria.marshalling.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 
    } 
} 
+0

ありがとう@tenshi、それは知っておくと良いです。 –

0

:実行は、次の結果JSONを生成しますJsonの任意のチャンクに対処するために前に知られていますか?

Jsonの95%が指定されているが、最後の5%はJsonオブジェクトでもよい何らかのタイプの "追加のプロパティ"のチャンクです。私がプレイした

ソリューション:

  1. エンコード/ Map[String,Any]として自由形式のチャンクをデコードします。これは、Map[String, Any]の暗黙的なエンコーダ/デコーダを導入する必要があることを意味しますが、意図していない場所に暗黙的に引き込むことができるので危険です。

  2. フリーフォームチャンクをMap[String, Json]としてエンコード/デコードします。これは最も簡単なアプローチであり、Circeの箱からすぐにサポートされています。しかし今、Jsonのシリアル化ロジックがAPIに漏れています(多くの場合、Jsonの内容は完全にラップされているので、後で他の非json形式でスワップすることができます)。

  3. Stringにエンコード/デコードします。ここで、文字列は有効なJsonチャンクである必要があります。少なくともあなたは特定のJsonライブラリにあなたのAPIをロックしていませんが、この手作業でJsonのチャンクを作成するようにユーザーに依頼するのは非常に気になりません。

  4. データを保持するカスタム特性階層を作成します(例:sealed trait Free; FreeInt(i: Int) extends Free; FreeMap(m: Map[String, Free] extends Free; ...)。これで、特定のエンコーダ/デコーダを作成できます。しかし、あなたが実際に行ったことは、すでにCirceに存在するJson型の階層構造を複製することです。

私はオプション3に向けて傾いています。これは最も柔軟性があり、APIの依存性を最小限に抑えるためです。しかし、どれも完全に満足できるものではありません。他のアイデア?

関連する問題