remove tpe from json after serialization via scala-pickling - json

Is there an easy way to serialize to json without "tpe" field inside an object?
I need to serialize case classes to json structures and then, send them over the wire (They won't been deserialized in future). I have a specific api, so.. I don't need additional fields.
For class Person illustrated below:
case class Person(Name: String, Age: int, CandyLover: Boolean)
I'd like to see following:
{
"Name": "Paul",
"Age": 23,
"CandyLover": true
}

I would suggest you to take a look at spray-json or argonaut. Or play-json if you are already using Play.
Scala pickling is not really a json library, it was not created to generate JSON for public API, it's not flexible, you can't define JSON protocol first and then provide pickling serialization to match protocol. Pickling is for computer-to-computer serialization where no one is really care about bytes going between apps.

Probably the simplest way to do it is to pass somehow the Hint into JSONPickleBuilder. Quick way to do it is to override builders in JSONPickleFormat by calling hintStaticallyElidedType() method from PickleTools.
Here is kind of code snippet to demonstrate it:
import org.specs2.matcher.JsonMatchers
import org.specs2.mutable.Specification
import org.specs2.specification.Scope
import scala.pickling.Defaults._
import scala.pickling.json.{JSONPickleBuilder, JSONPickleFormat, JsonFormats}
import scala.pickling.{Output, PBuilder, PickleTools, StringOutput}
class PersonJsonFormatsTest extends Specification with JsonMatchers {
trait Context extends Scope with PersonJsonFormats
trait PersonJsonFormats extends JsonFormats {
override implicit val pickleFormat: JSONPickleFormat = new PersonJSONPickleFormat
}
class PersonJSONPickleFormat extends JSONPickleFormat {
override def createBuilder() = new JSONPickleBuilder(this, new StringOutput) with PickleTools {
hintStaticallyElidedType()
}
override def createBuilder(out: Output[String]): PBuilder = new JSONPickleBuilder(this, out) with PickleTools {
hintStaticallyElidedType()
}
}
"Pickle" should {
"Serialize Person without $type field in resulting JSON" in new Context {
case class Person(Name: String, Age: Int, CandyLover: Boolean)
val pickledPersonObject = Person("Paul", 23, CandyLover = true).pickle
println(pickledPersonObject.value)
pickledPersonObject.value must not */("$type" → ".*".r)
}
}
}
The output of println(pickledPersonObject.value) will be as you need:
{
"Name": "Paul",
"Age": 23,
"CandyLover": true
}
This way you will stay aligned with further pickle updates.
P.S. If someone knows more elegant and convenient way to reach the same behaviour - please let us know :-)

For scala-pickling 0.10.x:
import scala.pickling._
import scala.pickling.json.{JSONPickle, JSONPickleBuilder, JSONPickleFormat, JsonFormats}
import scala.pickling.pickler.AllPicklers
object serialization extends JsonFormats with Ops with AllPicklers {
override implicit val pickleFormat: JSONPickleFormat = new JSONPickleFormat {
private def setHints(h: Hintable): Unit = {
h.hintStaticallyElidedType()
h.hintDynamicallyElidedType()
}
override def createBuilder(): JSONPickleBuilder = {
val builder = super.createBuilder()
setHints(builder)
builder
}
override def createBuilder(out: Output[String]): PBuilder = {
val builder = super.createBuilder(out)
setHints(builder)
builder
}
override def createReader(pickle: JSONPickle): PReader = {
val reader = super.createReader(pickle)
setHints(reader)
reader
}
}
}
object SerializationTest extends App {
import serialization._
case class Person(firstName: String, lastName: String)
val pickle: JSONPickle = Person("Evelyn", "Patterson").pickle
val jsonString: String = pickle.value // {"firstName": "Evelyn","lastName": "Patterson"}
val person: Person = jsonString.unpickle[Person]
}
For scala-pickling 0.11.x:
import scala.pickling._
import scala.pickling.json.{JSONPickle, JSONPickleBuilder, JSONPickleFormat}
import scala.pickling.pickler.AllPicklers
object serialization extends AllPicklers {
private final class CustomJSONPickleFormat(tag: FastTypeTag[_]) extends JSONPickleFormat {
private def setHints(h: Hintable) {
h.hintElidedType(tag)
}
override def createBuilder(): JSONPickleBuilder = {
val b = super.createBuilder()
setHints(b)
b
}
override def createBuilder(out: Output[String]): PBuilder = {
val b = super.createBuilder(out)
setHints(b)
b
}
override def createReader(pickle: JSONPickle): PReader = {
val b = super.createReader(pickle)
setHints(b)
b
}
}
implicit val staticOnly = static.StaticOnly // for compile time serialization methods generation
implicit final class EncodeDecodeOps[T](picklee: T) {
def encode(implicit pickler: Pickler[T]): String = {
val pickleFormat = new CustomJSONPickleFormat(pickler.tag)
functions.pickle(picklee)(pickleFormat, pickler).value
}
def decode[A](implicit c: T => String, unpickler: Unpickler[A]): A = {
val pickleFormat = new CustomJSONPickleFormat(unpickler.tag)
functions.unpickle[A](json.JSONPickle(picklee))(unpickler, pickleFormat)
}
}
}
case class Person(firstName: String, lastName: String) {
#transient var x = "test"
}
object SerializationTest extends App {
import serialization._
val jsonString = Person("Lisa", "Daniels").encode
println(jsonString)
val person = jsonString.decode[Person]
println(person)
}

Related

Error parsing fragment to recyclerview in Kotlin

As part of one of my courses, I am using an API in Android Studio for the first time. For this I use OkHttp. However, I have the following error when I launch my application. I guess it's because the JSON is not parsed well but I can't find a solution.
If anyone could help me that would be great!
Thank you
The error screenshot
MyMarqueRecyclerViewAdapter.kt
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.google.gson.GsonBuilder
import kotlinx.android.synthetic.main.fragment_marque_list.*
import okhttp3.*
import java.io.IOException
/**
* A fragment representing a list of Items.
*/
class MarqueFragment : Fragment(), OnMarqueClickListener {
private var columnCount = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
columnCount = it.getInt(ARG_COLUMN_COUNT)
}
fetchJson()
}
fun fetchJson() {
val url = "https://tp3.infomobile.app/api/v1/brand"
val request = Request.Builder().url(url).build()
val lesmarques = OkHttpClient()
lesmarques.newCall(request).enqueue(object: Callback {
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
println(body)
val gson = GsonBuilder().create()
val homeFeed = gson.fromJson(body, HomeFeed::class.java)
println(homeFeed)
activity?.runOnUiThread {
recyclerView_main.adapter = MyMarqueRecyclerViewAdapter(homeFeed)
}
}
override fun onFailure(call: Call, e: IOException) {
println("Failed to execute request")
}
})
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_marque_list, container, false)
// Set the adapter
if (view is RecyclerView) {
with(view) {
layoutManager = when {
columnCount <= 1 -> LinearLayoutManager(context)
else -> GridLayoutManager(context, columnCount)
}
//adapter = MyMarqueRecyclerViewAdapter(homeFeed)
}
}
return view
}
override fun onMarqueItemClicked(position: Int) {
Toast.makeText(this.context, "ça marche", Toast.LENGTH_LONG).show()
val intent = Intent(this#MarqueFragment.requireContext(),MainActivity2::class.java)
startActivity(intent)
}
companion object {
// TODO: Customize parameter argument names
const val ARG_COLUMN_COUNT = "column-count"
// TODO: Customize parameter initialization
#JvmStatic
fun newInstance(columnCount: Int) =
MarqueFragment().apply {
arguments = Bundle().apply {
putInt(ARG_COLUMN_COUNT, columnCount)
}
}
}
}
class HomeFeed(val marques: List<Marque>)
class Marque(val id: Int, val name: String)
MarqueFragment.kt
import android.content.Intent
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import ca.ulaval.ima.tp3.placeholder.PlaceholderContent.PlaceholderItem
import ca.ulaval.ima.tp3.databinding.FragmentMarqueBinding
/**
* [RecyclerView.Adapter] that can display a [PlaceholderItem].
* TODO: Replace the implementation with code for your data type.
*/
class MyMarqueRecyclerViewAdapter(val homeFeed: HomeFeed?) : RecyclerView.Adapter<MyMarqueRecyclerViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
FragmentMarqueBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = homeFeed?.marques?.get(position)
holder.contentView.text = item.toString()
holder.contentView.setOnClickListener{
val context=holder.contentView.context
val intent = Intent( context, MainActivity2::class.java)
intent.putExtra("marque", item.toString())
context.startActivity(intent)
}
}
override fun getItemCount(): Int {
return homeFeed?.marques!!?.count()
}
inner class ViewHolder(binding: FragmentMarqueBinding) : RecyclerView.ViewHolder(binding.root) {
val contentView: TextView = binding.content
override fun toString(): String {
return super.toString() + " '" + contentView.text + "'"
}
}
}
First of all, you can trace the crash using the log which clearly states that the crash happened at line 42 in MyMarqueRecyclerViewAdapter.kt, in the getItemCount function, which means that this functions is causing it:
override fun getItemCount(): Int {
return homeFeed?.marques!!?.count()
}
What's happening here is that you're forcing kotlin to treat a property that might be null as a non null property, by using the "!!" operator, this tells kotlin that this propery would never be null in any case, and that isn't true in your case since its throwing a NullPointerException, what you actually need to do is allow it to be null and provide an alternative value in case it was null, using the elvis operator, like this:
override fun getItemCount(): Int {
return homeFeed?.marques?.count() ?: 0
}
Another thing is that you're using the count() function, i think you meant to use the size property instead:
override fun getItemCount(): Int {
return homeFeed?.marques?.size ?: 0
}
Second, i noticed your class declaration and compared the content to the json's response content, it seems that the json response conatins a parameter called "content", while the propery in the HomeFeed class is called "marques", which results in GSON not knowing where to get "marques" from, it only knows that there is a parameter called "content" in the json and it doesn't know what to do with it, the best solution would be to annotate the HomeFeed.marques property with a #SerializedName annotation, and provide the corresponding json parameter that should be mapped to this property, like so:
class HomeFeed(
#SerializedName("content")
val marques: List<Marque>
)
class Marque(
#SerializedName("id")
val id: Int,
#SerializedName("name")
val name: String
)
Hope this helps!
Some resources:
Null Safety | Kotlin
Difference between list.count() and list.size
Parsing between Kotlin classes and Json objects using GSON

Extracting a case class with an upper bound

I want to extract a case class from a JSON String, and reuse the code for every class.
Something like this question would have been perfect. But this means that I have to write for every class I want to extract.
I was hoping to do something like:
abstract class SocialMonitorParser[C <: SocialMonitorData] extends Serializable {
def toJSON(socialMonitorData: C): String = {
Requirements.notNull(socialMonitorData, "This field cannot be NULL!")
implicit val formats = DefaultFormats
write(socialMonitorData)
}
def fromJSON(json: String): Option[C] = {
implicit val formats = DefaultFormats // Brings in default date formats etc.
val jsonObj = liftweb.json.parse(json)
try {
val socialData = jsonObj.extract[C]
Some(socialData)
} catch {
case e: Exception => {
Logger.get(this.getClass.getName).warn("Unable to parse the following JSON:\n" + json + "\nException:\n" + e.toString())
None
}
}
}
}
But it gives me the following error:
Error:(43, 39) No Manifest available for C.
val socialData = jsonObj.extract[C]
Error:(43, 39) not enough arguments for method extract: (implicit formats: net.liftweb.json.Formats, implicit mf: scala.reflect.Manifest[C])C.
Unspecified value parameter mf.
val socialData = jsonObj.extract[C]
I was hoping I could do something like this, and maybe there is a way. But I can't wrap my head around this.
I will try to extend the question with some other information. Supposing I have Twitter and Facebook data, in case class like these:
case class FacebookData(raw_data: String, id: String, social: String) extends SocialMonitorData
case class TwitterData(...) extends SocialMonitorData{ ...}
I wish I could reuse the fromJSON and toJSON just once passing the Upper Bound type
class TwitterParser extends SocialMonitorParser[TwitterData] {
override FromJSON}
class FacebookParser extends SocialMonitorParser[FacebookData]
Much obliged.
I'm not sure why you want SocialMonitorParser to be abstract or Serializable or how are you going to use it but if you look closer at the error you may see that the compilers wants a Manifest for C. Manifest is a Scala way to preserve type information through the type erasure enforced onto generics by JVM. And if you fix that, then the code like this compiles:
import net.liftweb.json._
import net.liftweb.json.Serialization._
trait SocialMonitorData
case class FacebookData(raw_data: String, id: String, social: String) extends SocialMonitorData
class SocialMonitorParser[C <: SocialMonitorData : Manifest] extends Serializable {
def toJSON(socialMonitorData: C): String = {
// Requirements.notNull(socialMonitorData, "This field cannot be NULL!")
implicit val formats = DefaultFormats
write(socialMonitorData)
}
def fromJSON(json: String): Option[C] = {
implicit val formats = DefaultFormats // Brings in default date formats etc.
val jsonObj = parse(json)
try {
val socialData = jsonObj.extract[C]
Some(socialData)
} catch {
case e: Exception => {
// Logger.get(this.getClass.getName).warn("Unable to parse the following JSON:\n" + json + "\nException:\n" + e.toString())
None
}
}
}
}
and you can use it as
def test(): Unit = {
val parser = new SocialMonitorParser[FacebookData]
val src = FacebookData("fb_raw_data", "fb_id", "fb_social")
println(s"src = $src")
val json = parser.toJSON(src)
println(s"json = $json")
val back = parser.fromJSON(json)
println(s"back = $back")
}
to get the output exactly as one would expect.

JSON response with Http AKKA and case classes

I would like to build a simple stub server with several routes using Http AKKA.
I have the following case class:
case class Person(name: String, age: Int)
object Person {
implicit def cardJsonFormat: RootJsonFormat[Person] = jsonFormat2(Person.apply)
}
How can I return this case class as a JSON response.
My route looks like:
case class Person(name: String, age: Int)
def route =
get {
path("person") {
complete {
}
}
}
You should try something like this:
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.server.Directives._
import spray.json.DefaultJsonProtocol
case class Person(name: String, age: Int)
case object Person extends SprayJsonSupport with DefaultJsonProtocol {
implicit val personFormat = jsonFormat2(Person.apply)
}
object PersonRoute {
val route =
get {
path("person") {
complete {
Person("Pawel", 25)
}
}
}
}
More details and examples can be found in the docs.
I found the following library akka-http-json used with json4s useful. It helps me cut down on the number of jsonFormatX statements. Every jsonFormatX statement is needed for every data type that needs to be marshalled / unmarshalled.
Just mix in the following trait where the marshall / unmarshall needs to occur:
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s.jackson
trait JsonCodec extends Json4sSupport {
import org.json4s.DefaultFormats
import org.json4s.ext.JodaTimeSerializers
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats ++ JodaTimeSerializers.all
}

How do you exclude fields in Jackson json serialisation (Scala)

I am trying to exclude the _passthroughFields property in the example below. When I use the debugger, it looks like my PropertyFilter is never used. What am I doing wrong?
import java.util
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter.SerializeExceptFilter
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider
import com.fasterxml.jackson.databind.{DeserializationFeature, ObjectMapper, ObjectWriter}
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import org.scalatest.{Matchers, WordSpec}
import scala.collection.immutable.Map
class PassthroughFieldsSpec extends WordSpec with Matchers {
"JacksonParser" when {
"given an Object and undesired fields" should {
"not include those fields in the json response" in {
trait Foo {
def id: String
def _passthroughFields: Map[String, String] = Map.empty
}
class Bar(val id: String, override val _passthroughFields: Map[String, String]) extends Foo
val item = new Bar("abcd", Map.empty)
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
val excludes = new util.HashSet[String](1)
excludes.add("_passthroughFields")
excludes.add("get_passthroughFields")
val filters = new SimpleFilterProvider()
.addFilter("filter properties by name", new SerializeExceptFilter(excludes))
val writer: ObjectWriter = mapper.writer(filters)
val json = writer.writeValueAsString(item)
json.contains("_passthroughFields") shouldBe false
}
}
}
}
I think you can exclude it using something like #JsonIgnore or make the field transient.
Otherwise if you need to define it outside of the case class code (like in your example) you can do it with Genson.
import com.owlike.genson._
import com.owlike.genson.reflect.VisibilityFilter
// Note that if you use case classes you don't need the visibility filter stuff
// it is used to indicate that private fields should be ser/de
val genson = new GensonBuilder()
.withBundle(ScalaBundle())
.useFields(true, VisibilityFilter.PRIVATE)
.exclude("_passthroughFields")
.create()
genson.toJson(item)
Disclaimer: I am Gensons author.

Spray json marshalling

I'm on a quest to create a JSON API where some of the models could be nicely generalized. I'm a newbie in Spray, so I started out a spike with an over simplified example.
However I can't figure out what is going on with the bellow code...
I have imported both
my custom implicits and
spray.httpx.SprayJsonSupport._
As I understand this is what I have to do in order to have an implicit in scope that can convert from JsonFormat to Marshaller.
Compiler error:
TestService.scala:15: could not find implicit value for parameter um: spray.httpx.unmarshalling.FromRequestUnmarshaller[my.company.Test[my.company.X]]
Code:
package my.company
import spray.routing.HttpService
import spray.json.{JsValue, JsObject, JsonFormat, DefaultJsonProtocol}
trait TestService extends HttpService {
import my.company.TestImplicits._
import spray.httpx.SprayJsonSupport._
val test =
path("test") {
post {
entity(as[Test[X]]) {
test => {
complete(s"type: ${test.common}")
}
}
}
}
}
trait Common {
def commonData: String
}
case class X(id: Long, commonData: String) extends Common
case class Y(commonData: String) extends Common
case class Test[T <: Common](comment: String, common: T)
object TestImplicits extends DefaultJsonProtocol {
implicit val xFormat = jsonFormat2(X)
implicit val yFormat = jsonFormat1(Y)
implicit val yTestFormat: JsonFormat[Test[Y]] = new JsonFormat[Test[Y]] {
def write(test: Test[Y]) = JsObject()
def read(js: JsValue) = Test("test", Y("y"))
}
implicit val xTestFormat: JsonFormat[Test[X]] = new JsonFormat[Test[X]] {
def write(test: Test[X]) = JsObject()
def read(js: JsValue) = Test("test", X(1L, "y"))
}
}
I would appreciate any help. Thanks in advance.
SOLVED
Solution was (as #jrudolp suggested) both to:
Move implicit definitions on top of the file (surprising)
Create RootJsonFormat rather than JsonFormat.