Serializing a Scala List to JSON in Play2 - json

I am trying to deserialize a list of Scala objects to a JSON map in Play2 - a pretty trivial use case with JSON, I'd say. My JSON output would be something along the lines of:
{
"users": [
{
"name": "Example 1",
"age": 20
},
{
"name": "Example 2",
"age": 42
}
]
}
To achieve this I am looking at the Play2's JSON documentation titled "The Play JSON library". To me their examples are pretty trivial, and I've confirmed that they work for me. Hence, I am able to deserialize a single User object properly.
But making a map containing a list in JSON seems a bit verbose in Play2, when I read the documentation. Is there something I am not grokking?
This is basically my simple Scala code:
case class User(name: String, age: Int)
object UserList {
implicit val userFormat = Json.format[User]
val userList = List(User("Example 1", 20), User("Example 2", 42))
val oneUser = Json.toJson(userList(0)) // Deserialize one Scala object properly to JSON.
// JSON: { "user" : [ <-- put content of userList here. How?
// ]
// }
}
So my question would be; how can I transform the content of the userList List above to a hash in the JSON in a more generic way than explicitly writing out each hash element, as the Play documentation suggests?

scala> import play.api.libs.json._
import play.api.libs.json._
scala> case class User(name: String, age: Int)
defined class User
scala> implicit val userFormat = Json.format[User]
userFormat: play.api.libs.json.OFormat[User] = play.api.libs.json.OFormat$$anon$1#38d2c662
scala> val userList = List(User("Example 1", 20), User("Example 2", 42))
userList: List[User] = List(User(Example 1,20), User(Example 2,42))
scala> val users = Json.obj("users" -> userList)
users: play.api.libs.json.JsObject = {"users":[{"name":"Example 1","age":20},{"name":"Example 2","age":42}]}

Related

Trying unparse json string, but getting Expected start of the object '{', but had 'EOF' instead

I am trying to parse a json file into a list using kotlin serializable.
Here are my data classes.
#Serializable
data class Book(
val epub : String,
val fb2 : String,
val mobi : String,
val djvu : String,
val title : String,
val author : String,
val anotation: String,
val cover_uri : String,
)
#Serializable
data class Books(
#Serializable (with = BookListSerializer::class)
val books : List<Book>
)
object BookListSerializer : JsonTransformingSerializer < List < Book >> ( ListSerializer ( Book.serializer ()))
Here I am trying to parse a string
val books = Json.decodeFromString<Books>(stringJson)
Here my Json String
[
{
"anotation": "Этот город",
"author": "Чарльз Плэтт",
"cover_uri": "null",
"djvu": "null",
"epub": "/b/301494/epub",
"fb2": "/b/301494/fb2",
"mobi": "/b/301494/mobi",
"title": "New York Times (Пульс Нью-Йорка) (fb2)"
},
{
"anotation": "Способна л",
"author": "Триш Уайли",
"cover_uri": "/i/45/390445/cover.jpg",
"djvu": "null",
"epub": "/b/390445/epub",
"fb2": "/b/390445/fb2",
"mobi": "/b/390445/mobi",
"title": "Лучший мужчина Нью-Йорка (fb2)"
}
]
And i always getting this error
kotlinx.serialization.json.internal.JsonDecodingException: Expected start of the object '{', but had 'EOF' instead
JSON input: .....2","mobi":"/b/49442/mobi","title":"I love New York (fb2)"}]
I would be very glad and grateful for any help
tl;dr
Exchange this
val books = Json.decodeFromString<Books>(stringJson)
with this
val books = Json.decodeFromString<List<Book>>(stringJson)
You're trying to deserialize an JSON array [ ... ] but declare an object of type Books as target when calling decodeFromString, thus something like { books: [ ... ] }.
You either have to wrap your JSON array in the property books of an JSON object or change the expected type during deserialization to List<Book>.
Thus, besides the above solution, you could also do the following:
val wrappedStringJson = """
{
"books": $stringJson
}
""".trimIndent()
val books = Json.decodeFromString<Books>(wrappedStringJson)
I experienced the same issue during testing on Ktor Server.
fun testFun() = testApplication { ....
val response = client.get("/boruto/heroes")
val actual = Json.decodeFromString<ApiResponse>(response.content.toString())
....
}
The issue was that I was using this content instead of body.
val actual = Json.decodeFromString<T>(response.content.toString())
I changed it to this and the test passed
val actual = Json.decodeFromString<T>(response.body())
Leaving this here in case someone encounters the same issue.

Scala - how to take json as input arguments and parse it?

I am writing a small scala practice code where my input is going to be in the fashion -
{
"code": "",
"unique ID": "",
"count": "",
"names": [
{
"Matt": {
"name": "Matt",
"properties": [
"a",
"b",
"c"
],
"fav-colour": "red"
},
"jack": {
"name": "jack",
"properties": [
"a",
"b"
],
"fav-colour": "blue"
}
}
]
}
I'll be passing this file as an command line argument.
I want to know that how do I accept the input file parse the json and use the json keys in my code?
You may use a json library such as play-json to parse the json content.
You could either operate on the json AST or you could write case classes that have the same structure as your json file and let them be parsed.
You can find the documentation of the library here.
You'll first have to add playjson as depedency to your project. If you're using sbt, just add to your build.sbt file:
libraryDependencies += "com.typesafe.play" %% "play-json" % "2.6.13"
Play json using AST
Let's read the input file:
import play.api.libs.json.Json
object Main extends App {
// first we'll need a inputstream of your json object
// this should be familiar if you know java.
val in = new FileInputStream(args(0))
// now we'll let play-json parse it
val json = Json.parse(in)
}
Let's extract some fields from the AST:
val code = (json \ "code").as[String]
val uniqueID = (json \ "unique ID").as[UUID]
for {
JsObject(nameMap) ← (json \ "names").as[Seq[JsObject]]
(name, userMeta) ← nameMap // nameMap is a Map[String, JsValue]
} println(s"User $name has the favorite color ${(userMeta \ "fav-colour").as[String]}")
Using Deserialization
As I've just described, we may create case classes that represent your structure:
case class InputFile(code: String, `unique ID`: UUID, count: String, names: Seq[Map[String, UserData]])
case class UserData(name: String, properties: Seq[String], `fav-colour`: String)
In addition you'll need to define an implicit Format e.g. in the companion object of each case class. Instead of writing it by hand you can use the Json.format macro that derives it for you:
object UserData {
implicit val format: OFormat[UserData] = Json.format[UserData]
}
object InputFile {
implicit val format: OFormat[InputFile] = Json.format[InputFile]
}
You can now deserialize your json object:
val argumentData = json.as[InputFile]
I generally prefer this approach but in your case the json structure does not fit really well. One improvement could be to add an additional getter to your InputFile class that makes accesing the fields with space and similar in the name easier:
case class InputFile(code: String, `unique ID`: UUID, count: String, names: Seq[Map[String, String]]) {
// this method is nicer to use
def uniqueId = `unique ID`
}

How to desgin a class for json when I use Gson in Kotlin?

I'm a beginner of Json and Gson, I know I can map json into a class, and map a class to json via Gson.
"My Json" is a json data, I try to design a class "My Class" to map, but I think that "My Class" is not good. Could you show me some sample code? Thanks!
My Class
data class Setting (
val _id: Long,
val Bluetooth_Stauts: Boolean,
val WiFi_Name,String
val WiFi_Statuse: Boolean
)
My Json
{
"Setting": [
{
"id": "34345",
"Bluetooth": { "Status": "ON" },
"WiFi": { "Name": "MyConnect", "Status": "OFF" }
}
,
{
"id": "16454",
"Bluetooth": { "Status": "OFF" }
}
]
}
Updated
The following is made by Rivu Chakraborty's opinion, it can work well, but it's to complex, is there a simple way?
data class BluetoothDef(val Status:Boolean=false)
data class WiFiDef(val Name:String, val Status:Boolean=false)
data class MDetail (
val _id: Long,
val bluetooth: BluetoothDef,
val wiFi:WiFiDef
)
data class MDetailsList(val mListMetail: MutableList<MDetail>)
var mBluetoothDef1=BluetoothDef()
var mWiFiDef1=WiFiDef("MyConnect 1",true)
var aMDetail1= MDetail(5L,mBluetoothDef1,mWiFiDef1)
var mBluetoothDef2=BluetoothDef(true)
var mWiFiDef2=WiFiDef("MyConnect 2")
var aMDetail2= MDetail(6L,mBluetoothDef2,mWiFiDef2)
val mListMetail:MutableList<MDetail> = mutableListOf(aMDetail1,aMDetail2)
var aMDetailsList=MDetailsList(mListMetail)
val json = Gson().toJson(aMDetailsList)
As per your JSON Structure, I think below class definition should work with Gson
data class Setting (
val id: Long,
val Bluetooth: BluetoothDef,
val WiFi:WiFiDef
)
data class BluetoothDef(val Status:String)
data class WiFiDef(val Name:String, val Status:String)
Explanation -
If you're getting an object in your JSON, you should define a class for that to use with Gson.
Data types should match, use String if you're getting Strings like "ON" and "OFF". You can use Boolean if you're getting true and false (without quotes).
The JSON Element name should match the variable/property name unless you're using #SerializedName to define JSON variable name while using different variable/property name.
*Note You can rename the classes if you want
I think it'll be helpful for you

How to get json parent property instead of taken all of same property name in json4s

I use json4s and scala 2.11.12
the exmaple json is:
{ "name": "joe",
"children": [
{
"name": "Mary",
"age": 5
},
{
"name": "Mazy",
"age": 3
}
]
}
when I want to get the name, instead of get parent name "joe", it give me all names of parents and child by(I use json4s library http://json4s.org/)
compact(render(json \\ "name"))
it return me :
res2: String = {"name":"joe","name":"Mary","name":"Mazy"}
I only need {"name":"joe"}
I only need parent name, How to get only parent name?
val json = "..."
import org.json4s._
import org.json4s.native.JsonMethods._
val parent: JValue = json \ "name"
The \ method which with the native implementation of JSON4S will be lift-json based, will look up a field value by name inside a JSON object. Note, your json needs to be a JValue before you can do this, so from a val jsonData: String you need to call val json = Json.parse(jsonData) to get the initial JValue.
The double backslash \\ method will find all children of the JSON that have a given property, so that's why you are getting the entire set of JObject matches back.

How to create a JSON object in Scala?

First, I searched a lot on Google and StackOverflow for questions like that, but I didn't find any useful answers (to my big surprise).
I saw something about Play Framework, how to create JSON array in Java and how to create JSON objects in Java, but I don't want to use Play Framework and I don't know if the creation of JSON objects differ from Scala to Java.
Following is the JSON I want to create. Later I'll convert the object into a string to send it via a POST request (through an API call).
{
"start_relative": {
"value": "5",
"unit": "years"
},
"metrics": [
{
"name": "DP_391366" # S-Temperature - Celsius
},
{
"name": "DP_812682" # Sensor-A4 Luminosity
}
]
}
How can I do something like that in Scala?
You should use a library that handles serialization/deserialization.
I would consider choosing between Spray Json and Play Json.
I will explain to you how the process works with Play first, and it's very similar to that in Spray.
Let's say you have a class, and an object with an instance and a json as string:
case class MyClass(id: Int,
name: String,
description: String)
object Data {
val obj: MyClass = MyClass(1, "me", "awesome")
val str: String =
"""
|{
| "id": 1,
| "name": "me",
| "description": "awesome"
|}
""".stripMargin
}
For MyClass to be serialized/deserialized, you will need an implicit formatter, specific for this, so you will create an object that contains this formatter using Play.
trait MyClassPlayProtocol {
implicit val formatAbility = Json.format[Ability]
}
object MyClassPlayProtocol extends MyClassPlayProtocol
The serialization/deserialization will look something like this:
object PlayData {
import play.api.libs.json.JsValue
import play.api.libs.json.Json
import MyClassPlayProtocol._
import General._
val str2Json: JsValue = Json.parse(str)
val obj2Json: JsValue = Json.toJson(obj)
val json2Str: String = Json.stringify(str2Json)
val json2Obj: MyClass = obj2Json.as[MyClass]
}
In Spray, the protocol will look like this:
trait MyClassSprayProtocol extends DefaultJsonProtocol {
implicit val myClassFormat = jsonFormat3(MyClass)
}
object MyClassSprayProtocol extends MyClassSprayProtocol
and the serialization/deserialization:
object SprayData {
import spray.json._
import MyClassSprayProtocol._
import General._
val str2Json: JsValue = str.parseJson
val obj2Json: JsValue = obj.toJson
val json2Str: String = str2Json.compactPrint
val json2Obj: MyClass = obj2Json.convertTo[MyClass]
}
As you can see, it's mostly a matter of choice between this two. Both are still improved and probably will be in the near future.
Depending on the benchmark, you will find that one is better than the other by a few miliseconds (usually Spray).
I for one am using Spray at work and Play in some personal projects, and I can't say I found something fundamentally different from one to another.
EDIT:
And to finally answer your question, to go from MyClass to String (serialization), you will do something like this:
PLAY: Json.stringify(Json.toJson(myClass))
SPRAY: myClass.toJson.compactPrint
And the deserialization:
PLAY: Json.parse(string).as[MyClass]
SPRAY: myClass.parseJson.convertTo[MyClass]
You need to use a library if you dont want it to do by yourself there are serveral:
Spray Json - https://github.com/spray/spray-json
Lift Json - https://github.com/lift/lift/tree/master/framework/lift-base/lift-json/
Jerkson - https://github.com/codahale/jerkson
Jackson - You can use Jackson with the scala Module https://github.com/FasterXML/jackson-module-scala
Note: The cool Java Gson LIbrary looses a lot of Magic if you want to use it with Scala, because they dont know Collections. So if you want to use this library you have to convert the "Scala List" to java.util.List and after that use Gson.