Refined JSON
Making sense of stringly-typed data
- Tim Steinbach
- Senior Software Developer
- Advanced Threat Analytics
- eSentire Inc, Cambridge, ON
- @Tim_Steinbach
- NeQuissimus
{
"ip": "10.0.0.1",
"timestamp": 1432934640
}
final case class Foo(ip: String, timestamp: Long)
{
"ip": "10.0.0.1",
"timestamp": 1432934640
}
final case class Foo private(ip: String, timestamp: Long)
object Foo {
private[this] def isValidIp(s: String): Boolean = ???
def apply(ip: String, timestamp: Long): Option[Foo] =
(ip, timestamp) match {
case (i, t) if isValidIp(i) && t >= 0L => Some(new Foo(i, t))
case _ => None
}
}
refined
fthomas/refined
refined is a Scala library for refining types with type-level predicates which constrain the set of values described by the refined type
{
"ip": "10.0.0.1",
"timestamp": 1432934640
}
import eu.timepit.refined.api.Refined
import eu.timepit.refined.boolean._
import eu.timepit.refined.string._
import eu.timepit.refined.types.numeric.NonNegLong
type IpAddress = String Refined (IPv4 Or IPv6)
type Timestamp = NonNegLong
final case class Foo(ip: IpAddress, timestamp: Timestamp)
import eu.timepit.refined.auto._
val ts1: Timestamp = -42L
// Compile error: Predicate (-42 < 0) did not fail
val ts2: Timestamp = 1432934640L
val ip1: IpAddress = "Hello World"
// Compile error: Both predicates of (Hello World is a valid IPv4 || Hello World is a valid IPv6) failed. [...]
val ip2: IpAddress = "127.0.0.1"
val ip3: IpAddress = "::1"
import eu.timepit.refined.api.Refined
import eu.timepit.refined.boolean._
import eu.timepit.refined.string._
import eu.timepit.refined.types.numeric.NonNegLong
type IpAddress = String Refined (IPv4 Or IPv6)
type Timestamp = NonNegLong
import eu.timepit.refined.api.RefinedTypeOps
object IpAddress extends RefinedTypeOps[IpAddress, String]
IpAddress.from _ // String => Either[String, IpAddress]
NonNegLong.from _ // Long => Either[String, NonNegLong]
import io.circe.generic.auto._
import io.circe.parser.decode
import io.circe.refined._
decode[Foo]("""{"timestamp":123456789,"ip":"127.0.0.1"}""")
// Either[io.circe.Error, Foo] = Right(Foo(127.0.0.1, 123456789))
decode[Foo]("""{"timestamp":123456789,"ip":"hi!"}""")
// Either[io.circe.Error, Foo] = Left(DecodingFailure(Both predicates [...], List(DownField(ip))))
// Refined types in configuration
import eu.timepit.refined.pureconfig._
// Test with inhabitants of refined types
import eu.timepit.refined.scalacheck._
// Refined types in databases
import doobie.refined.implicits._