0

I have a case class containing a field of password. For safety I need to mask it when converting to Json. So I create a custom serializer for this, as below.

  import org.json4s.CustomSerializer
  import org.json4s._
  import scala.runtime.ScalaRunTime
  import org.json4s.jackson.JsonMethods._

  case class UserInfo(
                       userid: Long,
                       username: Option[String],
                       password: Option[String]
                     ) {
    override def toString: String = {
      val ui = this.copy(password = password.map(_ =>  "******"))
      ScalaRunTime._toString(ui)
    }
  }

  case object UserInfoSerializer extends CustomSerializer[UserInfo](format => ({
    case jsonObj: JObject =>
      implicit val formats = DefaultFormats
      jsonObj.extract[UserInfo]
  }, {
    case ui: UserInfo =>
      implicit val formats = DefaultFormats
      Extraction.decompose(ui.copy(password = ui.password.map(_ => "******")))
  }))

  implicit val formats = DefaultFormats + UserInfoSerializer

But when I try to convert val ui = UserInfo(123, Some("anonymous"), Some("xxx")) to a Json string by write(render(ui)), it always fails with

scala> render(ui)
<console>:22: error: type mismatch;
 found   : UserInfo
 required: org.json4s.JValue
    (which expands to)  org.json4s.JsonAST.JValue
       render(ui)

I have to use it as render(Extraction.decompose(ui)), or add an implicit conversion from UserInfo to JValue as implicit def userInfo2JValue(ui: UserInfn) = Extraction.decompose(ui)

What's the right way to make the custom serializer work as default ones?

user6502167
  • 731
  • 9
  • 18
  • BEWARE: Json4s is [vulnerable under DoS/DoW attacks](https://github.com/json4s/json4s/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+denial)! – Andriy Plokhotnyuk Jun 02 '20 at 07:30

1 Answers1

0

Method render() simply renders JSON AST, it does not know how to convert an instance of your class to JValue. Look at this diagram, which illustrates data transformations with Json4s. Long story short, if you want to render your class as a JSON string, you can first convert it to JValue and then render like you did:

 render(Extraction.decompose(ui))

or you can take a shortcut and use Serialization.write which does both operations internally:

 Serialization.write(ui)

In either case, it is going to use your custom serializer if it has been added the explicit formats.

uralian
  • 78
  • 3
  • I'm just curious why it works for scala predefined classes, like `val json = List(1, 2, 3); compact(render(json))`. I think I can make this happens for custom case classes by defining my own implicit conversion function. Not sure if this is how scala lib classes are working? – user6502167 Jun 04 '20 at 00:41
  • It happens because of a number of implicit conversions from common Scala types like String => JString, Boolean => JBool as well as containers List => JArray, Map => JObject etc. so in your example `List(1,2,3)` gets implicitly converted into `JArray(List(JInt(1), JInt(2), JInt(3)))` before `render()` is called. – uralian Jun 04 '20 at 01:35