Scala case classes with new firebase-server-sdk - scala

Does the new server SDK for firebase firebase-server-sdk (3.0.1) support Scala case class deserialization? The previous firebase java sdk used jackson which you could bolt in a scala module to support case classes. It's unclear if its possible to do something similar with the new SDK? Does it use Gson or some custom class mapper?
In the simplest example:
case class Person(firstName: String, lastName: String, age: Int)
With a Firebase listener setup such as:
var options = new FirebaseOptions.Builder()
.setDatabaseUrl("https://<your firebase>.firebaseio.com")
.setServiceAccount(new FileInputStream("firebase-auth.json"))
.build()
FirebaseApp.initializeApp(options);
var ref = FirebaseDatabase.getInstance().getReference("somepath")
ref.addListenerForSingleValueEvent(new ValueEventListener {
override def onDataChange(dataSnapshot: DataSnapshot): Unit = {
println(dataSnapshot.getValue(classOf[Person]))
}
override def onCancelled(databaseError: DatabaseError): Unit = {
println(databaseError.getMessage)
}
})
This will fail on the getValue call dataSnapshot.getValue(classOf[Person]) with the exception:
Exception in thread "FirebaseDatabaseEventTarget" com.google.firebase.database.DatabaseException: No properties to serialize found on class Person
at com.google.firebase.database.utilities.encoding.CustomClassMapper$BeanMapper.<init>(CustomClassMapper.java:495)
at com.google.firebase.database.utilities.encoding.CustomClassMapper.loadOrCreateBeanMapperForClass(CustomClassMapper.java:285)
at com.google.firebase.database.utilities.encoding.CustomClassMapper.convertBean(CustomClassMapper.java:379)
at com.google.firebase.database.utilities.encoding.CustomClassMapper.deserializeToClass(CustomClassMapper.java:187)
at com.google.firebase.database.utilities.encoding.CustomClassMapper.convertToCustomClass(CustomClassMapper.java:61)
at com.google.firebase.database.DataSnapshot.getValue(DataSnapshot.java:181)
at PetEventsNodeActorSpec$$anonfun$2$$anonfun$apply$mcV$sp$2$$anonfun$apply$mcV$sp$3$$anon$1.onDataChange(PetEventsNodeActorSpec.scala:290)
at com.google.firebase.database.Query$1.onDataChange(Query.java:147)
at com.google.firebase.database.core.ValueEventRegistration.fireEvent(ValueEventRegistration.java:57)
at com.google.firebase.database.core.view.DataEvent.fire(DataEvent.java:45)
at com.google.firebase.database.core.view.EventRaiser$1.run(EventRaiser.java:35)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
I've tried adding annotations to the class such as #BeanProperty but then get:
Exception in thread "FirebaseDatabaseEventTarget" com.google.firebase.database.DatabaseException: Class Person is missing a constructor with no arguments
Rather than go down the rabbit hole of annotating or adding code to every case class, any ideas on getting scala case classes to play nicely with the new firebase sdk?

AFAIK, there isn't a way to use a case class directly. I ended up creating plain classes with #BeanProperty annotations and then converting them to case classes. The reason for
Exception in thread "FirebaseDatabaseEventTarget" com.google.firebase.database.DatabaseException: Class Person is missing a constructor with no arguments
is because your class constructor must be nullary (i.e. it cannot take any arguments):
import scala.beans.BeanProperty
case class Person(firstName: String, lastName: String, age: Int) {
def toBean: PersonBean = {
val person = new PersonBean()
person.firstName = firstName
person.lastName = lastName
person.age = age
person
}
}
class PersonBean() {
#BeanProperty var firstName: String = ""
#BeanProperty var lastName: String = ""
#BeanProperty var age: Int = 0
def toCase: Person = Person(firstName, lastName, age)
}
var ref = FirebaseDatabase.getInstance().getReference("somepath")
ref.addListenerForSingleValueEvent(new ValueEventListener {
override def onDataChange(dataSnapshot: DataSnapshot): Unit = {
val record = dataSnapshot.getValue(classOf[PersonBean])
val person = if (record != null) record.toCase else null
}
override def onCancelled(databaseError: DatabaseError): Unit = {
println(databaseError.getMessage)
}
})

Related

Neo4j OGM example with Scala

I tried the example mentioned in Luanne's article The essence of Spring Data Neo4j 4 in Scala. The code can be found in the neo4j-ogm-scala repository.
package neo4j.ogm.scala.domain
import org.neo4j.ogm.annotation.GraphId;
import scala.beans.BeanProperty
import org.neo4j.ogm.annotation.NodeEntity
import org.neo4j.ogm.annotation.Relationship
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
abstract class Entity {
#GraphId
#BeanProperty
var id: Long = _
override def equals(o: Any): Boolean = o match {
case other: Entity => other.id.equals(this.id)
case _ => false
}
override def hashCode: Int = id.hashCode()
}
#NodeEntity
class Category extends Entity {
var name: String = _
def this(name: String) {
this()
this.name = name
}
}
#NodeEntity
class Ingredient extends Entity {
var name: String = _
#Relationship(`type` = "HAS_CATEGORY", direction = "OUTGOING")
var category: Category = _
#Relationship(`type` = "PAIRS_WITH", direction = "UNDIRECTED")
var pairings: Set[Pairing] = Set()
def addPairing(pairing: Pairing): Unit = {
pairing.first.pairings +(pairing)
pairing.second.pairings +(pairing)
}
def this(name: String, category: Category) {
this()
this.name = name
this.category = category
}
}
#RelationshipEntity(`type` = "PAIRS_WITH")
class Pairing extends Entity {
#StartNode
var first: Ingredient = _
#EndNode
var second: Ingredient = _
def this(first: Ingredient, second: Ingredient) {
this()
this.first = first
this.second = second
}
}
object Neo4jSessionFactory {
val sessionFactory = new SessionFactory("neo4j.ogm.scala.domain")
def getNeo4jSession(): Session = {
System.setProperty("username", "neo4j")
System.setProperty("password", "neo4j")
sessionFactory.openSession("http://localhost:7474")
}
}
object Main extends App {
val spices = new Category("Spices")
val turmeric = new Ingredient("Turmeric", spices)
val cumin = new Ingredient("Cumin", spices)
val pairing = new Pairing(turmeric, cumin)
cumin.addPairing(pairing)
val session = Neo4jSessionFactory.getNeo4jSession()
val tx: Transaction = session.beginTransaction()
try {
session.save(spices)
session.save(turmeric)
session.save(cumin)
session.save(pairing)
tx.commit()
} catch {
case e: Exception => // tx.rollback()
} finally {
// tx.commit()
}
}
The problem is that nothing gets saved to Neo4j. Can you please point out the problem in my code?
Thanks,
Manoj.
Scala’s Long is an instance of a Value class. Value classes were explicitly introduced to avoid allocating runtime objects. At the JVM level therefore Scala's Long is equivalent to Java’s primitive long which is why it has the primitive type signature J. It cannot be therefore be null, and should not be used as a graphId. Although Scala mostly will do auto-boxing between its own Long and Java’s Long class, this doesn’t apply to declarations, only to operations on those objects.
The #GraphId isn't being picked up on your entities. I have zero knowledge of Scala but it looks like the scala long isn't liked much by the OGM; var id: java.lang.Long = _ works fine.

JSON serialization of Scala enums using Jackson

Following this article https://github.com/FasterXML/jackson-module-scala/wiki/Enumerations
The enumeration declaration is as
object UserStatus extends Enumeration {
type UserStatus = Value
val Active, Paused = Value
}
class UserStatusType extends TypeReference[UserStatus.type]
case class UserStatusHolder(#JsonScalaEnumeration(classOf[UserStatusType]) enum: UserStatus.UserStatus)
The DTO is declared as
class UserInfo(val emailAddress: String, val userStatus:UserStatusHolder) {
}
and the serialization code is
val mapper = new ObjectMapper()
mapper.registerModule(DefaultScalaModule)
def serialize(value: Any): String = {
import java.io.StringWriter
val writer = new StringWriter()
mapper.writeValue(writer, value)
writer.toString
}
The resulting JSON serialization is
{
"emailAddress":"user1#test.com",
"userStatus":{"enum":"Active"}
}
Is it possible to get it the following form ?
{
"emailAddress":"user1#test.com",
"userStatus":"Active"
}
Have you tried:
case class UserInfo(
emailAddress: String,
#JsonScalaEnumeration(classOf[UserStatusType]) userStatus: UserStatus.UserStatus
)
The jackson wiki's example is a little misleading. You don't need the holder class. Its just an example of a thing that has that element. The thing you need is the annotation

Adding functionality before calling constructor in extra constructor

Is it possible to add functionality before calling constructor in extra constructor in scala ?
Lets say, I have class User, and want to get one string - and to split it into attributes - to send them to the constructor:
class User(val name: String, val age: Int){
def this(line: String) = {
val attrs = line.split(",") //This line is leading an error - what can I do instead
this(attrs(0), attrs(1).toInt)
}
}
So I know I'm not able to add a line before sending to this, because all constructors need to call another constructor as the first statement of the constructor.
Then what can I do instead?
Edit:
I have a long list of attributes, so I don't want to repeat line.split(",")
I think this is a place where companion object and apply() method come nicely into play:
object User {
def apply(line: String): User = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
class User(val name: String, val age: Int)
Then you just create your object the following way:
val u1 = User("Zorro,33")
Also since you're exposing name and age anyway, you might consider using case class instead of standard class and have consistent way of constructing User objects (without new keyword):
object User {
def apply(line: String): User = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
case class User(name: String, age: Int)
val u1 = User("Zorro,33")
val u2 = User("Zorro", "33")
Ugly, but working solution#1:
class User(val name: String, val age: Int){
def this(line: String) = {
this(line.split(",")(0), line.split(",")(1).toInt)
}
}
Ugly, but working solution#2:
class User(val name: String, val age: Int)
object User {
def fromString(line: String) = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
Which can be used as:
val johny = User.fromString("johny,35")
You could use apply in place of fromString, but this will lead to a confusion (in one case you have to use new, in the other you have to drop it) so I prefer to use different name
Another ugly solution:
class User(line: String) {
def this(name: String, age: Int) = this(s"$name,$age")
val (name, age) = {
val Array(nameStr,ageStr) = line.split(",")
(nameStr,ageStr.toInt)
}
}
But using a method of the companion object is probably better.

Scala case class generated field value

I have an existing Scala application and it uses case classes which are then persisted in MongoDB. I need to introduce a new field to a case class but the value of it is derived from existing field.
For example, there is phone number and I want to add normalised phone number while keeping the original phone number. I'll update the existing records in MongoDB but I would need to add this normalisation feature to existing save and update code.
So, is there any nice shortcut in Scala to add a "hook" to a certain field of a case class? For example, in Java one could modify setter of the phone number.
Edit:
The solution in Christian's answer works fine alone but in my case I have defaults for fields (I think because of Salat...)
case class Person(name: String = "a", phone: Option[String] = None, normalizedPhone: Option[String] = None)
object Person {
def apply(name: String, phone: Option[String]): Person = Person(name, phone, Some("xxx" + phone.getOrElse("")))
}
And if use something like:
Person(phone = Some("s"))
I'll get: Person = Person(a,Some(s),None)
You can define an apply method in the companion object:
case class Person(name: String, phone: String, normalizedPhone: String)
object Person {
def apply(name: String, phone: String): Person = Person(name, phone, "xxx" + phone)
}
Then, in the repl:
scala> Person("name", "phone")
res3: Person = Person(name,phone,xxxphone)
You could add methods to the case class that would contain the transforming logic from existing fields. For example:
case class Person(name: String, phone: String) {
def normalizedPhone = "+40" + phone
}
Then you can use the method just as if it was a field:
val p1 = new Person("Joe", "7234")
println(p1.normalizedPhone) // +407234
I think this comes close to what you need. Since you can't override the generated mutator, prefix the existing field with an underscore, make it private, and then write the accessor and mutator methods for the original field name. After that, you only need an extra line in the constructor to accommodate for constructor-based initialization of the field.
case class Test(private var _phoneNumber: String, var formatted: String = "") {
phoneNumber_=(_phoneNumber)
def phoneNumber = _phoneNumber
def phoneNumber_=(phoneNumber: String) {
_phoneNumber = phoneNumber
formatted = "formatted" + phoneNumber
}
}
object Test {
def main(args: Array[String]) {
var t = Test("09048751234")
println(t.phoneNumber)
println(t.formatted)
t.phoneNumber = "08068745963"
println(t.phoneNumber)
println(t.formatted)
}
}

Scalatra serializes object to JSON, but its relation is not serialized

I'm using Scalatra and Squeryl to make a Single Page Application example, so I need my Scalatra Servlet always returning JSON. It's working perfectly when serializing an object with no relations.
I have a class Address that has a ManyToOne relationship with the class City:
class City(val id: Long, val name: String) extends KeyedEntity[Long] {
def this() = this(0, "")
}
class Address(val id: Long, val street: String, val number: Int, val city_id: Long)
extends KeyedEntity[Long] {
def this() = this(0, "", 0, 0)
lazy val city = SpaDb.cities2Addresses.rightStateful(this)
}
object SpaDb extends Schema {
val cities = table[City]("cities")
val addresses = table[Address]("addresses")
val cities2Addresses = oneToManyRelation(cities, addresses).via(_.id === _.city_id)
}
And that's my Servlet:
class SpaServlet extends SpaStack with JacksonJsonSupport {
before() {
contentType = formats("json")
}
get("/addresses") {
Address.all //return all addresses
}
}
When the servlet serializes the object Address, it serializes all attributes, but not the relationship. The result is:
{"id":1,"street":"Street 1","city_id":1}
And what I'd like to receive is:
{"id":1,"street":"Street 1","city_id":1, "city": {"id":1,"name":"MyCity"}}
What can I do to create the json this way?