【问题标题】:Endpoint receiving a list of objects from a hierarchy端点从层次结构中接收对象列表
【发布时间】:2021-05-12 09:31:23
【问题描述】:

Akka Http 中的端点如下所示:

pathPrefix("somePath" / Segment) { someData =>
  post {
    entity(as[SMS]) { sms =>
      // some code here ...
      complete(StatusCodes.OK)
    }
  }
}

而短信定义为:

sealed trait Message
case class SMS(numFrom: String, message:String) extends Message
case class Email(emailFrom: String, message: String) extends Message

如果我想收到SMS 的列表,我可以执行以下操作:

type SMSList = List[SMS]
...

pathPrefix("somePath" / Segment) { someData =>
  post {
    entity(as[SMSList]) { listOfSMSs =>
      // some code here ...
      complete(StatusCodes.OK)
    }
  }
}

如果我想同时接收短信和电子邮件列表怎么办? 这个我试过了,还是不行:

type MessageList = List[Message]

pathPrefix("somePath" / Segment) { someData =>
  post {
    entity(as[MessageList]) { listOfMessages =>
      // some code here ...
      complete(StatusCodes.OK)
    }
  }
}

是否可以接收属于同一层次结构的对象列表?

图书馆:

circe = 0.13.0
heikoseeberger = 1.35.3
akka http = 10.2.3

json:

[ 
  {"numForm": "123 456", "message": "sms message"},
  {"emailFrom": "some@mail.com", "email message"}
]

【问题讨论】:

  • 糟糕,我忘了提到库:circe(0.13.0) 和 heikoseeberger (1.35.3),akka http (10.2.3)
  • 这对您有帮助吗? stackoverflow.com/q/50457466/2359227
  • 谢谢Tomer,但我认为这是一个不同的问题。在您的代码中,您会看到 ""{ \n "Something": ... }。这是一个不同的输入,我没有字符串“Something”。我已经用调用的 json 更新了文本。如果我错了,请纠正我

标签: scala akka json-deserialization akka-http circe


【解决方案1】:

假设您在 akka-http - spray-json 中使用默认的一个 json 序列化库 - 您在组合多个 json 阅读器时受到很大限制(根据 official page 和源代码)。您能做的最好的事情可能是为Message 手动编写一些格式化程序(或只是阅读器)。

import spray.json.DefaultJsonProtocol._
import spray.json._

implicit val smsFormat: JsonFormat[SMS] = jsonFormat2(SMS)
implicit val emailFormat: JsonFormat[Email] = jsonFormat2(Email)

implicit val messageFormat: JsonFormat[Message] = new JsonFormat[Message] {
  override def read(json: JsValue): Message = json match {
    case sms@JsObject(_) if sms.fields.contains("numFrom") => smsFormat.read(sms)
    case email@JsObject(_) if email.fields.contains("emailFrom") => emailFormat.read(email)
    case _ => deserializationError("object expected")
  }

  override def write(obj: Message): JsValue = obj match {
    case sms: SMS => sms.toJson
    case email: Email => email.toJson
    case _ => throw new RuntimeException("Houston, we have a problem")
  }
}

我还建议您查看circe 库,它的代码更加可组合。它也很容易与 akka-http 集成。


更新1:(指定确切的库后):

有几个选项:

  1. 结合几个解码器

      import io.circe.generic.auto._
      import io.circe.{Decoder, HCursor}
    
      class CirceExample extends App {
    
        sealed trait Message
    
        case class SMS(numFrom: String, message: String) extends Message
    
        case class Email(emailFrom: String, message: String) extends Message
    
        val smsDecoder = implicitly[Decoder[SMS]]
        val emailDecoder = implicitly[Decoder[Email]]
    
        val messageDecoder: Decoder[Message] = (c: HCursor) => smsDecoder(c).orElse(emailDecoder(c))
      }
    

    这很容易实现,因为Decoder的解码结果是Either

  2. 创建自定义解码器,显式检查所需字段 - documentation。这种方法有点类似于前面的 spray-json 示例。


更新 2:

import cats.syntax.functor._
import io.circe.Decoder
import io.circe.generic.auto._
import io.circe.parser._

object CirceExample extends App {

  sealed trait Message

  case class SMS(numFrom: String, message: String) extends Message

  case class Email(emailFrom: String, message: String) extends Message

  implicit val messageDecoder: Decoder[Message] = List[Decoder[Message]](Decoder[SMS].widen, Decoder[Email].widen).reduceLeft(_ or _)
  // or without list..
  //implicit val messageDecoder: Decoder[Message] = Decoder[SMS].widen or Decoder[Email].widen

  val payload = """[{"emailFrom":"a","message":"b"}]"""
  val result = decode[List[Message]](payload)
  println(result)
}

【讨论】:

  • 我忘了说我使用的是哪些库,实际上我使用的是circe
  • 我无法让它工作。我将代码修改为我的示例,我创建了带有编码器的对象 Message,导入了它,但它没有工作......
  • 它应该可以工作,因为 circe 中的解码器是可组合的。也就是说,如果您声明 List[Message],circe 将使用现有的预定义解码器作为列表,并为 Message 搜索隐式解码器(或者您可以显式指定所有解码器)。您应该检查解码器的分辨率。在 Intellij Idea IDE 中,您可以通过“查看”->“显示隐式提示”菜单执行此操作。如果您在此处发布错误将会很有帮助。
  • @M.G.好的,你是对的,它不起作用。它的详细信息(实际上您的案例在这里stackoverflow.com/questions/42165460/…)。添加了有关其外观的更新。请注意明确指定您将使用哪些解码器。
  • 哦,第二次更新真是太棒了!非常感谢!!
【解决方案2】:

这里有几个步骤,所以我会尽量保持简单。

首先,我们需要为EmailSMS定义编码器和解码器:

sealed trait Message
@JsonCodec case class SMS(numFrom: String, message:String) extends Message
@JsonCodec case class Email(emailFrom: String, message: String) extends Message

object Message {
  implicit val decodeA: Decoder[Message] = Decoder[SMS].map[Message](identity).or(Decoder[Email].map[Message](identity))
  implicit val encodeA: Encoder[Message] = Encoder.instance {
    case b @ SMS(_, _) => b.asJson
    case c @ Email(_, _) => c.asJson
  }
}

我在this 链接中找到了这个,此前该线程的所有尝试均无效。

让我们继续。我们必须在一个单独的模块中找到它。否则,我们会得到enable macro paradise to expand macro annotations 错误。在阅读了this 的帖子后,基本上解释了不能在同一个模块中声明和使用宏,我将上面的代码移到另一个模块中,这使得代码可以编译。

用上面的代码有了一个单独的模块后,我们需要定义一个新的模块,来依赖第一个。

现在,在最后一个模块中,我们需要先导入:

import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport.unmarshaller

然后我们可以创建一个路由:

val routes = pathPrefix("somePath" / Segment) { someData =>
  post {
    entity(as[List[Message]]) { listOfMessages =>
      // some code here ...
      complete(StatusCodes.OK)
    }
  }
}

【讨论】:

  • 我试过你的代码,但它没有编译,我必须为 SMS 和 Email 案例类定义隐式编解码器。然后它工作正常,但只接受一个对象,它不适用于“entity(as[List[Message]]) { ...”。除此之外,这是一个很好的代码
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-24
  • 1970-01-01
  • 2011-09-26
  • 1970-01-01
  • 1970-01-01
  • 2014-05-20
  • 1970-01-01
相关资源
最近更新 更多