7

I have the following Input Objects:

val BusinessInputType = InputObjectType[BusinessInput]("BusinessInput", List(
    InputField("userId", StringType),
    InputField("name", StringType),
    InputField("address", OptionInputType(StringType)),
    InputField("phonenumber", OptionInputType(StringType)),
    InputField("email", OptionInputType(StringType)),
    InputField("hours", ListInputType(BusinessHoursInputType))

  ))


 val BusinessHoursInputType = InputObjectType[BusinessHoursInput]("hours",  List(
    InputField("weekDay", IntType),
    InputField("startTime", StringType),
    InputField("endTime", StringType)
  ))

And here are my models with custom Marshalling defined:

case class BusinessInput(userId: String, name: String, address: Option[String], phonenumber: Option[String], email: Option[String], hours: Seq[BusinessHoursInput])

object BusinessInput {

  implicit val manual = new FromInput[BusinessInput] {
    val marshaller = CoercedScalaResultMarshaller.default

    def fromResult(node: marshaller.Node) = {
      val ad = node.asInstanceOf[Map[String, Any]]

      System.out.println(ad)
      BusinessInput(
        userId = ad("userId").asInstanceOf[String],
        name = ad("name").asInstanceOf[String],
        address = ad.get("address").flatMap(_.asInstanceOf[Option[String]]),
        phonenumber = ad.get("phonenumber").flatMap(_.asInstanceOf[Option[String]]),
        email = ad.get("email").flatMap(_.asInstanceOf[Option[String]]),
        hours = ad("hours").asInstanceOf[Seq[BusinessHoursInput]]

      )
    }
  }
}



case class BusinessHoursInput(weekDay: Int, startTime: Time, endTime: Time)

object BusinessHoursInput {

  implicit val manual = new FromInput[BusinessHoursInput] {
    val marshaller = CoercedScalaResultMarshaller.default
    def fromResult(node: marshaller.Node) = {
      val ad = node.asInstanceOf[Map[String, Any]]
      System.out.println("HEY")

      BusinessHoursInput(
        weekDay = ad("weekDay").asInstanceOf[Int],
        startTime = Time.valueOf(ad("startTime").asInstanceOf[String]),
        endTime = Time.valueOf(ad("endTime").asInstanceOf[String])
      )
    }
  }


}

My question is, When I have a nested InputObject that has custom Marshalling, I dont see the marshalling of BusinessHoursInput getting invoked before the BusinessInput is marshalled. I noticed this because the print statement of "Hey" is never executed before the print statement of "ad" in BusinessInput. This causes problems later down the road for me when I try to insert the hours field of BusinessInput in the DB because it cannot cast it to BusinessHoursInput object. In Sangria, is it not possible to custom Marshal nested Objects before the parent Object is marshalled?

john
  • 1,057
  • 1
  • 17
  • 28

2 Answers2

5

You are probably are using BusinessInput as an argument type. The actual implicit lookup takes place at the Argument definition time and only for BusinessInput type.

Since FromInput is a type-class based deserialization, you need to explicitly define the dependency between deserializers of nested object. For example, you can rewrite the deserializer like this:

case class BusinessInput(userId: String, name: String, address: Option[String], phonenumber: Option[String], email: Option[String], hours: Seq[BusinessHoursInput])

object BusinessInput {
  implicit def manual(implicit hoursFromInput: FromInput[BusinessHoursInput]) = new FromInput[BusinessInput] {
    val marshaller = CoercedScalaResultMarshaller.default

    def fromResult(node: marshaller.Node) = {
      val ad = node.asInstanceOf[Map[String, Any]]

      BusinessInput(
        userId = ad("userId").asInstanceOf[String],
        name = ad("name").asInstanceOf[String],
        address = ad.get("address").flatMap(_.asInstanceOf[Option[String]]),
        phonenumber = ad.get("phonenumber").flatMap(_.asInstanceOf[Option[String]]),
        email = ad.get("email").flatMap(_.asInstanceOf[Option[String]]),
        hours = hoursFromInput.fromResult(ad("hours").asInstanceOf[Seq[hoursFromInput.marshaller.Node]])
      )
    }
  }
}

In this version, I'm taking advantage of existing FromInput[BusinessHoursInput] to deserialize BusinessHoursInput from the raw input.

Also as an alternative, you can avoid defining manual FromInput deserializers altogether by taking advantage of existing JSON-based deserializers. For example, in most cases, circe's automatic derivation works just fine. You just need these 2 imports (in the file where you are defining the arguments):

import sangria.marshalling.circe._
import io.circe.generic.auto._

Those import put appropriate FromInput instances into the scope. These instances take advantage of circe's own deserialization mechanism.

tenshi
  • 26,268
  • 8
  • 76
  • 90
  • ```hours = hoursFromInput.fromResult(ad("hours").asInstanceOf[Seq[hoursFromInput.marshaller.Node]])``` This does not seem to work. It says, ```type mismatch, expected hoursFromInput.marshaller.Node but found Seq[hoursFromInput.marshaller.Node]```. This is obvious as fromResult of BusinessHoursInput takes only Node. What am I missing ? – Pritam Mar 28 '19 at 05:35
  • On exact same situation, where nested objects are not getting deserialized. @tenshi Any example to see how the nested deserializer works. Thanks. – Pritam Mar 28 '19 at 05:38
0
import io.circe.Decoder
import io.circe.generic.semiauto.deriveDecoder
import sangria.macros.derive.deriveInputObjectType
import sangria.marshalling.circe._
import sangria.schema.{Argument, InputObjectType}


object XXX {

  // when you have FromInput for all types in case class (Int, String) you can derive it
  case class BusinessHoursInput(weekDay: Int, startTime: String, endTime: String)

  object BusinessHoursInput {
    implicit val decoder: Decoder[BusinessHoursInput] = deriveDecoder
    implicit val inputType: InputObjectType[BusinessHoursInput] = deriveInputObjectType[BusinessHoursInput]()
  }

  // the same here, you need InputObjectType also for BusinessHoursInput
  case class BusinessInput(userId: String, name: String, address: Option[String], phonenumber: Option[String], email: Option[String], hours: Seq[BusinessHoursInput])

  object BusinessInput {
    implicit val decoder: Decoder[BusinessInput] = deriveDecoder
    implicit val inputType: InputObjectType[BusinessInput] = deriveInputObjectType[BusinessInput]()
  }

  // for this to work you need to have in scope InputType BusinessInput and FromInput for BusinessInput
  // FromInput you can get by having Decoder in scope and import sangria.marshalling.circe._
  private val businessInputArg = Argument("businessInput", BusinessInput.inputType)


}

id you do not use circe but different json library you should have of course different typeclasses and proper import in scope

user1698641
  • 211
  • 1
  • 12