Any way to serialize a Scala case class to JSON and have the ability to provide custom serialized names ?
For example, In Java this can be done using the Gson library :
public class SomeClassWithFields {
#SerializedName("name") private final String someField;
private final String someOtherField;
public SomeClassWithFields(String a, String b) {
this.someField = a;
this.someOtherField = b;
}
}
I tried doing this in Scala :
case class SomeClassWithFields(#SerializedName("name")
someField:String)
but it seems to have no effect .
Any thoughts ?
Yes it can be done. Here's how:
case class SomeClassWithFields(#(SerializedName #scala.annotation.meta.field)("name") someField:String)
The syntax is strange (note that the outer '#' wraps the "SerializedName" and the scala field annotation) but it works fine. See more details at: https://issues.scala-lang.org/plugins/servlet/mobile#issue/SI-8975
Further improving Corindiano's answer by making it into a custom annotation, which can be (re)used elsewhere.
import scala.annotation.meta.field
case class SomeClassWithFields(#SerializedNameField(value = "name") someField:String) { ... }
object SomeClassWithFields {
type SerializedNameField = com.google.gson.annotations.SerializedName #field
}
You can do it with spray-json calling the jsonFormat overloads.
Here's how it would work with your example:
import spray.json._
case class SomeClassWithFields( someField:String)
object SomeClassJsonProtocol extends DefaultJsonProtocol {
implicit val someClassFormat = jsonFormat(SomeClassWithFields,"name")
}
Related
Suppose I have the following abstract base class:
package Models
import reactivemongo.bson.BSONObjectID
abstract class RecordObject {
val _id: String = BSONObjectID.generate().stringify
}
Which is extended by the following concrete case class:
package Models
case class PersonRecord(name: String) extends RecordObject
I then try to get a JSON string using some code like the following:
import io.circe.syntax._
import io.circe.generic.auto._
import org.http4s.circe._
// ...
val person = new PersonRecord(name = "Bob")
println(person._id, person.name) // prints some UUID and "Bob"
println(person.asJso) // {"name": "Bob"} -- what happened to "_id"?
As you can see, the property _id: String inherited from RecordObject is missing. I would expect that the built-in Encoder should function just fine for this use case. Do I really need to build my own?
Let's see what happens in encoder generation. Circe uses shapeless to derive its codecs, so its enough to check what shapeless resolves into to answer your question. So in ammonite:
# abstract class RecordObject {
val _id: String = java.util.UUID.randomUUID.toString
}
defined class RecordObject
# case class PersonRecord(name: String) extends RecordObject
defined class PersonRecord
# import $ivy.`com.chuusai::shapeless:2.3.3`, shapeless._
import $ivy.$ , shapeless._
# Generic[PersonRecord]
res3: Generic[PersonRecord]{type Repr = String :: shapeless.HNil} = ammonite.$sess.cmd3$anon$macro$2$1#1123d461
OK, so its String :: HNil. Fair enough - what shapeless does is extracting all fields available in constructor transforming one way, and putting all fields back through constructor if converting the other.
Basically all typeclass derivation works this way, so you should make it possible to pass _id as constructor:
abstract class RecordObject {
val _id: String
}
case class PersonRecord(
name: String,
_id: String = BSONObjectID.generate().stringify
) extends RecordObject
That would help type class derivation do its work. If you cannot change how PersonRecord looks like... then yes you have to write your own codec. Though I doubt it would be easy as you made _id immutable and impossible to set from outside through a constructor, so it would also be hard to implement using any other way.
In Java I'm able to modify final members in the constructor. Please see the following example
class Scratch {
private final String strMember;
public Scratch(String strParam) {
this.strMember = strParam.trim();
}
}
Is there a way in Kotlin to modify val members during construction, in this case to trim() them before the parameter value are assigned to the field.
If not, what is the recommended workaround to do so without generating too much overhead?
You can declare an argument to the constructor that isn't marked with val or var. This has the effect of being local to the constructor and lost once class construction is complete. Take that argument and set it to whatever you want.
class Scratch(str: String) {
private val strMember = str.trim()
}
Like this: constructor parameters are available during initialization of properties.
class Scratch(strParam:String) {
private val strMember = strParam.trim()
}
Try your strParam final property as follow
class Scratch(strParam : String) {
val strParam : String = strParam
get() = field.trim()
}
So, you can use them inside and outside your Scratch class
I would like to serialize an extended class in scala and
i have some test code..
import org.specs2.mutable._
import org.specs2.runner._
import org.junit.runner._
import play.api.libs.json.Json
#RunWith(classOf[JUnitRunner])
class JsonSerializerTest extends Specification {
class A(val s1: String)
case class B(s2: String) extends A("a")
"Application" should {
"serialize class to JSON" in {
implicit val bWrites = Json.writes[B]
implicit val bReads = Json.reads[B]
val bClass = B("b")
println(bClass.s1 + " " + bClass.s2)
val serialized = Json.toJson[B](bClass)
val s1 = (serialized \ "s1").asOpt[String]
s1 should beSome[String]
}
}
}
In this case test print:
a b
Application should
'None' is not Some
java.lang.Exception: 'None' is not Some
It means that s1 field from parent class were not serialized.
The solution
class A(val s1: String)
case class B(override val s1: String, s2: String) extends A(s1)
mostly unacceptable because in real application classes have a lot of fields and specifying them explicitly every time when i extend class complicates the code.
Is there any other solution for this case?
you can manually create the json serializers (described here: https://www.playframework.com/documentation/2.5.x/ScalaJsonCombinators)
the problem with your version is that Json.writes and Json.reads are macros that are looking specifically at the constructor of the case class, and building the serializer from that (so superclass arguments aren't captured). you could copy and roll your own version of the macro: https://github.com/playframework/playframework/blob/d6c2673d91d85fd37de424951ee5ad9f4f4cce98/framework/src/play-json/src/main/scala/play/api/libs/json/JsMacroImpl.scala
lastly, you can make a function that takes the result of Json.writes and Json.reads, and adds on the shared fields you want. something like:
object A {
def writesSubclass[T](writer: Writes[T]): Writes[T] = new Writes[T] {
def writes(t: T) = Json.obj("s1" -> t.s1) ++ writer.writes(t).as[JsObject]
}
}
implicit val bWrites = A.writesSubclass(Json.writes[B])
depending on how often your A gets extended, this might be your best bet.
How to parse json with spray json that uses snake case (underscore notation) instead of camel case?
E.g.
case class Test(subjectDescription: String)
"{\"subject_description\":\"Medicine\"}".parseJson.convertTo[Test]
should work and not throw exception.
Like this:
case class Test(subjectDescription: String)
implicit val testFormat = jsonFormat(Test.apply, "subject_description")
"{\"subject_description\":\"Medicine\"}".parseJson.convertTo[Test]
The trick here is jsonFormat function takes string arguments for json object keys.
This answer is taken from https://groups.google.com/forum/#!msg/spray-user/KsPIqWDK0AY/HcanflgRzMcJ. Putting it on SO since the SEO is better.
/**
* A custom version of the Spray DefaultJsonProtocol with a modified field naming strategy
*/
trait SnakifiedSprayJsonSupport extends DefaultJsonProtocol {
import reflect._
/**
* This is the most important piece of code in this object!
* It overrides the default naming scheme used by spray-json and replaces it with a scheme that turns camelcased
* names into snakified names (i.e. using underscores as word separators).
*/
override protected def extractFieldNames(classTag: ClassTag[_]) = {
import java.util.Locale
def snakify(name: String) = PASS2.replaceAllIn(PASS1.replaceAllIn(name, REPLACEMENT), REPLACEMENT).toLowerCase(Locale.US)
super.extractFieldNames(classTag).map { snakify(_) }
}
private val PASS1 = """([A-Z]+)([A-Z][a-z])""".r
private val PASS2 = """([a-z\d])([A-Z])""".r
private val REPLACEMENT = "$1_$2"
}
object SnakifiedSprayJsonSupport extends SnakifiedSprayJsonSupport
import SnakifiedSprayJsonSupport._
object MyJsonProtocol extends SnakifiedSprayJsonSupport {
implicit val testFormat = jsonFormat1(Test.apply)
}
I have an EntityId class that servers as a simple wrapper class to database identifiers. The class already has methods for converting to and from a string representation. I'd like to use this string representation of the EntityId in my JSON web resources.
What's the simplest to implement custom serialization for this simple type in Jackson? I know I can write a custom serializer and deserializer, but I wondered if there might be an even simpler solution.
Thanks!
If there is a method to serialize type as String, you can just add #JsonValue annotation like so:
public class MyClass {
#JsonValue public String toString() { return "xxx"; }
}
Conversely, if there is a single-arg constructor that takes a String, int or long (or some Java type that Jackson can convert to from JSON Scalar type), you can add #JsonCreator annotation next to that constructor:
public class MyClass {
#JsonCreator
public MyClass(OtherPojo value) { // or use 'Map<String,Object>', extract data
// ...
}
}