Why does Gson().toJson() of object with Enumeration throw StackOverflowError? - scala

import Tenum.Tenum
import com.google.gson.Gson
object Temp extends App {
val gson = new Gson()
gson.toJson(new Status("foo", Tenum.X))
System.exit(1)
}
case class Status(id: String, tenum: Tenum)
object Tenum extends Enumeration {
type Tenum = Value
val X = Value
}
I thought it would just print:
{id:"foo", tenum:"X"}

Probably because Scala Enumeration has a field on it that is self referential, and Gson is trying to serialize it, getting stuck in an endless loop.
I'd try serializing Tenum.X.toString (or providing a json field on your enum) or looking around for a Scala Gson wrapper.

Related

Scala inner case class not serializable

I am trying to do a very basic serialization of a very simple case class in Scala:
import org.scalatest.wordspec.AnyWordSpecLike
import java.io.{ByteArrayOutputStream, ObjectOutputStream}
class PersistenceSpec extends AnyWordSpecLike{
case class TestClass(name: String) extends Serializable
def serializeSomething(): ByteArrayOutputStream = {
val testItem = TestClass("My Thing")
val bos: ByteArrayOutputStream = new ByteArrayOutputStream()
val oos = new ObjectOutputStream(bos)
oos.writeObject(testItem)
bos
}
"serializeSomething" when {
"executed" must {
"successfully serialize" in {
val outputStream = serializeSomething()
println(outputStream.toString())
}
}
}
}
When I run this test I get a java.io.NotSerializableException on the call to oos.writeObject(testItem), which makes no sense, since case classes automatically implement Serializable, and this is the simplest possible example.
However, if I paste the code for TestClass and serializeSomething() into repl, I am able to call the function, and it works just fine.
What is different when calling my function via scalatest, vs repl that would cause this exception?
One final note: If I change the call from oos.writeObject(testItem) to oos.writeObject("Hello"), it works fine, even when run from scalatest.
You need to define TestClass outside of PersistenceSpec.
Inner class instances automatically get a reference to the instance of the outer class. So, when you write it out, it tries to serialize the PersistenceSpec instance as well, and that of course fails.

How to test my scala json after creating the implicit classes

I am using specs2 and want to test my json reads that I created.
I have my case classes and implicits created like:
object ComputerImplicits {
implicit val partReads = Json.reads[Part]
implicit val computerReads = Json.reads[Computer]
}
I have a sample json file in my test/resources/computer.json folder.
I am loading the JSON file as a string like this:
val jsonString = Source.fromURL(getClass.getResource("/computer.json")).mkString
I brought the implicits in scope:
import ComputerImplicits._
Now how do I take my case classes and use the json string and attempt to parse it and match it to test if it is working correctly?
I am using Plays json macros https://www.playframework.com/documentation/2.8.x/ScalaJsonAutomated
Assuming you use Play JSON:
final class FooSpec extends org.specs2.mutable.Specification {
"Json" should {
"be ok" in {
Json.parse(jsonString).validate[YourType] must_=== JsSuccess(expectedVal)
}
}
}
Also implicit related to a type are usually declared in its companion object (rather than in a shared object).

Transforming Scala case class into JSON

I have two case classes. The main one, Request, contains two maps.
The first map has a string for both key and value.
The second map has a string key, and value which is an instance of the second case class, KVMapList.
case class Request (var parameters:MutableMap[String, String] = MutableMap[String, String](), var deps:MutableMap[String, KVMapList] = MutableMap[String, KVMapList]())
case class KVMapList(kvMap:MutableMap[String, String], list:ListBuffer[MutableMap[String, String]])
The requirement is to transform Request into a JSON representation.
The following code is trying to do this:
import com.fasterxml.jackson.annotation.PropertyAccessor
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility
import com.fasterxml.jackson.databind.ObjectMapper
def test(req:Request):String {
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.setVisibility(PropertyAccessor.ALL, Visibility.ANY)
var jsonInString: String = null
try {
jsonInString = mapper.writeValueAsString(request)
}
catch {
=case e: IOException => {
e.printStackTrace
}
jsonString
}
This however is not working. Even when the Request class is populated, the output is :
{"parameters":{"underlying":{"some-value":""},"empty":false,"traversableAgain":true},"deps":{"sizeMapDefined":false,"empty":false,"traversableAgain":true}}
Using the JSON object mapper with corresponding Java classes is straightforward, but have not yet got it working in Scala. Any assistance is very much appreciated.
Jackson is more of a bad old memory in Scala to some degree. You should use a native Scala library for JSON processing, particularly one really good at compile time derivation of JSON serializers, such as circe.
I'm aware this doesn't directly answer your question, but after using circe I would never go back to anything else.
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._
val req = new Request(...)
val json = req.asJson.noSpaces
val reparsed = decode[Request](json)
On a different note, using mutable maps inside case classes is as non-idiomatic as it gets, and it should be quite trivial to implement immutable ops for your maps using the auto-generated copy method.
case class Request(parameters: Map[String, String] {
def +(key: String, value: String): Request = {
this.copy(parameters = parameters + (key -> value))
}
}
You should really avoid mutability wherever possible, and it looks like avoiding it here wouldn't be much work at all.
I am not sure what this ScalaObjectMapper does, doesn't look like it is useful.
If you add mapper.registerModule(DefaultScalaModule) in the beginning, it should work ... assuming that by MutableMap you mean mutable.Map, and not some sort of home-made class (because, if you do, you'd have to provide a serializer for it yourself).
(DefaultScalaModule is in jackson-module-scala library. Just add it to your build if you don't already have it).

Scala - Expression of type Object doesn't confirm to type String

I am trying to make this trait as close to idiomatic Scala as possible:
My goal is to mix in this trait in ScalaTest class as
class MyTest extends FunSpect with Matchers With MyTrait
The trait itself:
import java.io.{FileFilter, File}
import com.fasterxml.jackson.core
import com.fasterxml.jackson.databind.node.{JsonNodeFactory, ArrayNode}
import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper}
import com.fasterxml.jackson.core.JsonFactory
trait TicketInfo_Json {
var testJsonOpt: Option[JsonNode] = None
var jsonFileOpt: Option[File] = None
val testJson = {
try {
val jsonFile = new File(this.getClass.getResource("/ticketJson.json").getPath)
testJsonOpt = if(jsonFile.exists() && jsonFile.isFile) Some(new ObjectMapper().readTree(jsonFile)) else None
testJsonOpt.get.toString
} catch {
case ioe: java.io.IOException => Some(new JsonNodeFactory(true).arrayNode()).get
}
}
def getJson: String = testJson // Error: Expression of type Object doesn't confirm to type String
}
}
The goal is to be able to call the method testJson, and my hope is that the logic in the trait will process the ticketJson.json json file and return a json file via getJson back to my calling class. In this class, my Test Class.
However I am getting the error
Expression of type Object doesn't confirm to type String
The reason I am struggling to get this program right, is the fact that I am trying to stay away from vars, but I still cannot wrap my head around the best way to use vals. How would I use Options to good effect. My code has a strong code smell and I do not know how to approach this, as yet.
This is as far as I have got. How do I avoid the error aforementioned?
thanks in advance
Looks like your catch block return non-String value, so variable testJson has type Object instead of String. Try figure out what following code returns:
case ioe: java.io.IOException => Some(new JsonNodeFactory(true).arrayNode()).get
try section returns String and catch section return ArrayNode. So result type of try-catch block is the nearest superclass of String and ArrayNode and it will be Object.

Serialize class as its one member with Jackson

Sometimes the class has only one member to serialize (other members are transient), and I would like to serialize and deserialize this class as its only member.
Consider following code:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
case class Content(i:Seq[Int])
case class Wrap(content:Content)
object Main extends App {
val om = new ObjectMapper() with ScalaObjectMapper {
registerModule(new DefaultScalaModule)
}
val roots = Wrap(Content(Seq(1,2,3)))
val out = om.writeValueAsString(roots)
println(out)
val test = om.readValue(out, classOf[Wrap])
println(test)
}
The result of serialization of Wrapis {"content":{"i":[1,2,3]}}.
I would like to get {"i":[1,2,3]} only. I guess I could do this with custom serializer/deserializer, but given in real case the content is a complex class, this would mean I would have to serialize the content manually, if I am not mistaken. I would prefer some more straightforward solution.
Is it possible to "delegate" the serialization/deserialization to a member/constructor parameter?
It can be done using converters, which can be used to modify Jackson behaviour using JsonSerialize and JsonDeserialize properties.
import com.fasterxml.jackson.databind.annotation.{JsonSerialize, JsonDeserialize}
import com.fasterxml.jackson.databind.util.StdConverter
#JsonDeserialize(converter=classOf[WrapConverterDeserialize])
#JsonSerialize(converter=classOf[WrapConverterSerialize])
case class Wrap(content:Content)
class WrapConverterDeserialize extends StdConverter[Content,Wrap] {
override def convert(value: Content) = new Wrap(value)
}
class WrapConverterSerialize extends StdConverter[Wrap,Content] {
override def convert(value: Wrap) = value.content
}