skip to Main Content

Example:

opaque type UserName = String

This version is serialized automatically:

case class UserName(value: String) extends AnyVal

2

Answers


  1. Chosen as BEST ANSWER

    I don't know if it is the most elegant way of doing it, because I'm novice in circe:

    opaque type UserName = String
    
    object UserName:
      def apply(s: String): UserName = new UserName(s)
      given Encoder[UserName] = new Encoder[UserName]:
        def apply(a: UserName): Json = Json.fromString(a.toString)
      given Decoder[UserName] = new Decoder[UserName]:
        def apply(c: HCursor): Decoder.Result[UserName] =
          c.as[String].map{ UserName(_)}
    
    

  2. The easiest way is to NOT use raw opaque type:

    1. use Monix Newtypes, Iron, Neotype, Refined4s, …
    2. these libraries have integrations e.g. for Circe:
    1. if they do not have an integration for a particular library… then they are exposing type classes which allow converting type classes for underlying types into wrapper types

    The mechanism for all of them is the same:

    type MyType = MyType.Type
    object MyType {
      opaque type Type = UnderlyingType
      // here code knows that Type = UnderlyingType
    
      // factories, extension methods, instances
    }
    
    // here code asking for MyType, resolves it to MyType.Type, then implicit
    // resolution would look inside object MyType for implicits
    

    it’s just the common content is extracted into a mixin trait

    type MyType = MyType.Type
    object MyType extends Newtype[UnderlyingType] {
      // custom stuff
    }
    

    which would provide some instance of ConvertToAndFrom[Inner, Outer] (sometimes split into 2 type classes, 1 for extraction and 1 for construction, details depends on the library).

    It saves unnecessary burden of writing something like:

    // givens
    object namespace {
      opaque type MyType = String
      // opaque type (just like normal type alias)
      // CANNOT have a companion object, so putting implicits/givens into
      // object MyType will NOT automatically import them.
      // (Meaning you'd have to import MyType.given every time you need instances.)
      //
      // BUT putting things into top level `object` WILL pull implicits
      // in this object into implicit scope for opaque type defined in the same object.
      // Which is a trick used by all "newtypes| libraries.
      given Encoder[MyType] = Encoder.encodeString
      given DecoderMyType]  = Decoder.decodeString
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search