scala的Circe解码器.也许 [英] Circe decoder for scalaz.Maybe

查看:94
本文介绍了scala的Circe解码器.也许的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个简单的芬奇服务器,使用circe作为解码器:

Here's a simple finch server, using circe as decoder:

import com.twitter.finagle.http.RequestBuilder
import com.twitter.io.Buf
import io.circe.generic.auto._
import io.finch._
import io.finch.circe._

case class Test(myValue: Int)

val api = post("foo" :: body.as[Test]) { test: Test => Ok(test) }

val bodyPost = RequestBuilder()
  .url("http://localhost:8080/foo")
  .buildPost(Buf.Utf8("""{ "myValue" : 42 }"""))

api.toService.apply(bodyPost).onSuccess { response =>
  println(s"$response: ${response.contentString}")
}

// output: Response("HTTP/1.1 Status(200)"): {"myValue":42}

myValue更改为Option即可使用,其结果与上述代码相同.但是,将其更改为scalaz.Maybe:

Changing the myValue into an Option works out of the box, giving the same result as above code. However, changing it into a scalaz.Maybe:

import scalaz.Maybe
case class Test(myValue: Maybe[Int])

导致:

Response("HTTP/1.1 Status(400)"):{"message":正文无法转换 要测试:CNil:El(DownField(myValue),true,false).}

Response("HTTP/1.1 Status(400)"): {"message":"body cannot be converted to Test: CNil: El(DownField(myValue),true,false)."}

我应该如何实现所需的编码器/解码器?

How should I implement the needed encoder/decoder?

推荐答案

这是一种略有不同的方法:

Here's a slightly different approach:

import io.circe.{ Decoder, Encoder }
import scalaz.Maybe

trait ScalazInstances {
  implicit def decodeMaybe[A: Decoder]: Decoder[Maybe[A]] =
    Decoder[Option[A]].map(Maybe.fromOption)

  implicit def encodeMaybe[A: Encoder]: Encoder[Maybe[A]] =
    Encoder[Option[A]].contramap(_.toOption)
}

object ScalazInstances extends ScalazInstances

然后:

scala> import scalaz.Scalaz._, ScalazInstances._
import scalaz.Scalaz._
import ScalazInstances._

scala> import io.circe.parser.decode, io.circe.syntax._
import io.circe.parser.decode
import io.circe.syntax._

scala> Map("a" -> 1).just.asJson.noSpaces
res0: String = {"a":1}

scala> decode[Maybe[Int]]("1")
res1: Either[io.circe.Error,scalaz.Maybe[Int]] = Right(Just(1))

此实现的主要优点(除了它更通用,甚至更简洁之外)是,它具有在case类中通常期望的可选成员的行为.例如,在您的实施中,以下输入将失败:

The main advantage of this implementation (apart from the fact that it's more generic and even a little more concise) is that it has the behavior you generally expect for optional members in case classes. With your implementation, for example, the following inputs fail:

scala> import io.circe.generic.auto._
import io.circe.generic.auto._

scala> case class Foo(i: Maybe[Int], s: String)
defined class Foo

scala> decode[Foo]("""{ "s": "abcd" }""")
res2: Either[io.circe.Error,Foo] = Left(DecodingFailure(Attempt to decode value on failed cursor, List(DownField(i))))

scala> decode[Foo]("""{ "i": null, "s": "abcd" }""")
res3: Either[io.circe.Error,Foo] = Left(DecodingFailure(Int, List(DownField(i))))

如果您使用上面的仅将解码器委托给Option解码器的解码器,它们将被解码为Empty:

While if you use the decoder above that just delegates to the Option decoder, they get decoded to Empty:

scala> decode[Foo]("""{ "s": "abcd" }""")
res0: Either[io.circe.Error,Foo] = Right(Foo(Empty(),abcd))

scala> decode[Foo]("""{ "i": null, "s": "abcd" }""")
res1: Either[io.circe.Error,Foo] = Right(Foo(Empty(),abcd))

当然,您是否要执行此操作取决于您,但这是大多数人可能希望Maybe编解码器提供的功能.

Whether you want this behavior or not is up to you, of course, but it's what most people are likely to expect from a Maybe codec.

我的解码器的一个缺点(在某些非常特殊的情况下)是,它为每个成功解码的值实例化一个额外的Option.如果您非常担心分配(或者您只是对这些东西的工作方式感到好奇,这可能是一个更好的理由),则可以基于circe decodeOption:

One disadvantage (in some very specific cases) of my decoder is that it instantiates an extra Option for every successfully decoded value. If you're extremely concerned about allocations (or if you're just curious about how this stuff works, which is probably a better reason), you can implement your own based on circe's decodeOption:

import cats.syntax.either._
import io.circe.{ Decoder, DecodingFailure, Encoder, FailedCursor, HCursor }
import scalaz.Maybe

implicit def decodeMaybe[A](implicit decodeA: Decoder[A]): Decoder[Maybe[A]] =
  Decoder.withReattempt {
    case c: HCursor if c.value.isNull => Right(Maybe.empty)
    case c: HCursor => decodeA(c).map(Maybe.just)
    case c: FailedCursor if !c.incorrectFocus => Right(Maybe.empty)
    case c: FailedCursor => Left(DecodingFailure("[A]Maybe[A]", c.history))
  }

Decoder.withReattempt部分是使我们能够将{}之类的内容解码为case class Foo(v: Maybe[Int])并按预期获得Foo(Maybe.empty)的魔力.这个名称有点令人困惑,但是真正的含义是即使最后一个操作失败,也要应用此解码操作".在解析的上下文中对于像case class Foo(v: Maybe[Int])这样的case类,最后的操作将是尝试在JSON对象中选择"v"字段.如果没有"v"键,通常情况就这样了-我们的解码器甚至都不会被应用,因为没有什么可应用的. withReattempt允许我们继续解码.

The Decoder.withReattempt part is the magic that allows us to decode something like {} into a case class Foo(v: Maybe[Int]) and get Foo(Maybe.empty) as expected. The name is a little confusing, but what it really means is "apply this decoding operation even if the last operation failed". In the context of parsing e.g. a case class like case class Foo(v: Maybe[Int]), the last operation would be the attempt to select a "v" field in the JSON object. If there's no "v" key, normally that would be the end of the story—our decoder wouldn't even be applied, because there's nothing to apply it to. withReattempt allows us to continue decoding anyway.

此代码是非常底层的代码,DecoderHCursor API的这些部分旨在提高效率,而不是为了用户友好,但是如果您盯着它看,仍然可以知道发生了什么.如果最后一个操作没有失败,我们可以检查当前JSON值是否为null,如果是,则返回Maybe.empty.如果不是,我们尝试将其解码为A,如果成功,则将结果包装在Maybe.just中.如果最后一个操作失败,则我们首先检查该操作和最后一个焦点是否不匹配(由于某些奇怪的极端情况,这是必要的细节,请参阅我的建议错误报告以获取详细信息).如果不是的话,我们将一无所获.如果它们不匹配,我们将失败.

This code is pretty low-level and these parts of the Decoder and HCursor APIs are designed more for efficiency than for user-friendliness, but it's still possible to tell what's going on if you stare at it. If the last operation didn't fail, we can check whether the current JSON value is null and return Maybe.empty if it is. If it's not, we try to decode it as an A and wrap the result in Maybe.just if it succeeds. If the last operation failed, we first check whether the operation and the last focus are mismatched (a detail that's necessary because of some weird corner cases—see my proposal here and the linked bug report for details). If they're not, we succeed emptily. If they are mismatched, we fail.

同样,您几乎可以肯定不应该使用该版本-映射Decoder[Option[A]]更加清晰,更可靠,更可靠,并且效率略低.无论如何,了解withReattempt还是有用的.

Again, you almost certainly shouldn't use this version—mapping over the Decoder[Option[A]] is clearer, more future-proof, and only very slightly less efficient. Understanding withReattempt can be useful anyway, though.

这篇关于scala的Circe解码器.也许的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆