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._
                

 Thank you

 Questions?