Json:
I need to get only id field from source. Data class:
data class Article(
val sourceId: String,
val author: String,
val title: String,
...
)
Convertor factory is GsonConvertorFactory
In the JSON you provided, source is a complex object, so you can't define it as a string unless you create a custom deserialiser. A quick way to get this working, though, is to create another set of classes to mimic the JSON structure, like this:
data class Source(
val id: String
)
data class Article(
val source: Source,
val author: String,
val title: String
)
Then you can use it this way:
fun main() {
val json = """ {
"source": {
"id": "bbc-news",
"name": "BBC News"
},
"author": "BBC News",
"title": "Afrobeat pioneer Tony Allen dies aged 79"
}
""".trimIndent()
val gson = GsonBuilder().create()
val article = gson.fromJson(json, Article::class.java)
println(article)
}
This prints: Article(source=Source(id=bbc-news), author=BBC News, title=Afrobeat pioneer Tony Allen dies aged 79).
Related
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.
Good day, Mr. Freeman! I'm trying to make a non trivial polymorphic dto for Json / Object conversion for an external api. The dto contains the property, that can be of two types, but it depends on it's internal value... Let's say that i have a such json:
[{
"Id": 1,
"Age": 2,
"Car": {
"MaxPassengers": 20,
"Model": "Audi",
"UniqueAudyTechnology": true
},
"Vendor": "VOLKSWAGEN AUTO GROUP (VAG)"
},
{
"Id": 2,
"Age": 1,
"Car": {
"MaxPassengers": 5,
"Model": "Skoda",
"SkodaRentalProgramId": 100
},
"Vendor": "VOLKSWAGEN AUTO GROUP (VAG)"
}]
So, in "Car" field i can have any car class, but to define it i need to use Car.Model property.
I've made a common interface and data classes:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "Model",
visible = true
)
#JsonSubTypes(value = [
JsonSubTypes.Type(value = Audi::class, name = "Audi"),
JsonSubTypes.Type(value = Skoda::class, name = "Skoda")
])
#ApiModel(description = "Used car")
interface UsedCar {
#get:JsonProperty("Id")
val id: Long
#get:JsonProperty("Age")
val age: Int
#get:JsonProperty("Vendor")
val vendor: String
}
and the data classes:
data class Audi(
#JsonProperty("UniqueAudyTechnology")
val hasUniqueAudyTechnology: boolean,
#JsonProperty("Model")
val model: String,
#JsonProperty("MaxPassengers")
val maxPassengers: Int
)
data class Skoda(
#JsonProperty("SkodaRentalProgramId")
val skodaRentalProgramId: Int,
#JsonProperty("Model")
val model: String,
#JsonProperty("MaxPassengers")
val maxPassengers: Int
)
In fact, i want to make jackson resolve subtype by subtypes property Model. I keep trying all the day, but i can't understand what did i miss...?
P.S. the code may not work because i've removed implementation of UsedCar by Audi and Skoda... sorry... no idea how to handle it...
IMHO this is the easiest solution for an API that sends multiple unused fields.
#JsonIgnoreProperties(ignoreUnknown = true)
data class Car(
#JsonProperty("Id")
val id: Long,
#JsonProperty("Age")
val age: Int,
#JsonProperty("Vendor")
val vendor: String,
#JsonProperty("UniqueAudiTechnology")
val hasUniqueAudiTechnology: Boolean,
#JsonProperty("SkodaRentalProgramId")
val skodaRentalProgramId: Int,
#JsonProperty("Model")
val model: String,
#JsonProperty("MaxPassengers")
val maxPassengers: Int)
It can get really tedious if there are many fields, but that's a bad API design in the first place.
Check this answer for more info in the above way.
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
I have a json model, where contents of certain attribute depend on the other attribute. Something like this:
"paymentMethod": "CREDIT_CARD",
"metaData": {
"cardType": "VISA",
"panPrefix": "",
"panSuffix": "",
"cardHolder": "",
"expiryDate": ""
}
So when paymentMethod equals to CREDIT_CARD, the metadata object will contain attributes as described. In case of other payment method, there'll be different metadata.
I want to handle this situation in a future-proof way. What I'm trying to do is to not parse the metadata field right away, but keep it somehow "unparsed" until I've parsed the paymentMethod field. Then I'd take the metadata and applied appropriate parsing approach.
However I don't know which type to use for a Scala class field for such "late parsed" attributes. I've tried String, JsonInput, JObject, and they all are not suitable (either don't compile or can't be parsed). Any ideas which type can I use? Or, in other words:
case class CreditCardMetadata(
cardType: String,
panPrefix: String,
panSuffix: String,
cardHolder: String,
expiryDate: String)
case class PaypalMetadata(...) // etc.
case class PaymentGatewayResponse(
paymentMethod: String,
metadata: ???)
You could create a CustomSerializer to parse the metadata directly. Something like :
case class PaymentResponse(payment: Payment, otherField: String)
sealed trait Payment
case class CreditCardPayment(cardType: String, expiryDate: String) extends Payment
case class PayPalPayment(email: String) extends Payment
object PaymentResponseSerializer extends CustomSerializer[PaymentResponse]( format => (
{
case JObject(List(
JField("paymentMethod", JString(method)),
JField("metaData", metadata),
JField("otherField", JString(otherField))
)) =>
implicit val formats = DefaultFormats
val payment = method match {
case "CREDIT_CARD" => metadata.extract[CreditCardPayment]
case "PAYPAL" => metadata.extract[PayPalPayment]
}
PaymentResponse(payment, otherField)
},
{ case _ => throw new UnsupportedOperationException } // no serialization to json
))
Which can be used as:
implicit val formats = DefaultFormats + PaymentResponseSerializer
val json = parse("""
{
"paymentMethod": "CREDIT_CARD",
"metaData": {
"cardType": "VISA",
"expiryDate": "2015"
},
"otherField": "hello"
}
""")
val json2 = parse("""
{
"paymentMethod": "PAYPAL",
"metaData": {
"email": "foo#bar.com"
},
"otherField": "world"
}
""")
val cc = json.extract[PaymentResponse]
// PaymentResponse(CreditCardPayment(VISA,2015),hello)
val pp = json2.extract[PaymentResponse]
// PaymentResponse(PayPalPayment(foo#bar.com),world)
You can use a Map[String, String].
It will contain anything you may need.
The answer by Peter Neyens has inspired me to implement my own solution. It's not as generic as his, but in my case I needed something really simple and ad-hoc. Here's what I've done:
It's possible to define a case class with the field of unknown type is represented by a JObject type. Something like this:
case class PaymentGatewayResponse(
default: Boolean,
paymentMethod: String,
visibleForCustomer: Boolean,
active: Boolean,
metaData: JObject)
When such json is parsed into such case class, this field is not parsed immediately, but contains all the necessary information. Then it's possible parse it in a separate step:
case class CreditCardMetadata(
cardType: String,
cardObfuscatedNumber: String,
cardHolder: String,
expiryDate: String)
val response: PaymentGatewayResponse = doRequest(...)
response.map { r =>
r.paymentMethod match {
case "CREDIT_CARD" => r.metaData.extract[CreditCardMetadata]
case unsupportedType: String => throw new UnsupportedPaymentMethodException(unsupportedType)
}
}
I have two JsValue created from case class, i.e. Book and Book detail
val bookJson = Json.tojson(Book)
val bookDetailJson = Json.tojson(BookDetail)
and the format would be:
//Book
{
id: 1,
name: "A Brief History of Time"
}
//BookDetail
{
bookId: 1,
author: "Steven Hawking",
publicationDate: 1988,
pages: 256
}
How can I merge them to a single Json in play-framework 2.10? i.e.
//Book with detail
{
id: 1,
name: "A Brief History of Time",
bookId: 1,
author: "Steven Hawking",
publicationDate: 1988,
pages: 256
}
I was trying the transformation and failed to iterate through the second JsValue:
val mapDetail = (__).json.update(
__.read[JsObject].map { o =>
o.deepMerge( JsObject(Seq(("detail", bookDetailJson))) )
})
bookJson.validate(mapDetail).get
It would become one level down, which I don't really want.
//Book with detail
{
id: 1,
name: "A Brief History of Time",
detail: {
bookId: 1,
author: "Steven Hawking",
publicationDate: 1988,
pages: 256
}
}
Please let me know if any trick could provide on this Json transform. Many Thanks!
Play has a lot of new features for JSON right now. This would be a nice showcase for the Format[A] trait (see Scala Json Inception) which you could include implicitly as I will show, or explicitly to the methods that require an implicit Format[A]/Reads[A]/Writes[A].
Create a case class to represent your JSON objects,
case class Book(id: Int, name: String)
case class BookDetail(id: Int, author: String, publicationDate: Int, pages: Int)
Create companion objects that contain the implicit Format[A] so that Format/Reads/Writes will automatically be in scope when you need them.
object Book {
implicit val fmt: Format[Book] = Json.format[Book]
}
object BookDetail {
implicit val fmt: Format[BookDetail] = Json.format[BookDetail]
}
Now you could do something like this,
val bookJson = Json.toJson(Book(1, "A Brief History Of Time"))
val bookDetailJson = Json.toJson(BookDetail(1, "Steven Hawking", 1988, 256))
bookJson.as[JsObject].deepMerge(bookDetailJson.as[JsObject])
And you will have an object like this,
{
id: 1,
name: "A Brief History Of Time",
author: "Steven Hawking",
publicationDate: 1988,
pages: 256
}
I've tried this in the REPL but it does not work, in a Play application it does just fine though. Also in a production scenario we would likely use asOpt[T] in place of as[T].
Here is an example of why asOpt[T] may be better suited, suppose instead of a valid JSON object for book you get,
val bookJson = Json.toJson("not a book")
You will end up with a
[JsResultException: JsResultException(errors:List((,List(ValidationError(validate.error.expected.jsobject,WrappedArray())))))]
But suppose instead you change your method to use asOpt[T],
bookJson.asOpt[JsObject].getOrElse(Json.obj()).deepMerge(bookDetailJson.asOpt[JsObject].getOrElse(Json.obj()))
Now you will end up with at least a partial JSON object,
{
id: 1,
author: "Steven Hawking",
publicationDate: 1988,
pages: 256
}
So depending on how you would like to handle improperly formatted JSON you could choose either option.
JsObject is subtype of JsValue.
JsValue can be simple converted to the JsObject using as or asOpt methods from JsValue.
Example:
val someJsValue = ....
val asObject:JsObject = someJsValue.as[JsObject]
val asObjectMaybe:Option[JsObject] = v.asOpt[JsObject]
In the case of JsArray you can not use above code.
If you use play and parse JSON with array, then Json.toJson(...) produces JsValue which is JsArray actually.
You need to convert JsArray as following:
val someJsValueButArray = ....
val asJsArray:JsArray = Json.toJson(someJsValueButArray).as[JsArray]
val asSeqOfJsObjects:Seq[JsObject] = asJsArray.value.map(_.as[JsObject])