diff --git a/enumeratum-json4s/src/main/scala/enumeratum/Json4s.scala b/enumeratum-json4s/src/main/scala/enumeratum/Json4s.scala index 051be275..5b95d763 100644 --- a/enumeratum-json4s/src/main/scala/enumeratum/Json4s.scala +++ b/enumeratum-json4s/src/main/scala/enumeratum/Json4s.scala @@ -1,6 +1,6 @@ package enumeratum -import org.json4s.CustomSerializer +import org.json4s.{CustomSerializer, CustomKeySerializer} import org.json4s.JsonAST.JString @SuppressWarnings(Array("org.wartremover.warts.Any")) @@ -44,4 +44,43 @@ object Json4s { } )) + /** + * Returns a Json [[CustomKeySerializer]] for the given Enumeratum enum + * + * {{{ + * scala> import enumeratum._ + * scala> import org.json4s._ + * scala> import org.json4s.native.Serialization + * + * scala> sealed trait ShirtSize extends EnumEntry + * scala> case object ShirtSize extends Enum[ShirtSize] { + * | case object Small extends ShirtSize + * | case object Medium extends ShirtSize + * | case object Large extends ShirtSize + * | val values = findValues + * | } + * + * scala> implicit val formats = Serialization.formats(NoTypeHints) + Json4s.keySerializer(ShirtSize) + * + * scala> val valueMap = ShirtSize.values.zipWithIndex.toMap + * + * scala> Serialization.write(valueMap) + * res0: String = {"Small":0,"Medium":1,"Large":2} + * + * scala> Serialization.read[Map[ShirtSize,Int]]("""{"Small":0,"Medium":1,"Large":2}""") == valueMap + * res1: Boolean = true + * }}} + * + * @param enum the enum you want to generate a Json4s key serialiser for + */ + def keySerializer[A <: EnumEntry: Manifest](enum: Enum[A]): CustomKeySerializer[A] = + new CustomKeySerializer[A]( + _ => + ( + { + case s: String if enum.withNameOption(s).isDefined => enum.withName(s) + }, { + case x: A => x.entryName + } + )) } diff --git a/enumeratum-json4s/src/test/scala/enumeratum/Json4sSpec.scala b/enumeratum-json4s/src/test/scala/enumeratum/Json4sSpec.scala index 232abf5c..2652e70f 100644 --- a/enumeratum-json4s/src/test/scala/enumeratum/Json4sSpec.scala +++ b/enumeratum-json4s/src/test/scala/enumeratum/Json4sSpec.scala @@ -6,10 +6,12 @@ import org.scalatest.{FunSpec, Matchers} class Json4sSpec extends FunSpec with Matchers { - implicit val formats = DefaultFormats + Json4s.serializer(TrafficLight) + implicit val formats = DefaultFormats + Json4s.serializer(TrafficLight) + Json4s.keySerializer( + TrafficLight) case class Data(tr: TrafficLight) case class DataOpt(tr: Option[TrafficLight]) + case class DataMap(tr: Map[TrafficLight, Int]) describe("to JSON") { it("should serialize plain value to entryName") { @@ -27,6 +29,13 @@ class Json4sSpec extends FunSpec with Matchers { it("should serialize None to nothing") { Serialization.write(DataOpt(tr = None)) shouldBe """{}""" } + + it("should serialize value to key") { + TrafficLight.values.foreach { value => + val name = value.entryName + Serialization.write(DataMap(tr = Map(value -> 0))) shouldBe s"""{"tr":{"$name":0}}""" + } + } } describe("from JSON") { @@ -47,6 +56,13 @@ class Json4sSpec extends FunSpec with Matchers { Serialization.read[DataOpt]("""{}""").tr shouldBe None } + it("should parse enum members into keys") { + TrafficLight.values.foreach { value => + val name = value.entryName + Serialization.read[DataMap](s"""{"tr":{"$name":0}}""").tr shouldBe Map(value -> 0) + } + } + it("should parse invalid value into None") { Serialization.read[DataOpt]("""{"tr":"bogus"}""").tr shouldBe None Serialization.read[DataOpt]("""{"tr":17}""").tr shouldBe None