Combining type and field serializers
Asked Answered
K

2

12

Let's assume I have a case class with the following setup:

case class Place(id:java.util.UUID, name:String)

I can write a (working!) serializer for this type as follows:

class placeSerializer extends CustomSerializer[Place]( format => (
        {
            case JObject(JField("id", JString(s)) :: JField("name",JString(x)) :: Nil ) =>
                Place(UUID.fromString(s), x)
        },
        {
            case x:Place =>
                JObject(
                  JField("id", JString(x.id.toString())) :: 
                  JField("name", JString(x.name)) :: Nil)
        }
        )
    )

But assuming my case class eventually has a lot more fields, this could lead to me enumerating the entire structure of the object with the AST, creating something that's very verbose just to encode primitives.

json4s appears to have field serializers that can act only on specific fields, with boilerplate methods included to easily transform names and discard fields. These, however, have the following signature for their serialize and deserialize partial functions:

case class FieldSerializer[A: Manifest](
  serializer:   PartialFunction[(String, Any), Option[(String, Any)]] = Map(),
  deserializer: PartialFunction[JField, JField] = Map()
)

Since JField (the type that representes a key -> val from the json) is its own type and not a subclass of JValue, how can I combine these two types of serializers to properly encode the id key by its name to a UUID, while maintaining the default handling of the other fields (which are primitive datatypes).

Essentially I'd like a format chain that understands the field within Place is a UUID, without having to specify AST structure for all the fields that DefaultFormats can already handle.

What I'm looking for specifically is to mimic a pattern similar to the JSONEncoder and JSONDecoder interfaces in python, which can use the key name as well as value type to determine how to handle the marshalling for the field.

Khufu answered 24/4, 2014 at 19:8 Comment(1)
check also #23978279 for the UUID specific issueQuark
Q
7

The trick is to not write a serializer for your type, but for the type that you're using inside (in this case java.util.UUID)

Then you can add that serializer to the toolbox and from then any type using UUID will work exactly like types using DefaultSerializer supported fields did:

case object UUIDSerialiser extends CustomSerializer[UUID](format => (
    {
      case JString(s) => UUID.fromString(s)
      case JNull => null
    },
    {
      case x: UUID => JString(x.toString)
    }
  )
)

implicit val json4sFormats = Serialization.formats(NoTypeHints) + UUIDSerialiser

Update link to the PR

Update 2 the PR was merged, and now, in case of UUID you can use:

import org.json4s.ext.JavaTypesSerializers

implicit val json4sFormats = Serialization.formats(NoTypeHints) ++ JavaTypesSerializers.all
Quark answered 1/6, 2014 at 8:7 Comment(1)
Thanks @iwein! Still getting used to the magic of implicits... hard to break old imperative habits.Khufu
L
11

There is now a UUID serializer provided in the extras package of json4s. It will most likely be available in version 3.2.11 (which has not been released as of this writing).

You'll be able to do something like this:

import org.json4s.ext.JavaTypesSerializers

implicit val json4sFormats = Serialization.formats(NoTypeHints) ++ JavaTypesSerializers.all

This was taken from the tests for this feature's PR.

Lillith answered 26/8, 2014 at 17:37 Comment(2)
I love it when my answers get outdated by someone merging my pull requests :) +1 for the good find, I hope you don't mind I updated my answer to reflect the new reality.Quark
btw, the link to the test is broken, you could just link to the pull request like I did: github.com/json4s/json4s/pull/130/filesQuark
Q
7

The trick is to not write a serializer for your type, but for the type that you're using inside (in this case java.util.UUID)

Then you can add that serializer to the toolbox and from then any type using UUID will work exactly like types using DefaultSerializer supported fields did:

case object UUIDSerialiser extends CustomSerializer[UUID](format => (
    {
      case JString(s) => UUID.fromString(s)
      case JNull => null
    },
    {
      case x: UUID => JString(x.toString)
    }
  )
)

implicit val json4sFormats = Serialization.formats(NoTypeHints) + UUIDSerialiser

Update link to the PR

Update 2 the PR was merged, and now, in case of UUID you can use:

import org.json4s.ext.JavaTypesSerializers

implicit val json4sFormats = Serialization.formats(NoTypeHints) ++ JavaTypesSerializers.all
Quark answered 1/6, 2014 at 8:7 Comment(1)
Thanks @iwein! Still getting used to the magic of implicits... hard to break old imperative habits.Khufu

© 2022 - 2024 — McMap. All rights reserved.