How to get listitems in JSONObject? - json

my JSON is supposed to look like this.
{"zip":123, "people":[{"firstname":"Thomas", "lastname":"Tatum"},
{"firstname":"Drew", "lastname":"Uncle"}]}
(I am using import org.json.JSONObject)
I have a MutableList, in the List are Person (it’s a data class with firstname and lastname).
But I don’t know how to get my list items in a JSONObject to fit in json (see below).
val json = JSONObject(
mapOf(
"zip" to 123,
"people" to //I don't know how to get my values here
)
)
Maybe someone can help me.

You could do this
import org.json.JSONObject
data class Person(val firstname: String, val lastname: String)
fun main() {
val people = arrayOf(Person("Thomas", "Tatum"), Person("Drew", "Uncle")) //also works for Lists, doesn't need to be an array
val json = JSONObject(
mapOf(
"zip" to 123,
"people" to people,
)
)
println(json)
//prints: {"zip":123,"people":[{"firstname":"Thomas","lastname":"Tatum"},{"firstname":"Drew","lastname":"Uncle"}]}
}

Related

Parse JSON without data class in Kotlin?

There are many JSON parsers in Kotlin like Forge, Gson, JSON, Jackson... But they deserialize the JSON to a data class, meaning it's needed to define a data class with the properties corresponding to the JSON, and this for every JSON which has a different structure.
But what if you don't want to define a data class for every JSON you could have to parse?
I'd like to have a parser which wouldn't use data classes, for example it could be something like:
val jsonstring = '{"a": "b", "c": {"d: "e"}}'
parse(jsonstring).get("c").get("d") // -> "e"
Just something that doesn't require me to write a data class like
data class DataClass (
val a: String,
val b: AnotherDataClass
)
data class AnotherDataClass (
val d: String
)
which is very heavy and not useful for my use case.
Does such a library exist? Thanks!
With kotlinx.serialization you can parse JSON String into a JsonElement:
val json: Map<String, JsonElement> = Json.parseToJsonElement(jsonstring).jsonObject
You can use JsonPath
val json = """{"a": "b", "c": {"d": "e"}}"""
val context = JsonPath.parse(json)
val str = context.read<String>("c.d")
println(str)
Output:
Result: e

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.

PlayJSON in Scala

I am trying to familiarize myself with the PlayJSON library. I have a JSON formatted file like this:
{
"Person": [
{
"name": "Jonathon",
"age": 24,
"job": "Accountant"
}
]
}
However, I'm having difficulty with parsing it properly due to the file having different types (name is a String but age is an Int). I could technically make it so the age is a String and call .toInt on it later but for my purposes, it is by default an integer.
I know how to parse some of it:
import play.api.libs.json.{JsValue, Json}
val parsed: JsValue = Json.parse(jsonFile) //assuming jsonFile is that entire JSON String as shown above
val person: List[Map[String, String]] = (parsed \ "Person").as[List[Map[String, String]]]
Creating that person value throws an error. I know Scala is a strongly-typed language but I'm sure there is something I am missing here. I feel like this is an obvious fix too but I'm not quite sure.
The error produced is:
JsResultException(errors:List(((0)/age,List(JsonValidationError(List(error.expected.jsstring),WrappedArray())))
The error you are having, as explained in the error you are getting, is in casting to the map of string to string. The data you provided does not align with it, because the age is a string. If you want to keep in with this approach, you need to parse it into a type that will handle both strings and ints. For example:
(parsed \ "Person").validate[List[Map[String, Any]]]
Having said that, as #Luis wrote in a comment, you can just use case class to parse it. Lets declare 2 case classes:
case class JsonParsingExample(Person: Seq[Person])
case class Person(name: String, age: Int, job: String)
Now we will create a formatter for each of them on their corresponding companion object:
object Person {
implicit val format: OFormat[Person] = Json.format[Person]
}
object JsonParsingExample {
implicit val format: OFormat[JsonParsingExample] = Json.format[JsonParsingExample]
}
Now we can just do:
Json.parse(jsonFile).validate[JsonParsingExample]
Code run at Scastie.

Parse JSON object into list of objects

I'm trying to use circe to decode a JSON object into a list of objects. I only want to use some of the fields of the JSON response to create the object, so I feel like I have to create a custom decoder.
The class I want to make a sequence of is defined as follows:
case class Review(Id: String, ProductId: String, Rating: Int)
I tried creating a custom decoder like this:
implicit val reviewDecoder: Decoder[Review] = Decoder.instance { c =>
val resultsC = c.downField("Results")
for {
id <- resultsC.downArray.get[String]("Id")
productId <- resultsC.downArray.get[String]("ProductId")
rating <- resultsC.downArray.get[Int]("Rating")
} yield Review(id, productId, rating)
}
reviewDecoder.decodeJson(json) seems to result in only doing the first result and not all of them.
I have a JSON response like this:
{
"Limit":2,
"Offset":0,
"TotalResults":31,
"Locale":"en_US",
"Results":
[
{"Id":"14518388",
"CID":"21a9436b",
"ProductId":"Product11",
"AuthorId":"jcknwekjcnwjk",
"Rating":3
},
{"Id":"14518035",
"CID":"8d67b6f5",
"ProductId":"Product11",
"AuthorId":"fnkjwernfk",
"Rating":3
}
],
"Includes":{},
"HasErrors":false,
"Errors":[]}
I want to be able to parse this JSON object using circe to create a Seq[Review], but I'm stumped how.
****Edit** Luis' answer does answer this question but say I have a more complicated class I want to create a sequence of:
case class User(id: Int)
case class Review(user: User, ProductId: String, Rating: Int)
How would I be able to create a sequence of Reviews in this case?
I would just use the cursor to getting the Array and then use the generic decoder.
The following code was tested on ammonite, where json is a string containing your sample input.
import $ivy.`io.circe::circe-core:0.11.1`
import $ivy.`io.circe::circe-generic:0.11.1`
import $ivy.`io.circe::circe-parser:0.11.1`
import io.circe.{Decoder, Jsom}
import io.circe.parser.parse
final case class Review(Id: String, ProductId: String, Rating: Int)
implicit val reviewDecoder: Decoder[Review] = io.circe.generic.semiauto.deriveDecoder
parse(json).getOrElse(Json.Null).hcursor.downField("Results").as[List[Review]]
// res: io.circe.Decoder.Result[List[Review]] = Right(List(Review("14518388", "Product11", 3), Review("14518035", "Product11", 3)))

Deserializing Json array into Scala object

I have been having major problems trying to deserialize a JSON array to a Scala object
[{"name":"Cool","city":"College Park","address":"806","id":1},{"name":"Mars ","city":"Durham","address":"12","id":2},{"name":"Something","city":"Raleigh
","address":"","id":3},{"name":"test","city":"","address":"","id":5}]
I have tried gson, jerkson(jackson scala wrapper), sjson, flexjson. None of them have worked. What I have here is a List of Customers. List[Customer].
This is the closest I've got:
val array = new JsonParser().parse( customers ).getAsJsonArray()
This gave me an 4 arrays. It obviously didn't give me a customer object though. I tried Jerkson.
val array = parse[List[Customer]](customers)
But I get this.
GenericSignatureFormatError occured : null
I'm just trying to find a simple way like I would in Java.
Here is my Scala class.
case class Customer(
id : Pk[ Int ],
name : String,
address : Option[ String ],
city : Option[ String ],
state : Option[ String ],
user_id : Int )
object Customer extends Magic[ Customer ]( Option( "Customer" ) ) {
def apply( name : String, address : String, city : String, state : String, user_id : Int ) = {
new Customer( NotAssigned, name, Some( address ), Some( city ), Some( state ), user_id )
}
def delete( id : Int ) = {
SQL( "DELETE from Customer where id = {id}" ).onParams( id ).executeUpdate()
}
}
Thanks for any help.
With gson, you could write your own json reader:
case class Customer(id: Int, name: String, address: Option[String],
city: Option[String], state: Option[String], user_id: Int)
object CustomerJsonReader {
def read(in: Reader) = readCustomers(new JsonReader(in))
def readCustomers(reader: JsonReader) = {
val customers = new ListBuffer[Customer]
reader.beginArray()
while (reader.hasNext()) {
customers += readCustomer(reader)
}
reader.endArray()
customers toList
}
def readCustomer(reader: JsonReader): Customer = {
var id = 0
var customerName = ""
var address: Option[String] = None
var city: Option[String] = None
var state: Option[String] = None
var userId = 0
reader.beginObject()
while (reader.hasNext()) {
val name = reader.nextName()
name match {
case "id" => id = reader.nextInt()
case "address" => address = Some(reader.nextString())
case "state" => state = Some(reader.nextString())
case "user_id" => userId = reader.nextInt()
case "name" => customerName = reader.nextString()
case "city" => city = Some(reader.nextString())
case _ => reader.skipValue()
}
}
reader.endObject()
Customer(id, customerName, address, city, state, userId)
}
}
val json =
"""
[{"name":"Cool","city":"College Park","address":"806","id":1},
{"name":"Mars ","city":"Durham","address":"12","id":2},
{"name":"Something","city":"Raleigh ","address":"","id":3},
{"name":"test","city":"","address":"","id":5}]
"""
val customers: List[Customer] =
CustomerJsonReader.read(new StringReader(json))
I know that with gson, you would need Array instead of a scala.List. I would suggest giving that a shot. You should use that with gson.fromJson, I think.
You can also try Jerkson = Jackson + Scala
Quite easy to use even if I had problems with special JSON fields containing "-"
A small tuto I saw on twitter recently: http://logician.free.fr/index.php/2011/09/16/play-scala-and-json/
I've been driven insane by this now and went through trying GSON, Lift-Json, Sjson and finally Jerkson, and found peace with that one.
Here's how I use it in combination with Play:
http://logician.eu/index.php/2011/09/16/play-scala-and-json/
http://logician.eu/index.php/2011/11/01/writing-custom-deserializers-for-jerkson/
I use Lift's json library for this purpose, it easily lets you parse JSON and extract values into case classes. It's packaged as a separate jar so you don't need the whole lift framework to use it.
import net.liftweb.json._
import net.liftweb.json.JsonDSL._
implicit val formats = DefaultFormats
val json: String = "[{...."
val parsed: JValue = parse(json)
val customers: List[Customer] = parsed.extract[List[Customer]]
Just make sure any optional fields are defined in the case class using Option. I noticed in your code the objects are missing the user_id field, which would cause a parse error if the user_id field is declared as Int instead of Option[Int].
Aside from trying to make Jerkson (which is a great library to use from what I have heard), you could also try Jackson's Scala module -- modules are the official way Jackson is extended to deal with 3rd party datatypes as well as native datatypes and constructs of other JVM languages.
(this is not to say this is more official than Jerkson, just that there are many useful Jackson extension modules that many developers are not familiar with)
Issues with Scala module are discussed on main Jackson mailing lists (user#jackson.codehaus.org); you may have found an edge case that could be fixed.
I have written a parser/validator dsl, which allows you to explicitly resolve any type erasure issue. Out of the box it handles case classes, tuples, Option, Either, List, Map, joda DatetTime, piping to functions, multiple key mapping and key name remapping.
It uses the Jackson parser
https://github.com/HigherState/jameson
Its pretty simple to with scala and with play libraries and the code
import play.api.libs.json.{Format, Json}
case class Customer(name:String, city:String, address:String, id:Int)
object Customer {
implicit val jsonFormat: Format[Customer] = Json.format(Customer)
}
val jsonDef = Json.parse(<json-string>)
val customers = jsonDef.as[List[Customer]]
customers is list of Customer objects.