How to rename nested fields with Json4s - scala

As the title states, I am trying to rename fields on generated json from case classes using Json4s.
If I try to rename fields on simple case classes like:
case class User(name: String, lastName: String)
Following examples that you can find in the documentation of json4s or here How can I rename a field during serialization with Json4s? will work.
But documentation does not mention how to do nested object renames like for example from deviceId to did in this example:
case class User(name: String, lastName: String, details: UserDetails)
case class UserDetails(deviceId: String)
I tried using things like:
FieldSerializer.renameFrom("deviceId", "did")
or
FieldSerializer.renameFrom("details.deviceId", "details.did")
or
parse(message) transformField {
case ("deviceId", value) => ("did", value)
}
or
parse(message) transformField {
case ("details.deviceId", value) => ("details.did", value)
}
And none of them worked, so my question is: Is this nested rename possible on scala4s? If yes, how can I do to for example rename deviceId to did?

For the nested object, you can create FieldSerializer to bind this nested type, like:
import org.json4s._
import org.json4s.FieldSerializer._
import org.json4s.jackson.Serialization.write
val rename = FieldSerializer[UserDetails](renameTo("deviceId", "did")) // bind UserDetails to FieldSerializer
implicit val format: Formats = DefaultFormats + rename
println(write(u))
> {"name":"name","lastName":"lastName","details":{"did":"deviceId"}}

Related

How to ignore a field from serializing when using circe in scala

I am using circe in scala and have a following requirement :
Let's say I have some class like below and I want to avoid password field from being serialised then is there any way to let circe know that it should not serialise password field?
In other libraries we have annotations like #transient which prevent field from being serialised ,is there any such annotation in circe?
case class Employee(
name: String,
password: String)
You could make a custom encoder that redacts some fields:
implicit val encodeEmployee: Encoder[Employee] = new Encoder[Employee] {
final def apply(a: Employee): Json = Json.obj(
("name", Json.fromString(a.name)),
("password", Json.fromString("[REDACTED]")),
)
}
LATER UPDATE
In order to avoid going through all fields contramap from a semiauto/auto decoder:
import io.circe.generic.semiauto._
implicit val encodeEmployee: Encoder[Employee] =
deriveEncoder[Employee]
.contramap[Employee](unredacted => unredacted.copy(password = "[REDACTED]"))
Although #gatear's answer is useful, it doesn't actually answer the question.
Unfortunately Circe (at least until version 0.14.2) does not have annotations to ignore fields. So far there is only a single annotation (#JsonKey) and this is used to rename field names.
In order to ignore a field when serialising (which Circe calls encoding) you can avoid that field in the Encoder implementation.
So instead of including the password field:
implicit val employeeEncoder: Encoder[Employee] =
Encoder.forProduct2("name", "password")(employee => (employee.name, employee.password))
you ommit it:
implicit val employeeEncoder: Encoder[Employee] =
Encoder.forProduct1("name")(employee => (u.name))
Alternatively what I've been using is creating a smaller case class which only includes the fields I'm interested in. Then I let Circe's automatic derivation kick in with io.circe.generic.auto._:
import io.circe.generic.auto._
import io.circe.syntax._
case class EmployeeToEncode(name: String)
// Then given an employee object:
EmployeeToEncode(employee.name).asJson
deriveEncoder.mapJsonObject(_.remove("password"))

Doobie Query for Entities with Value Class Fields

I am using Doobie to manage database persistence. I use value class fields in my entity Foo i.e.
case class CreatedOn(value: LocalDateTime) extends AnyVal
case class Resource(value: String) extends AnyVal
case class Foo(id: Option[Int], resource: Resource, createdOn: CreatedOn)
implicit fooRead: Read[Foo] = Read[(Option[Int], String, LocalDateTime)].map {
case (oid, resource, createdOn) => Foo(oid, Resource(resource), CreatedOn(createdOn))
}
implicit fooWrite: Write[Foo] = Write[(Option[Int], String, LocalDateTime)].contramap {e => (e.oid, e.resource.value, e.createdOn.value}
Yet the compiler complains missing Read[(Int, String, LocalDateTime)].map {...
Any suggestions on how I can fix this? Is using value class a bad idea, to begin with, when comes to the entity? Thanks
Doobie is able to read queries into case classes. If you imagine case classes as tuples and you imagine that you can flatten them then this is what Doobie does when you query things into a case class.
Foo(Some(1), Resource("test"), CreatedOn(time))
(Some(1), Tuple1("test"), Tuple1(time))
(Some(1), "test", time) // (Option[Int], String, LocalDateTime)
If your derivation fails you can check for which of these fields:
sql"".query[Int]
sql"".query[String]
sql"".query[LocalDateTime]
The line which fail compilation tells you the missing instance.
From my experience it is time instance. You have to import them separately, which you probably didn't import
doobie.implicits.javatime._

Scala generics usage for methods + json4s parsing

I am not sure if this is achievable and I have a very basic understanding of how generics work in scala. But I was wondering if this is possible.
Say I have a method:
case class Person(id:String,name:String)
case class Student(id:String,name:String, class:String)
def convertToJson[A](fileName:String):{
//read file
parse[A]
}
Is it possible to write this generic method which would parse the json based on the type of class I send when I call the convertToJson method?
Something like:
convertToJson[Student](fileName)
convertToJson[Person](fileName)
BTW the above code gives me a :
No Manifest available for A. error.
Using json4s for parsing.
Any help is appreciated.
This will convert a JSON string to a case class
import org.json4s._
import org.json4s.jackson.JsonMethods._
def convertToJson[T](json: String)(implicit fmt: Formats = DefaultFormats, mf: Manifest[T]): T =
Extraction.extract(parse(json))
Once this is defined you can parse appropriate strings to the required type:
case class Person(id: String, name: String)
case class Student(id: String, name: String, `class`: String)
val person = convertToJson[Person]("""{"name":"Jane","id":45}""")
val student = convertToJson[Student]("""{"name":"John","id":63, "class": "101"}""")
Note that this will ignore JSON data that does not match fields in the case class. If a field is optional in the JSON, make it an Option in the case class and you will get None if the field is not there.

How to use Circe Optics and Decode together

I am using circe optics like this
import io.circe.parser._
import io.circe.optics._
import io.circe.optics.JsonPath._
val json = parse("""{"response": {"person": {"firstname": "foo", "lastname":"bar"}}}""").right.get
Now I want to extract the whole person object in string form ... from this json like
val p = root.response.person.string
and then decode it into a case class like
case class Person(firstname: String, lastname: String)
decode[Person](p.getOption(json).get)
But it doesn't work because root.response.person.string returns null. I think it only works on actual string and integer columns.
So can circe optics be used to extract entire sections of json (for example the person object inside of json)? and then that section is decoded into a case class?
This gets it done. No need to get the string in between, just work with the Json
object Some extends App {
import io.circe.optics.JsonPath._
import io.circe.parser._
import io.circe._
import io.circe.generic.semiauto._
val json = parse("""{"response": {"person": {"firstname": "foo", "lastname":"bar"}}}""").right.get
// this is just a lense to the person, not the person yet
val personJsonPath = root.response.person.json
case class Person(firstname: String, lastname: String)
implicit val personDecoder: Decoder[Person] = deriveDecoder[Person]
val maybePerson = personJsonPath
// here you get the person out
.getOption(json)
// transforming json directly to case class, error handling should not be done like this ;)
.map(_.as[Person].fold(throw _, identity))
println(maybePerson)
}

Where to place Scala Lenses in my project?

Actually I've stuck with functional programming code style and project structure. When programming in Java I knew where to place all my logic, but I'm not familiar with the functional style.
Actually I try to make my Scala classes in my current project immutable. Then I want to use scalaz.Lens and scalaz.PLens to change my objects in future(actually create new).
In all Lense examples people place a code in one object, which extends App trait to simply show how it works. But in real-life example should be some appropriate place to write those Lenses.
In Java all mutators and accessors placed in classes itself. But with Lenses I don't know where to write them.
Will appreciate any advice
Typically lenses are hold in companion objects, like
package package1
import monocle.Iso
import monocle.macros.Lenses
#Lenses
case class Name(first: String, last: String, mid: Option[String]) {
def fullName = s"$first ${mid.fold("")(_ + " ")}$last"
}
object Name {
val fullName = Iso[Name, String](_.fullName)(_.split(' ') match {
case Array() => Name("", "", None)
case Array(name) => Name(name, "", None)
case Array(first, last) => Name(first, last, None)
case Array(first, mid, last, _*) => Name(first, last, Some(mid))
})
}
and
package package2
import monocle.macros.Lenses
import package1._
#Lenses
case class User(name: Name, age: Int)
object User {
val fullName = name ^<-> Name.fullName
}
Here the #Lenses macro annotation will automatically put lenses for simple fields in companion objects