Extracting certain fields from scala object <-> Json - json

I'm trying to extract certain fields from Scala object before converting to Json. Is there an easy way to do this.
It would also work if i could make a new Json with certain fields from a Json.

You can simply extract out the value of a Json and scala gives you the corresponding map. Example:
var myJson = Json.obj(
"customerId" -> "xyz",
"addressId" -> "xyz",
"firstName" -> "xyz",
"lastName" -> "xyz",
"address" -> "xyz"
)
Suppose you have the Json of above type. To convert it into map simply do:
var mapFromJson = myJson.value
This gives you a map of type : scala.collection.immutable.HashMap$HashTrieMap

Hard to say without more details. Suppose that you have the following Scala case class...
case class SomeObject(customerId: Long, addressId: Long, firstName: String, lastName: String, address: String)
...and that you wanted to extract the 'firstName', 'lastName', and address fields and then convert the object to Json. Using play-json you could define an implicit conversion on the companion object for the SomeObject class...
object SomeObject {
implicit val someObjectWrites = new Writes[SomeObject] {
def writes(object: SomeObject) = Json.obj(
"firstName" -> object.firstName,
"lastName" -> object.lastName,
"address" -> object.address
)
}
}
Then you could just use the code as follows:
val obj = SomeObject(12345, 678910, "John", "Doe", "My Address")
val json = Json.toJson(obj)
Note that there are probably other JSON libraries, besides play-json, that support similar functionality.

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.

Deserializing complex nested JSON into Scala objects

Scala 2.12 here. I'm trying to use Lift-JSON to deserialize some JSON into a Scala object and am having trouble navigating the Lift API. Please note: I'm not married to Lift-JSON, any other working solution will be accepted so long as I don't have to bring any heavy/core Play dependencies into my project.
Here's the JSON file I'm trying to read:
{
"fizz" : "buzz",
"foo" : [
"123",
"456",
"789"
],
"bar" : {
"whistle" : 1,
"feather" : true
}
}
Here's my Scala object hierarchy:
case class Bar(whistle : Integer, feather : Boolean)
case class MyConfig(fizz : String, foo : Array[String], bar : Bar)
And finally my best attempt at the codeup for this:
def loadConfig(configFilePath : String) : MyConfig = {
val configJson = Source.fromFile(configFilePath)
val parsedJson = parse(configJson.mkString)
MyConfig(???)
}
I need validation in place so that if the JSON is not valid an exception is thrown. Any ideas how I can extract fields out of parsedJson and use them to set values for my MyConfig instance? And how to perform the validation?
Have you tried parsedJson.extract[MyConfig]? That is straight out of the Extracting values documentation. If you haven't already, you will need to specify an implicit reference to the default formats:
implicit val formats = DefaultFormats

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

Error in json serialization in Scala with Play2

i have following three case classes
case class Delete(var deleteStatus : DeleteStatus , var deleteReason : DeleteReason) // DeleteStatus and DeleteReason are enums
case class Message(val uuid: Int ,val subject : String, val body : String, var awt : Int,val dateTime : LocalDateTime = LocalDateTime.now(), delete : Delete)
case class Inbox( val uuid : Int,var messageList : ListBuffer[Message] )
i want to serialize them to Json and but i am not sure how should i do this
i have tried it like this
def writedelete(delete: Delete) = Json.obj(
"deleteStatus" -> delete.getDeleteStatusInt.toString,
"deleteReason" -> delete.getDeleteReasonInt.toString
)
def writeMessage(mgs : Message)= Json.obj(
"uuid" -> mgs.getUuid ,
"subject" -> mgs.getSubject,
"body" -> mgs.getBody,
"awt" -> mgs.getAwt,
"datetime" -> mgs.getdateTime.toString,
"delete" -> mgs.delete
)
def writeInbox(inbox : Inbox)= Json.obj(
"uuid" -> inbox.getUuid,
"mgslist" -> Seq(inbox.getMessageList)
)
but it gives following error on mgs.delete in writeMessage and mgslist in writeInbox
type mismatch; found : models.UserNotifications.MailMessages.Delete
required: play.api.libs.json.Json.JsValueWrapper
type mismatch; found :
Seq[scala.collection.mutable.ListBuffer[models.UserNotifications.MailMessages.Message]]
required: play.api.libs.json.Json.JsValueWrapper
please guide me how can i get get rid off it
and also is there any better way of doing this?
When you use Json.obj(...) to construct a JSON object various implicit conversions are available to convert common types (e.g. String, Int) to their JSON wrapper types (JsString, JsNumber). The problem with your code is there are no implicit conversions available to convert your Delete and Message types to JSON. One option would be to use your explicit conversion functions directly, e.g:
"delete" -> writeDelete(mgs.delete)
and (using Json.arr(...) to construct a JSON array):
"msglist" -> Json.arr(inbox.getMessageList.toSeq.map(writeMessage): _*)
However, the more idiomatic way to do this would be to use the JSON Inception macros to automatically generate (implicit) serializers for your types.
Simplifying slightly, this would look something like this:
case class Delete(deleteStatus: DeleteStatus, deleteReason: DeleteReason)
object Delete {
implicit val _format = Json.format[Delete]
}
case class Message(uuid: Int, subject: String, body: String, awt: Int, dateTime: LocalDateTime, delete: Delete)
object Message {
implicit val _format = Json.format[Message]
}
case class Inbox(uuid: Int, messageList: ListBuffer[Message])
object Inbox {
implicit val _format = Json.format[Message]
}
You should now be able to automatically serialize (and de-serialize) your Delete, Message, and Inbox objects using Json.toJson(thing) because it will find an implicit Format object (a combined Reads and Writes) on the companion object of each custom type.
One complication here is that your case classes contain enums; if they're Scala enumerations see this answer for how to convert them. I leave that as an exercise for the reader.

How do you create Json object with values of different types?

How do you create Json object with values of different types ?
I'm using spray-json
Here is the code
val images : List[JsObject] = fetchImageUrls(url).map((url: String) => {
JsObject(List(
"link_path" -> JsString(url),
"display_name" -> JsString("image"),
"size" -> JsString(""),
"modified" -> JsString(""),
"thumbnail" -> JsString(url),
"filename" -> JsString("image"),
"is_dir" -> JsBoolean(x = false),
"thumb_exists" -> JsBoolean(x = true)) )
})
val jsonAst: JsObject = JsObject(List(
"client" -> JsString("urlimages"),
"view" -> JsString("thumbnails"),
"contents" -> JsArray(images)
))
It works but looks really heavy. Is there a way to define json with code like this ?
val images : List[List[(String, Any)]] = fetchImageUrls(url).map((url: String) => {
List(
"link_path" -> url,
"display_name" -> "image",
"size" -> "",
"modified" -> "",
"thumbnail" -> url,
"filename" -> "image",
"is_dir" -> false,
"thumb_exists" -> true)
})
val jsonAst = List(
"client" -> "urlimages",
"view" -> "thumbnails",
"contents" -> images
).toJson
It doesn't work saying that
Cannot find JsonWriter or JsonFormat type class for List[(String, Object)]
).toJson
^
Which I get, type of each field is not defined at compile time. But why wouldn't it work if serializer does pattern matching anyway ?
Thanks!
I agree with #alex23 that a case class based approach will be better. Using spray-json, you would first define your case class structure as well as an extension of DefaultJsonProtocol to describe the case classes you want to be able to serialize. That would look like this:
case class Image(link_path:String, display_name:String, size:Option[String],
modified:Option[String], thumbnail:String, filename:String, is_dir:Boolean, thumb_exists:Boolean)
object Image
case class UrlImages(client:String, view:String, contents:List[Image])
object UrlImages
object MyJsonProtocol extends DefaultJsonProtocol {
implicit val imageFormat = jsonFormat8(Image.apply)
implicit val urlImagesFormat = jsonFormat3(UrlImages.apply)
}
Then, a modified version of your example would look like this:
import MyJsonProtocol._
val images : List[Image] = fetchImageUrls(url).map((url: String) => {
Image(url, "image", None, None, url, "image", false, true)
})
val jsonAst = UrlImages("urlimages", "thumbnails", images).toJson
The reason why you were seeing that error is that spray-json does not know how to serialize the Lists of tuples you are creating. If you really want to use that structure and not go the case class route, then you could look into providing a custom serializer for List[(String,Any)]. Check out the section in the spray-json docs titled "Providing JsonFormats for other Types". If you want to go this route and need more help, let me know.
You are going for the wrong approach here. For consistency purposes I would strongly encourage you to use a case class.
Say you have this
case class Image(
url: String,
size: Double,
properties: Map[String][String]
optionalProperty: Option[String]
// etc.
);
And then you use parse and decompose to deal with this.
val image = parse(jsonString).extract[Image]; // extracts an Image from JSON.
val jsonForImage: JValue = decompose(image); // serializes an Image to JSON.
And if you want to serialize a List[Image] to JSON:
def serialize(images: List[Image]) : JValue = {
for (image <- images)
yield decompose(image);
};
To parse a list of images from JSON:
val images: List[Image] = parse(jsonString).extract[List[Image]];
Using Option[SomeType] in the Image case class will deal with missing/optional parameters automatically.