In Scala I want to create a JSON Object or JSON String where the result would be something similar to this (i.e. a relatively basic nested JSON):
{
"info":{
"info1":"1234",
"info2":"123456",
"info3":false
},
"x":[
{
"x1_info":10,
"x2_info":"abcde"
}
],
"y":[
{
"y1_info":"28732",
"y2_info":"defghi",
"y3_info":"1261",
"y_extra_info":[
{
"Id":"543890",
"ye1":"asdfg",
"ye2":"BOOLEAN",
"ye3":false,
"ye4":true,
"ye5":true,
"ye6":0,
"ye7":396387
}
]
}
]
}
I plan to use this object in tests so ideally I can set the information inside the JSON object to whatever I want (I don't plan on sourcing the info for the JSON object from a separate file). I tried the following where I created a Map of the information I want in my JSON object and then tried converting it to json using gson:
val myInfo = Map(
"info" -> Map("info1" -> "1234",
"info2" -> "123456",
"info3" -> "false"),
"x" -> Map("x1_info" -> 10,
"x2_info" -> "abcde"),
"y" -> Map("y1_info" -> "28732",
"y2_info" -> "defghi",
"y3_info" -> "1261",
"y_extra_info"-> Map("Id" -> "543890",
"ye1" -> "asdfg",
"ye2" -> "BOOLEAN",
"ye3" -> false,
"ye4" -> true,
"ye5" -> true,
"ye6" -> 0,
"ye7" -> 396387
)
)
)
val gson = new GsonBuilder().setPrettyPrinting.create
print(gson.toJson(myInfo))
However though it produces JSON correctly does not produce the JSON in the structure I have above but instead structures it like so:
{
"key1": "info",
"value1": {
"key1": "info1",
"value1": "1234",
"key2": "info2",
"value2": "123456",
...(etc)...
I have figured out an inelegant way of achieving what I want by creating a string containing the JSON I expect and then parsing as JSON (but I assume there is a better way)
val jsonString = "{\"info\":{\"info1\":\"1234\", \"info2\":\"123456\", " +
"\"info3\":false}," +
"\"x\":[{ \"x1_info\":10,\"x2_info\":\"abcde\"}]," +
"\"y\":[{\"y1_info\":\"28732\",\"y2_info\":\"defghi\",\"y3_info\":\"1261\", " +
"\"y_extra_info\":[{" +
"\"Id\":\"543890\",\"ye1\":\"asdfg\"," +
"\"ye2\":\"BOOLEAN\",\"ye3\":false,\"ye4\":true," +
"\"ye5\":true,\"ye6\":0,\"ye7\":396387}]}]}"
I'm relatively new to Scala so perhaps using a Map or writing it all out as a string is not the best or most "Scala way"?
Is there an easy way to create such a JSON object using preferable either gson or spray or otherwise by some other means?
In general, there's no good reason to use GSON with Scala, as GSON is Java-based and doesn't really know how to deal with Scala collections. In this case, it doesn't know how to deal with the key-value-ness of a Scala Map and instead (most likely due to reflection) just leaks the implementation detail of the default Scala Map being customized for the cases where the size is less than 4.
JSON libraries which are native to Scala (e.g. circe, play-json, spray-json, etc.) do what you expect for String-keyed Maps. They also have built-in support for directly handling case classes in Scala, e.g. for
case class Foo(id: String, code: Int, notes: String)
val foo = Foo("fooId", 42, "this is a very important foo!")
will serialize as something like (ignoring possible differences around pretty-printing and field ordering)
{ "id": "fooId", "code": 42, "notes": "this is a very important foo!" }
If there's a hard project requirement that you must use GSON, I believe it does understand Java's collections, so you could convert a Scala Map to a java.util.Map by using the conversions in CollectionConverters (in 2.13, it's in the scala.jdk package; in earlier versions, it should be in scala.collection). I'm not going to elaborate on this, because my very strong opinion is that in a Scala codebase, it's best to use a native-to-Scala JSON library.
Related
I am using Scala to parse json with a structure like this:
{
"root": {
"metadata": {
"name": "Farmer John",
"hasTractor": false
},
"plants": {
"corn": 137.137,
"soy": 0.45
},
"animals": {
"cow": 4,
"sheep": 12,
"pig": 1
}
}
}
And I am currently using the org.json library to parse it, like this:
val jsonObject = new JSONObject(jsonString) // Where jsonString is the above json tree
But when I run something like jsonObject.get("root.metadata.name") then I get the error:
JSONObject["root.metadata.name"] not found.
org.json.JSONException: JSONObject["root.metadata.name"] not found.
I suspect I can get the objects one at a time by splitting up that path, but that sounds tedious and I assume a better json library already exists. Is there a way to easily get the data the way I am trying to or is there a better library to use that works better with Scala?
The JSONObject you are using is deprecated. The deprecation message sends you to The Scala Library Index. I'll demonstrate how this can be done in play-json. Let's assume that the json above is stored at jsonString, so we can do:
val json = Json.parse(jsonString)
val path = JsPath \ "root" \ "metadata" \ "name"
path(json) match {
case Nil =>
??? // path does nont exist
case values =>
println(s"Values are: $values")
}
Code run at Scastie.
Looks like I was able to solve it using JsonPath like this:
val document = Configuration.defaultConfiguration().jsonProvider().parse(jsonString): Object
val value = JsonPath.read(document, "$.root.metadata.name"): Any
I've been playing with Kotlinx.serialisation. I've been trying to find a quick way to use Kotlinx.serialisation to create a plain simple JSON (mostly to send it away), with minimum code clutter.
For a simple string such as:
{"Album": "Foxtrot", "Year": 1972}
I've been doing is something like:
val str:String = Json.stringify(mapOf(
"Album" to JsonPrimitive("Foxtrot"),
"Year" to JsonPrimitive(1972)))
Which is far from being nice. My elements are mostly primitive, so I wish I had something like:
val str:String = Json.stringify(mapOf(
"Album" to "Sergeant Pepper",
"Year" to 1967))
Furthermore, I'd be glad to have a solution with a nested JSON. Something like:
Json.stringify(JsonObject("Movies", JsonArray(
JsonObject("Name" to "Johnny English 3", "Rate" to 8),
JsonObject("Name" to "Grease", "Rate" to 1))))
That would produce:
{
"Movies": [
{
"Name":"Johnny English 3",
"Rate":8
},
{
"Name":"Grease",
"Rate":1
}
]
}
(not necessarily prettified, even better not)
Is there anything like that?
Note: It's important to use a serialiser, and not a direct string such as
"""{"Name":$name, "Val": $year}"""
because it's unsafe to concat strings. Any illegal char might disintegrate the JSON! I don't want to deal with escaping illegal chars :-(
Thanks
Does this set of extension methods give you what you want?
#ImplicitReflectionSerializer
fun Map<*, *>.toJson() = Json.stringify(toJsonObject())
#ImplicitReflectionSerializer
fun Map<*, *>.toJsonObject(): JsonObject = JsonObject(map {
it.key.toString() to it.value.toJsonElement()
}.toMap())
#ImplicitReflectionSerializer
fun Any?.toJsonElement(): JsonElement = when (this) {
null -> JsonNull
is Number -> JsonPrimitive(this)
is String -> JsonPrimitive(this)
is Boolean -> JsonPrimitive(this)
is Map<*, *> -> this.toJsonObject()
is Iterable<*> -> JsonArray(this.map { it.toJsonElement() })
is Array<*> -> JsonArray(this.map { it.toJsonElement() })
else -> JsonPrimitive(this.toString()) // Or throw some "unsupported" exception?
}
This allows you to pass in a Map with various types of keys/values in it, and get back a JSON representation of it. In the map, each value can be a primitive (string, number or boolean), null, another map (representing a child node in the JSON), or an array or collection of any of the above.
You can call it as follows:
val json = mapOf(
"Album" to "Sergeant Pepper",
"Year" to 1967,
"TestNullValue" to null,
"Musicians" to mapOf(
"John" to arrayOf("Guitar", "Vocals"),
"Paul" to arrayOf("Bass", "Guitar", "Vocals"),
"George" to arrayOf("Guitar", "Sitar", "Vocals"),
"Ringo" to arrayOf("Drums")
)
).toJson()
This returns the following JSON, not prettified, as you wanted:
{"Album":"Sergeant Pepper","Year":1967,"TestNullValue":null,"Musicians":{"John":["Guitar","Vocals"],"Paul":["Bass","Guitar","Vocals"],"George":["Guitar","Sitar","Vocals"],"Ringo":["Drums"]}}
You probably also want to add handling for some other types, e.g. dates.
But can I just check that you want to manually build up JSON in code this way rather than creating data classes for all your JSON structures and serializing them that way? I think that is generally the more standard way of handling this kind of stuff. Though maybe your use case does not allow that.
It's also worth noting that the code has to use the ImplicitReflectionSerializer annotation, as it's using reflection to figure out which serializer to use for each bit. This is still experimental functionality which might change in future.
During my fresh adventures with kotlin-react I hit a hard stop when trying to parse some data from my backend which contains enum values.
Spring-Boot sends the object in JSON form like this:
{
"id": 1,
"username": "Johnny",
"role": "CLIENT"
}
role in this case is the enum value and can have the two values CLIENT and LECTURER. If I were to parse this with a java library or let this be handled by Spring-Boot, role would be parsed to the corresponding enum value.
With kotlin-js' JSON.parse, that wouldn't work and I would have a simple string value in there.
After some testing, I came up with this snippet
val json = """{
"id": 1,
"username": "Johnny",
"role": "CLIENT",
}"""
val member: Member = JSON.parse(json) { key: String, value: Any? ->
if (key == "role") Member.Role.valueOf(value.toString())
else value
}
in which I manually have to define the conversion from the string value to the enum.
Is there something I am missing that would simplify this behaviour?
(I am not referring to using ids for the JSON and the looking those up, etc. I am curious about some method in Kotlin-JS)
I have the assumption there is not because the "original" JSON.parse in JS doesn't do this and Kotlin does not add any additional stuff in there but I still have hope!
As far as I know, no.
The problem
Kotlin.JS produces an incredibly weird type situation when deserializing using the embedded JSON class, which actually is a mirror for JavaScript's JSON class. While I haven't done much JavaScript, its type handling is near non-existent. Only manual throws can enforce it, so JSON.parse doesn't care if it returns a SomeCustomObject or a newly created object with the exact same fields.
As an example of that, if you have two different classes with the same field names (no inheritance), and have a function that accepts a variable, it doesn't care which of those (or a third for that matter) it receives as long as the variables it tries accessing on the class exists.
The type issues manifest themselves into Kotlin. Now wrapping it back to Kotlin, consider this code:
val json = """{
"x": 1, "y": "yes", "z": {
"x": 42, "y": 314159, "z": 444
}
}""".trimIndent()
data class SomeClass(val x: Int, val y: String, val z: Struct)
data class Struct(val x: Int, val y: Int, val z: Int)
fun main(args: Array<String>) {
val someInstance = JSON.parse<SomeClass>(json)
if(someInstance.z::class != Struct::class) {
println("Incompatible types: Required ${Struct::class}, found ${someInstance.z::class}");
}
}
What would you expect this to print? The natural would be to expect a Struct. The type is also explicitly declared
Unfortunately, that is not the case. Instead, it prints:
Incompatible types: Required class Struct, found class Any
The point
The embedded JSON de/serializer isn't good with types. You might be able to fix this by using a different serializing library, but I'll avoid turning this into a "use [this] library".
Essentially, JSON.parse fails to parse objects as expected. If you entirely remove the arguments and try a raw JSON.parse(json); on the JSON in your question, you'll get a role that is a String and not a Role, which you might expect. And with JSON.parse doing no type conversion what so ever, that means you have two options: using a library, or using your approach.
Your approach will unfortunately get complicated if you have nested objects, but with the types being changed, the only option you appear to have left is explicitly parsing the objects manually.
TL;DR: your approach is fine.
I have a simple custom data structure which I use to map the results from the database:
case class Filter(id: Int, table: String, name: String, Type: String, structure: String)
The resulting object type is List[Filter] and if converted to JSON, it should look something like this:
[
{
"id": 1,
"table": "table1",
"name": "name1",
"Type": "type1",
"structure": "structure1"
},
{
"id": 2,
"table": "table2",
"name": "name2",
"Type": "type2",
"structure": "structure2"
}
]
Now when I try to serialize my object into JSON
val result: String = Json.toJson(filters)
I am getting something like
No Json deserializer found for type List[Filter]. Try to implement an implicit Writes or Format for this type.
How do I solve this seemingly simple problem without writing some ridiculous amount of boilerplate?
My stack is Play 2.2.1, Scala 2.10.3, Java 8 64bit
Short answer:
Just add:
implicit val filterWrites = Json.writes[Filter]
Longer answer:
If you look at the definition of Json.toJson, you will see that its complete signature is:
def toJson[T](o: T)(implicit tjs: Writes[T]): JsValue = tjs.writes(o)
Writes[T] knows how to take a T and transform it to a JsValue. You will need to have an implicit Writes[Filter] around that knows how to serialize your Filter instance. The good news is that Play's JSON library comes with a macro that can instantiate those Writes[_] for you, so you don't have to write boring code that transforms your case class's fields into JSON values. To invoke this macro and have its value picked up by implicit search add the line above to your scope.
I want to convert a scala list of strings, List[String], to an Json object.
For each string in my list I want to add it to my Json object.
So that it would look like something like this:
{
"names":[
{
"Bob",
"Andrea",
"Mike",
"Lisa"
}
]
}
How do I create an json object looking like this, from my list of strings?
To directly answer your question, a very simplistic and hacky way to do it:
val start = """"{"names":[{"""
val end = """}]}"""
val json = mylist.mkString(start, ",", end)
However, what you almost certainly want to do is pick one of the many JSON libraries out there: play-json gets some good comments, as does lift-json. At the worst, you could just grab a simple JSON library for Java and use that.
Since I'm familiar with lift-json, I'll show you how to do it with that library.
import net.liftweb.json.JsonDSL._
import net.liftweb.json.JsonAST._
import net.liftweb.json.Printer._
import net.liftweb.json.JObject
val json: JObject = "names" -> List("Bob", "Andrea", "Mike", "Lisa")
println(json)
println(pretty(render(json)))
The names -> List(...) expression is implicitly converted by the JsonDSL, since I specified that I wanted it to result in a JObject, so now json is the in-memory model of the json data you wanted.
pretty comes from the Printer object, and render comes from the JsonAST object. Combined, they create a String representation of your data, which looks like
{
"names":["Bob","Andrea","Mike","Lisa"]
}
Be sure to check out the lift documentation, where you'll likely find answers to any further questions about lift's json support.