json4s allows user to convert a JsonAST object to a case class using extract.
import org.json4s._
import org.json4s.jackson.JsonMethods._
implicit val formats = DefaultFormats
case class Item(name: String, price: Double)
val json = parse("""{"name": "phone", "price": 1000.0}""") // JObject(List((name,JString(phone)), (price,JDouble(1000.0))))
val item = json.extract[Item] // Item(phone,1000.0)
However, to convert a case class into a JsonAST object, the only way I can think of is:
serialize a case class using write
deserialize a string using extract
Like below:
parse(write(item)) // JObject(List((name,JString(phone)), (price,JDouble(1000.0))))
Is there any better way for the conversion? Thank you!
Extraction.decompose converts a case class object into a JsonAST.
Extraction.decompose(item) // JObject(List((name,JString(phone)), (price,JDouble(1000.0))))
Related
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"))
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?
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.
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.
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)
}
I'm trying to use "pickling" serialization is Scala, and I see the same example demonstrating it:
import scala.pickling._
import json._
val pckl = List(1, 2, 3, 4).pickle
Unpickling is just as easy as pickling:
val lst = pckl.unpickle[List[Int]]
This example raises some question. First of all, it skips converting of object to string. Apparently you need to call pckl.value to get json string representation.
Unpickling is even more confusing. Deserialization is an act of turning string (or bytes) into an object. How come this "example" demonstrates deserialization if there is no string/binry representation of object?
So, how do I deserialize simple object with pickling library?
Use the type system and case classes to achieve your goals. You can unpickle to some superior type in your hierarchy (up to and including AnyRef). Here is an example:
trait Zero
case class One(a:Int) extends Zero
case class Two(s:String) extends Zero
object Test extends App {
import scala.pickling._
import json._
// String that can be sent down a wire
val wire: String = Two("abc").pickle.value
// On the other side, just use a case class
wire.unpickle[Zero] match {
case One(a) => println(a)
case Two(s) => println(s)
case unknown => println(unknown.getClass.getCanonicalName)
}
}
Ok, I think I understood it.
import scala.pickling._
import json._
var str = Array(1,2,3).pickle.value // this is JSON string
println(str)
val x = str.unpickle[Array[Int]] // unpickle from string
will produce JSON string:
{
"tpe": "scala.Array[scala.Int]",
"value": [
1,
2,
3
]
}
So, the same way we pickle any type, we can unpickle string. Type of serialization is regulated by implicit formatter declared in "json." and can be replaced by "binary."
It does look like you will be starting with a pickle to unpickle to a case class. But the JSON string can be fed to the JSONPickle class to get the starting pickle.
Here's an example based on their array-json test
package so
import scala.pickling._
import json._
case class C(arr: Array[Int]) { override def toString = s"""C(${arr.mkString("[", ",", "]")})""" }
object PickleTester extends App {
val json = """{"arr":[ 1, 2, 3 ]}"""
val cPickle = JSONPickle( json )
val unpickledC: C = cPickle.unpickle[C]
println( s"$unpickledC, arr.sum = ${unpickledC.arr.sum}" )
}
The output printed is:
C([1,2,3]), arr.sum = 6
I was able to drop the "tpe" in from the test as well as the .stripMargin.trim on the input JSON from the test. It works all in one line, but I thought it might be more apparent split up. It's unclear to me if that "tpe" from the test is supposed to provide a measure of type safety for the incoming JSON.
Looks like the only other class they support for pickling is a BinaryPickle unless you want to roll your own. The latest scala-pickling snapshot jar requires quasiquotes to compile the code in this answer.
I tried someting more complicated this morning and discovered that the "tpe" is required for non-primatives in the incoming JSON - which points out that the serialized string really must be compatible with the pickler( which I mixed into the above code ):
case class J(a: Option[Boolean], b: Option[String], c: Option[Int]) { override def toString = s"J($a, $b, $c)" }
...
val jJson = """{"a": {"tpe": "scala.None.type"},
| "b":{"tpe": "scala.Some[java.lang.String]","x":"donut"},
| "c":{"tpe": "scala.Some[scala.Int]","x":47}}"""
val jPickle = JSONPickle( jJson.stripMargin.trim )
val unpickledJ: J = jPickle.unpickle[J]
println( s"$unpickledJ" )
...
where naturually, I had to use .value on a J(None, Some("donut"), Some(47)) to figure out how to create the jJson input value to prevent the unpickling from throwing an exception.
The output for J is like:
J(None, Some(donut), Some(47))
Looking at this test, it appears that if the incoming JSON is all primatives or case classes (or combinations) that the JSONPickle magic works, but some other classes like Options require extra "tpe" type information to unpickle correctly.