Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Marshalling nested custom Objects in Sangria

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?

like image 848
john Avatar asked Aug 25 '17 16:08

john


1 Answers

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.

like image 162
tenshi Avatar answered Sep 25 '22 09:09

tenshi