I'm very new to Scala and I'm trying to send a json from a client to a service.
I have an interface ("trait") where I have the method receiveData takes a JSONObject as an argument, but apparently it's deprecated.
trait DataService {
def receiveData(data: JSONObject, clientId: String): Unit
}
I have this Scala version ThisBuild / scalaVersion := "3.0.0-RC1"
I have looked around for what I should use instead and found nothing.
it says
#deprecated("Use The Scala Library Index to find alternatives: https://index.scala-lang.org/", "1.0.6")
but I don't know how to search for things there.
Appreciate all help!
EDIT:
ANSWER: there are plenty: Play-Json, zio-json, Circe, Jackson...
Related
I'm fairly new to Kotlin and I'm having trouble manipulating a basic JSON string to access its contents. The JSON string looks like this:
"{\"id\":24,\"name\":\"nope\",\"username\":\"unavailable1991\",\"profile_image_90\":\"/uploads/user/profile_image/24/23102ca5-1412-489d-afdf-235c112c7d8e.jpg\",\"followed_tag_names\":[],\"followed_tags\":\"[]\",\"followed_user_ids\":[],\"followed_organization_ids\":[],\"followed_podcast_ids\":[],\"reading_list_ids\":[],\"blocked_user_ids\":[],\"saw_onboarding\":true,\"checked_code_of_conduct\":true,\"checked_terms_and_conditions\":true,\"number_of_comments\":0,\"display_sponsors\":true,\"trusted\":false,\"moderator_for_tags\":[],\"experience_level\":null,\"preferred_languages_array\":[\"en\"],\"config_body_class\":\"default default-article-body pro-status-false trusted-status-false default-navbar-config\",\"onboarding_variant_version\":\"8\",\"pro\":false}"
I've tried using the Gson and Klaxon packages without any luck. My most recent attempt using Klaxon looked like this:
val json: JsonObject? = Klaxon().parse<JsonObject>(jsonString)
But I get the following error: java.lang.String cannot be cast to com.beust.klaxon.JsonObject
I also tried trimming the double quotes (") at the start and end of the string, and also removing all the backslashes like this:
val jsonString = rawStr.substring(1,rawStr.length-1).replace("\\", "")
But when running the same Klaxon parse I now get the following error: com.beust.klaxon.KlaxonException: Unable to instantiate JsonObject with parameters []
Any suggestions (with or without Klaxon) to parse this string into an object would be greatly appreciated! It doesn't matter if the result is a JsonObject, Map or a custom class, as long as I can access the parsed JSON data :)
Gson is perfect library for this kinda task, here how to do it with gson.
Kotlin implementation,
var map: Map<String, Any> = HashMap()
map = Gson().fromJson(jsonString, map.javaClass)
Or if you want to try with Java,
Gson gson = new Gson();
Map<String,Object> map = new HashMap<String,Object>();
map = (Map<String,Object>) gson.fromJson(jsonString, map.getClass());
And also I just tried with your json-string and it is perfectly working,
Kotlin now provides a multiplatform / multi-format reflectionless serialization.
plugins {
kotlin("jvm") version "1.7.10" // or kotlin("multiplatform") or any other kotlin plugin
kotlin("plugin.serialization") version "1.7.10"
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")
}
So now you can simply use their standard JSON serialization library:
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
fun main() {
val jsonString = "{\"id\":24,\"name\":\"nope\",\"username\":\"unavailable1991\",\"profile_image_90\":\"/uploads/user/profile_image/24/23102ca5-1412-489d-afdf-235c112c7d8e.jpg\",\"followed_tag_names\":[],\"followed_tags\":\"[]\",\"followed_user_ids\":[],\"followed_organization_ids\":[],\"followed_podcast_ids\":[],\"reading_list_ids\":[],\"blocked_user_ids\":[],\"saw_onboarding\":true,\"checked_code_of_conduct\":true,\"checked_terms_and_conditions\":true,\"number_of_comments\":0,\"display_sponsors\":true,\"trusted\":false,\"moderator_for_tags\":[],\"experience_level\":null,\"preferred_languages_array\":[\"en\"],\"config_body_class\":\"default default-article-body pro-status-false trusted-status-false default-navbar-config\",\"onboarding_variant_version\":\"8\",\"pro\":false}"
Json.parseToJsonElement(jsonString) // To a JsonElement
.jsonObject // To a JsonObject
.toMutableMap() // To a MutableMap
}
See: Kotlin Serialization Guide for further details.
To do it in Klaxon, you can do:
Klaxon().parse<Map<String,Any>>(jsonString)!!
My question concerns the second solution offered by mixel here: Scala Circe with generics
Note that the trait named Auto in Circe has been renamed to AutoDerivation in the current version of Circe.
I am using the solution mixel provides in his StackOverflow solution but have not been able to get it to work. I have tried things like updating my Circe version to the most recent one and making sure the Macro Paradise plugin is imported, but still no luck.
Here is my code. The first is its own file, called CirceGeneric.
import io.circe._
import io.circe.parser._
import io.circe.generic.extras._
object CirceGeneric {
trait JsonEncoder[T] {
def apply(in: T): Json
}
trait JsonDecoder[T] {
def apply(s: String): Either[Error, T]
}
object CirceEncoderProvider {
def apply[T: Encoder]: JsonEncoder[T] = new JsonEncoder[T] {
def apply(in: T) = Encoder[T].apply(in)
}
}
object CirceDecoderProvider {
def apply[T: Decoder]: JsonDecoder[T] = new JsonDecoder[T] {
def apply(s: String) = decode[T](s)
}
}
}
object Generic extends AutoDerivation {
import CirceGeneric._
implicit def encoder[T: Encoder]: JsonEncoder[T] = CirceEncoderProvider[T]
implicit def decoder[T: Decoder]: JsonDecoder[T] = CirceDecoderProvider[T]
}
The second is a method for unit testing that uses the Akka function responseAs. The method appears in a class called BaseServiceTest.
def responseTo[T]: T = {
def response(s: String)(implicit d: JsonDecoder[T]) = {
d.apply(responseAs[String]) match {
case Right(value) => value
case Left(error) => throw new IllegalArgumentException(error.fillInStackTrace)
}
}
response(responseAs[String])
}
The idea is to convert the result of responseAs[String] (which returns a string) into a decoded case class.
The code is not behaving as expected. Intellij does not detect any missing implicits, but when compilation time comes around, I am getting problems. I should mention that the BaseServiceTest file contains imports for CirceGeneric._ and Generic._, so a missing import statement is not the problem.
[error] [...]/BaseServiceTest.scala:59: could not find implicit value for parameter d: [...]CirceGeneric.JsonDecoder[T]
[error] response(responseAs[String])
Either the implicit conversion from Decoder[T] to JsonDecoder[T] is not happening, or the Decoder[T] instance is not being created. Either way, something is wrong.
You still need a Decoder or JsonDecoder context bound on responseTo.
def responseTo[T : Decoder]: T = ...
This is because all your code, and indeed mixel's code in the linked answer, is about abstracting from a Decoder out to a JsonDecoder trait which can be used for cross-library support. But you still don't have any way of constructing one without an underlying Decoder instance.
Now, there are some ways of automatically generating Decoders for (for instance) case classes contained in circe.generics.auto, but at this point in your code
def responseTo[T]: T = {
def response(s: String)(implicit d: JsonDecoder[T]) = ...
...
}
you're asking the compiler to be able to provide an implicit JsonDecoder (i.e., in your setup, Decoder) instance for any arbitrary type. As the accepted answer to the linked question explains, that's not possible.
You need to delay the implicit resolution to the point where you know what type you're dealing with - in particular, that you can provide a Decoder[T] instance for it.
EDIT: In your response to your comment regarding what the point is if you can't create JsonDecoders for all types...
My understanding of the linked question is that they're trying to abstract away the circe library in order to allow swapping out the JSON library implementation. This is done as follows:
add the JsonDecoder type class
have a package json which contains implicits (using Circe) for constructing them automatically via the package object extending AutoDerivation
have external code only refer to JsonDecoder and import the implicits in the json package
Then all the JSON serialization and implicit resolution works out without ever needing the calling code to reference io.circe, and it's easy to switch over the json/JsonDecoder to another JSON library if desired. But you're still going to have to use the JsonDecoder context bound, and be restricted to working with types where such an implicit can be constructed. Which is not every type.
I have HTTP client written in Scala that uses json4s/jackson to serialize and deserialize HTTP payloads. For now I was using only Scala case classes as model and everything was working fine, but now I have to communicate with third party service. They provided me with their own model but its written in Java, so now I need to deserialize jsons also to Java classes. It seams to work fine with simple classes but when class contains collections like Lists or Maps json4s has problems and sets all such fields to null.
Is there any way to handle such cases? Maybe I should use different formats (I'm using DefaultFormats + few custom ones). Example of problem with test:
import org.json4s.DefaultFormats
import org.json4s.jackson.Serialization.read
import org.scalatest.{FlatSpec, Matchers}
class JavaListTest extends FlatSpec with Matchers{
implicit val formats = DefaultFormats
"Java List" should "be deserialized properly" in {
val input = """{"list":["a", "b", "c"]}"""
val output = read[ObjectWithList](input)
output.list.size() shouldBe 3
}
}
And sample Java class:
import java.util.List;
public class ObjectWithList {
List<String> list;
}
I have also noticed that when I'll try to deserialize to Scala case class that contains java.util.List[String] type of field I'll get an exception of type: org.json4s.package$MappingException: Expected collection but got List[String]
Key for solving your issue, is composition of formatters. Basically you want to define JList formatter as list formatter composed with toJList function.
Unfortunately, json4s Formatters are extremely difficult to compose, so I used the Readers for you to get an idea. I also simplified an example, to having only java list:
import DefaultReaders._
import scala.collection.JavaConverters._
implicit def javaListReader[A: Reader]: Reader[java.util.List[A]] = new Reader[util.List[A]] {
override def read(value: JValue) = DefaultReaders.traversableReader[List, A].read(value).asJava
}
val input = """["a", "b", "c"]"""
val output = Formats.read[java.util.List[String]](parse(input))
To my knowledge json4s readers will not work with java classes out of the box, so you might either need to implement the Serializer[JList[_]] the same way, or mirror your java classes with case classes and use them inside your domain.
P.S.
Highly recommend you to switch to circe or argonaut, then you will forget about the most problems with jsons.
I'm trying to use JSON-inception for vaildating a request inside a RESTful application. But it seems that either the JSON-request or the JSON-inception logic is faulty.
some fragments of the necessary code(simplified):
(JsPath \ "geometry").readNullable[Geometry]
case class Geometry(
circle : Option[Circle]
)
object Geometry{
implicit val circleReads: Format[Circle] = Json.format[Circle]
}
case class Circle(radius: Double)
The JSON-String i am using:
"geometry":{
"circle":{"radius":1.1},
}
Posting this request is resulting in a NullPointerException at this place
request.body.validate[Calculation]
Tests showed that the validation recognizes the "circle"-path in the JSON-string because there is no reaction to other names at this point.
Am i missing something or do i misunderstand the JSON-inception approach? The playframework version is 2.4.6.
Is there a way and/or library to automatically create Kotlin Data class from Json like it is works in Scala Json.Spray?
Something like this:
data class User(id: Int, name: String)
class DataClassFactory(val json: String) {
fun getUser(): User {
//some reflection
return User(10, "Kirill")
}
}
fun main(args: Array<String>): Unit {
val json = "{id: 10, name: Kirill}"
val usr = DataClassFactory(json).getUser()
println(usr)
}
You can use the Jackson module for Kotlin to serialize/deserialize easily from any format that Jackson supports (including JSON). This is the easiest way, and supports Kotlin data classes without annotations. See https://github.com/FasterXML/jackson-module-kotlin for the module which includes the latest information for using from Maven and Gradle (you can infer IVY and download JARs from the Maven repositories as well)
Alternatives exist such as Boon, but it has no specific support for Kotlin (usually a problem with not having a default constructor) and uses some unsafe direct access to internal JVM classes for performance. In doing so, it can crash on some VM's, and in cases where you extend Boon from Kotlin with custom serializer/deserializer it makes assumptions about the classes that do not hold true in Kotlin (the String class is wrapped for example) which I have seen core dump. Boon is lightening fast, just be careful of these issues and test first before using.
(note: I am the creator of the Jackson-Kotlin module)
This is very clean and easy in Kotlin.
import com.fasterxml.jackson.module.kotlin.*
data class User(val id: Int, val name: String)
fun main(args: Array<String>) {
val mapper = jacksonObjectMapper()
val json = """{"id": 10, "name": "Kirill"}"""
val user = mapper.readValue<User>(json)
println(user)
}
produces this output:
User(id=10, name=Kirill)
you only have to add this to your pom.xml
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
<version>2.6.3-4</version>
</dependency>
Why not use Jackson or any other serializer? It should work..
What about this
This is a translator that translate JSON string into kotlin data class ,it make it throught a plugin,see next
https://plugins.jetbrains.com/plugin/9960-jsontokotlinclass
http://www.json2kotlin.com converts your json response to kotlin data classes online, without the need to install any plugin. Optionally, you can generate gson annotations too. (Disclosure: I created this utility)