Example:
opaque type UserName = String
This version is serialized automatically:
case class UserName(value: String) extends AnyVal
The easiest way is to NOT use raw opaque type
:
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
}