com-lihaoyi / upickle

uPickle: a simple, fast, dependency-free JSON & Binary (MessagePack) serialization library for Scala
https://com-lihaoyi.github.io/upickle
MIT License
721 stars 165 forks source link

Document how to customize discriminator values #595

Closed nafg closed 4 months ago

nafg commented 4 months ago

Took me a while to figure it out. You overwrite annotate methods. Here is an example that uses the unqualified class names instead of fully qualifying them.

  object MyPickle extends upickle.AttributeTagged {
    override def annotate[V](rw: MyPickle.CaseR[V], n: String) = super.annotate(rw, n.split('.').last)
    override def annotate[V](rw: MyPickle.CaseW[V], n: String)(implicit c: ClassTag[V]) =
      super.annotate(rw, n.split('.').last)
  }

I wonder why it isn't the default. After all, the Read/Writers would only be part of some super type. It's unlikely I'm defining my data types like

sealed trait Base
case class Option(value: Int) extends Base
object nested {
  case class Option(value: String) extends Base
}

i.e. having the same unqualified name and needing the fully qualified name to discriminate.

lihaoyi commented 4 months ago

You're generally expected to use the @key annotation for customizing things for now; overriding annotate works too I guess, but it's a bit more advanced a use case

https://github.com/com-lihaoyi/upickle/issues/527 is meant to make what you're trying to do the default

plokhotnyuk commented 4 months ago

@nafg Here is an example of more efficient extraction of class names.

nafg commented 4 months ago

@nafg Here is an example of more efficient extraction of class names.

How is it more efficient? You mean using lastIndex and substring is more performant than split and last?

It's also more robust but neither matter for my use case. But it seems like you agree that overriding annotate is the current way to do it, which is the relevant question.

nafg commented 4 months ago

You're generally expected to use the @key annotation for customizing things for now

But as you know that doesn't do the same thing.

mrdziuban commented 4 months ago

I also was curious how to do this and ended up overriding taggedWrite instead of annotate. It only has one variant so it's a bit less verbose IMO

override def taggedWrite[T, R](w: ObjectWriter[T], tagKey: String, tagValue: String, out: Visitor[?, R], v: T): R =
  super.taggedWrite(w, tagKey, tagValue.split('.').last, out, v)
nafg commented 4 months ago

And for reading? If you only care about writing it's one override either way, but in your case it involves other concerns too

mrdziuban commented 4 months ago

Ah yeah good point, I do indeed only care about writing

lihaoyi commented 4 months ago

Pretty sure this is covered by the documentation on https://com-lihaoyi.github.io/upickle/#CustomConfiguration? It discusses objectTypeKeyReadMap and objectTypeKeyWriteMap, which should be usable to transform the $type value

nafg commented 4 months ago

Indeed, I suppose then that that is the correct way to do it.

However it's not clear from the docs that that's what that does. In fact, when I was trying to figure this out I searched the docs, and I even saw that section (in fact I use a modified SnakePickle in this same codebase), but I didn't realize that this was a solution to the problem I was searching for.