prickle.Pickler.scala Maven / Gradle / Ivy
package prickle
import scala.collection.immutable.SortedMap
import scala.language.experimental.macros
import scala.collection.mutable
import scala.concurrent.duration.Duration
import microjson._
import java.util.Date
import java.util.UUID
/** Use this object to invoke Pickling from user code */
object Pickle {
def apply[A, P](value: A, state: PickleState = PickleState())(implicit p: Pickler[A], config: PConfig[P]): P =
p.pickle(value, state)(config)
def intoString[A](value: A, state: PickleState = PickleState())(implicit p: Pickler[A], config: PConfig[JsValue]): String = {
val pickle = p.pickle(value, state)(config)
Json.write(pickle)
}
def withPickler[A, P](value: A, state: PickleState = PickleState(), p: Pickler[A])(implicit config: PConfig[P]): P =
p.pickle(value, state)(config)
def withConfig[A, P](value: A, state: PickleState = PickleState(), config: PConfig[P])(implicit p: Pickler[A]): P =
p.pickle(value, state)(config)
}
case class PickleState(refs: mutable.Map[Any, String] = mutable.Map.empty, var seq: Int = 0)
/** You should not need to implement this for the supported use cases:
* - Primitives and Strings
* - Case classes and case objects
* - Maps, Sets and Seqs
* - Class-hierarchies supported via composite picklers
* */
trait Pickler[A] {
def pickle[P](obj: A, state: PickleState)(implicit config: PConfig[P]): P
}
/** Do not import this companion object into scope in user code.*/
object Pickler extends MaterializePicklerFallback {
private case class Identity[+A <: AnyRef](obj: A) {
override def equals(that: Any): Boolean = that match {
case that: Identity[_] => this.obj eq that.obj
case _ => false
}
override def hashCode(): Int =
System.identityHashCode(obj)
}
/** Rendered into English:
* Take a `value`, whose fields have been pickled, and the current pickle `state`,
* and do the right thing if shared object support is enabled:
* - if its the first time we've seen this object, give it an id, add it to the pickle state,
* and emit its' pickle normally.
* - if we've seen it before, don't emit a full pickle; substitute a ref to the previous occurence.*/
def resolvingSharing[P](value: AnyRef, fieldPickles: Seq[(String, P)], state: PickleState, config: PConfig[P]): P = {
if (config.areSharedObjectsSupported) {
val key = Identity(value)
state.refs.get(key).fold {
state.seq += 1
state.refs += key -> state.seq.toString
val idKey = config.prefix + "id"
config.makeObject((idKey, config.makeString(state.seq.toString)) +: fieldPickles)
}(
id => config.makeObject(config.prefix + "ref", config.makeString(id))
)
}
else {
config.makeObject(fieldPickles)
}
}
/** As above, but for a collection whose elements are stored in a Json Array*/
def resolvingSharingCollection[P](coll: AnyRef, elems: Iterable[P], state: PickleState, config: PConfig[P]): P = {
def payload = (config.prefix + "elems", config.makeArray(elems.toVector:_*))
if (config.areSharedObjectsSupported) {
val key = Identity(coll)
state.refs.get(key).fold {
state.seq += 1
state.refs += key -> state.seq.toString
val idTag = (config.prefix + "id", config.makeString(state.seq.toString))
config.makeObjectFrom(idTag, payload)
}(
id => config.makeObject(config.prefix + "ref", config.makeString(id))
)
}
else {
config.makeObjectFrom(payload)
}
}
implicit object BooleanPickler extends Pickler[Boolean] {
def pickle[P](x: Boolean, state: PickleState)(implicit config: PConfig[P]): P =
config.makeBoolean(x)
}
implicit object CharPickler extends Pickler[Char] {
def pickle[P](x: Char, state: PickleState)(implicit config: PConfig[P]): P =
config.makeString(x.toString)
}
implicit object BytePickler extends Pickler[Byte] {
def pickle[P](x: Byte, state: PickleState)(implicit config: PConfig[P]): P =
config.makeNumber(x)
}
implicit object ShortPickler extends Pickler[Short] {
def pickle[P](x: Short, state: PickleState)(implicit config: PConfig[P]): P =
config.makeNumber(x)
}
implicit object IntPickler extends Pickler[Int] {
def pickle[P](x: Int, state: PickleState)(implicit config: PConfig[P]): P =
config.makeNumber(x)
}
implicit object LongPickler extends Pickler[Long] {
def pickle[P](x: Long, state: PickleState)(implicit config: PConfig[P]): P = {
config.makeObject(Seq(
("l", config.makeNumber(x.toInt & 0x3fffff)),
("m", config.makeNumber((x >> 22).toInt & 0x3fffff)),
("h", config.makeNumber((x >> 44).toInt))))
}
}
implicit object FloatPickler extends Pickler[Float] {
def pickle[P](x: Float, state: PickleState)(implicit config: PConfig[P]): P =
config.makeNumber(x)
}
implicit object DoublePickler extends Pickler[Double] {
def pickle[P](x: Double, state: PickleState)(implicit config: PConfig[P]): P =
config.makeNumber(x)
}
implicit object StringPickler extends Pickler[String] {
def pickle[P](x: String, state: PickleState)(implicit config: PConfig[P]): P =
config.makeString(x)
}
implicit object UnitPickler extends Pickler[Unit] {
def pickle[P](x: Unit, state: PickleState)(implicit config: PConfig[P]): P =
config.makeString("Unit")
}
implicit object DurationPickler extends Pickler[Duration] {
def pickle[P](x: Duration, state: PickleState)(implicit config: PConfig[P]): P =
LongPickler.pickle(x.toNanos, state)
}
implicit object DatePickler extends Pickler[Date] {
def pickle[P](x: Date, state: PickleState)(implicit config: PConfig[P]): P =
config.makeNumber(x.getTime)
}
implicit object UUIDPickler extends Pickler[UUID] {
def pickle[P](x: UUID, state: PickleState)(implicit config: PConfig[P]): P =
config.makeString(x.toString)
}
implicit def mapPickler[K, V](implicit kpickler: Pickler[K], vpickler: Pickler[V]) = new Pickler[Map[K, V]] {
def pickle[P](value: Map[K, V], state: PickleState)(implicit config: PConfig[P]): P = {
val entries: Iterable[P] = value.map(kv => {
val (k, v) = kv
config.makeArray(Pickle(k, state), Pickle(v, state))
})
resolvingSharingCollection[P](value, entries, state, config)
}
}
implicit def sortedMapPickler[K, V](implicit kpickler: Pickler[K], vpickler: Pickler[V]) = new Pickler[SortedMap[K, V]] {
def pickle[P](value: SortedMap[K, V], state: PickleState)(implicit config: PConfig[P]): P = {
val entries: Iterable[P] = value.map(kv => {
val (k, v) = kv
config.makeArray(Pickle(k, state), Pickle(v, state))
})
resolvingSharingCollection[P](value, entries, state, config)
}
}
implicit def listPickler[T](implicit pickler: Pickler[T]): Pickler[List[T]] = new Pickler[List[T]] {
def pickle[P](value: List[T], state: PickleState)(implicit config: PConfig[P]): P = {
resolvingSharingCollection[P](value, value.map(e => Pickle(e, state)), state, config)
}
}
implicit def vectorPickler[T](implicit pickler: Pickler[T]): Pickler[Vector[T]] = new Pickler[Vector[T]] {
def pickle[P](value: Vector[T], state: PickleState)(implicit config: PConfig[P]): P = {
resolvingSharingCollection[P](value, value.map(e => Pickle(e, state)), state, config)
}
}
implicit def immutableSeqPickler[T](implicit pickler: Pickler[T]) =
new Pickler[collection.immutable.Seq[T]] {
def pickle[P](value: collection.immutable.Seq[T], state: PickleState)(implicit config: PConfig[P]): P = {
resolvingSharingCollection[P](value, value.map(e => Pickle(e, state)), state, config)
}
}
implicit def seqPickler[T](implicit pickler: Pickler[T]) = new Pickler[Seq[T]] {
def pickle[P](value: Seq[T], state: PickleState)(implicit config: PConfig[P]): P = {
resolvingSharingCollection[P](value, value.map(e => Pickle(e, state)), state, config)
}
}
implicit def iterablePickler[T](implicit pickler: Pickler[T]) = new Pickler[Iterable[T]] {
def pickle[P](value: Iterable[T], state: PickleState)(implicit config: PConfig[P]): P = {
resolvingSharingCollection[P](value, value.map(e => Pickle(e, state)).toSeq, state, config)
}
}
implicit def setPickler[T](implicit pickler: Pickler[T]) = new Pickler[Set[T]] {
def pickle[P](value: Set[T], state: PickleState)(implicit config: PConfig[P]): P = {
resolvingSharingCollection[P](value, value.toSeq.map(e => Pickle(e, state)), state, config)
}
}
implicit def optionPickler[T](implicit pickler: Pickler[T]): Pickler[Option[T]] = new Pickler[Option[T]] {
def pickle[P](value: Option[T], state: PickleState)(implicit config: PConfig[P]): P = {
resolvingSharingCollection[P](value, value.map(e => Pickle(e, state)).toSeq, state, config)
}
}
implicit def toPickler[A <: AnyRef](implicit pair: PicklerPair[A]): Pickler[A] = pair.pickler
}
trait MaterializePicklerFallback {
implicit def materializePickler[T]: Pickler[T] =
macro PicklerMaterializersImpl.materializePickler[T]
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy