I am a little confused about how Gson is parsing Strings to JSON.
At the very start, I initialize gson like this
val gson = Gson().newBuilder().serializeNulls().disableHtmlEscaping().create()
Next, I'm converting my map to a String:
val pushJson = gson.toJson(data) // data is of type Map<String,Any>
That gives the following output:
{
"name": null,
"uuid": "5a8e8202-6654-44d9-a452-310773da78c1",
"paymentCurrency": "EU"
}
At this point, the JSON string has null values. But in the following step:
val jsonObject = JsonParser.parseString(pushJson).asJsonObject
it hasn't!
{
"uuid": "5a8e8202-6654-44d9-a452-310773da78c1",
"paymentCurrency": "EU"
}
Nulls are omitted. How to get all null values in JsonObject like in JSON string:
{
"string-key": null,
"other-key": null
}
#Edit
Added some json's to help understand the issue.
After discussing with the OP it came out that the JSON object was then serialised by Retrofit to allow for an API call, using the following code:
return Retrofit.Builder()
.baseUrl("api/url")
.client(httpClient.build())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiInterface::class.java)
The issue here lays in the GsonConverterFactory: since no Gson object is passed to the create method, a new default Gson instance gets created under the hood, and by default it doesn't serialise null values.
The issue can be easily solved by passing the appropriate instance to the factory:
val gson = GsonBuilder().serializeNulls().create() // plus any other custom configuration
....
fun createRetrofit() = Retrofit.Builder()
.baseUrl("api/url")
.client(httpClient.build())
.addConverterFactory(GsonConverterFactory.create(gson)) // use the configured Gson instance
.build()
.create(ApiInterface::class.java)
Related
I have this API that returns a list as a response:
https://animension.to/public-api/search.php?search_text=&season=&genres=&dub=&airing=&sort=popular-week&page=2
This returns a response string in list format like this
[["item", 1, "other item"], ["item", 2, "other item"]]
How do I convert this so that I can use it as a list, not a string data type?
One way is to use Kotlinx Serialization. It doesn't have direct support for tuples, but it's easy to parse the input as a JSON array, then manually map to a specific data type (if we make some assumptions).
First add a dependency on Kotlinx Serialization.
// build.gradle.kts
plugins {
kotlin("jvm") version "1.7.10"
kotlin("plugin.serialization") version "1.7.10"
// the KxS plugin isn't needed, but it might come in handy later!
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")
}
(See the Kotlinx Serialization README for Maven instructions.)
Then we can use the JSON mapper to parse the result of the API request.
import kotlinx.serialization.json.*
fun main() {
val inputJson = """
[["item", 1, "other item"], ["item", 2, "other item"]]
""".trimIndent()
// Parse, and use .jsonArray to force the results to be a JSON array
val parsedJson: JsonArray = Json.parseToJsonElement(inputJson).jsonArray
println(parsedJson)
// [["item",1,"other item"],["item",2,"other item"]]
}
It's much easier to manually map this JsonArray object to a data class than it is to try and set up a custom Kotlinx Serializer.
import kotlinx.serialization.json.*
fun main() {
val inputJson = """
[["item", 1, "other item"], ["item", 2, "other item"]]
""".trimIndent()
val parsedJson: JsonArray = Json.parseToJsonElement(inputJson).jsonArray
val results = parsedJson.map { element ->
val data = element.jsonArray
// Manually map to the data class.
// This will throw an exception if the content isn't the correct type...
SearchResult(
data[0].jsonPrimitive.content,
data[1].jsonPrimitive.int,
data[2].jsonPrimitive.content,
)
}
println(results)
// [SearchResult(title=item, id=1, description=other item), SearchResult(title=item, id=2, description=other item)]
}
data class SearchResult(
val title: String,
val id: Int,
val description: String,
)
The parsing I've defined is strict, and assumes each element in the list will also be a list of 3 elements.
I have a String (jsonData) being mapped to json via Jackson Object mapper as below to JaxB.
var completeJson = objectMapper.readValue(jsonData, Data.class);
myDto.setFirstName(completeJson.getFirstName())
myDto.setLastName(completeJson.getLastName()))
.....
.....etc..etc
I'm able to map to the above strings just fine. However, I'm having problems mapping
to a jooq JSON object. I guess I will now have to convert jsonData to jooq JSON.
How would I do this?
JSON newJson = objectMapper.(best method to use);
myDto.setJsonSource(newJson)
Or maybe I have to create some sort of wrapper?
DTO configured by jooq
public myDto setJsonSource(JSON jsonSource) {
this.jsonSource = jsonSource;
return this;
}
The org.jooq.JSON type is just a wrapper around a string, so why not just use:
JSON json = JSON.json(objectMapper.writeValueAsString(object));
I have looked at a lot of articles but can't seem to figure out the answer. The problem being is that I get a json file like this from my api:
{
"forecast": {
"2020-01-04": {
"date": "2020-01-04",
"mintemp": 7,
"maxtemp": 11,
"avgtemp": 9,
"hourly":
...
}
}
}
The problem is that the "key" in this JSON is always the day of today (2020-01-04). The way I do this now is like this:
data class ForecastDaysContainer(
#SerializedName("2020-01-04")
var forecastday1: FutureWeatherEntry,
#SerializedName("2020-01-05")
val forecastday2: FutureWeatherEntry,
#SerializedName("2020-01-06")
val forecastday3: FutureWeatherEntry,
#SerializedName("2020-01-07")
val forecastday4: FutureWeatherEntry,
#SerializedName("2020-01-08")
val forecastday5: FutureWeatherEntry,
#SerializedName("2020-01-09")
val forecastday6: FutureWeatherEntry,
#SerializedName("2020-01-10")
val forecastday7: FutureWeatherEntry
)
This of course results in me changing the dates every day manually.
Is there any way I can set the #SerializedName to the date of today? When I try this with:
LocalDate.now().toString()
I get the error:
An annotation argument must be a compile-time constant
I can't seem to find a decent fix for this in Kotlin.
It is not possible to have annotation data to be created dynamically. So you need to think it the other way around. With Gson you could try to implement custom JsonDeserializer that parses fields dynamically by that date value.
However I think that most convenient way would be to use Map as comment suggests. If you can make your ForecastDaysContainer like this:
data class ForecastDaysContainer(
var forecast : Map<String, FutureWeatherEntry>
)
When deserialized you will then have a Map that contains possibly many days with the date as the key. So like:
val container = gson.fromJson(json, ForecastDaysContainer::class.java);
val forecast = container.forecast.get("2020-01-04");
Gson has parse() method for that.
val key = "2020-01-04" // This is generated, probably?
val json = JsonParser().parse(jsonString)
val result = json.asJsonObject["forecast"].asJsonObject[key]
Using jackson library I read json data from a file (each row of file is a JSON object) an parse it to a map object of String and Any. My goal is to save specified keys (id and text) to a collection.
val input = scala.io.Source.fromFile("data.json").getLines()
val mapper = new ObjectMapper() with DefaultScalaModule
val data_collection = mutable.HashMap.empty[Int, String]
for (i <- input){
val parsedJson = mapper.readValue[Map[String, Any]](i)
data_collection.put(
parsedJson.get("id"),
parsedJson.get("text")
)
But as the values in the parsedJson map have the Any type, getting some keys like id and text, it returns Some(value) not just the value with the appropriate type. I expect the values for the id key to be Integer and values for the text to be String.
Running the code I got the error:
Error:(31, 23) type mismatch;
found : Option[Any]
required: Int
parsedJson.get("id"),
Here is a sample of JSON data in the file:
{"text": "Hello How are you", "id": 1}
Is it possible in Scala to parse id values to Int and text values to String, or at least convert Some(value) to value with type Int or String?
If you want to get a plain value from a Map instead of a Option you can use the () (apply) method - However it will throw an exception if the key is not found.
Second, Scala type system is static not dynamic, if you have an Any that's it, it won't change to Int or String at runtime, and the compiler will fail - Nevertheless, you can cast them using the asInstanceOf[T] method, but again if type can't be casted to the target type it will throw an exception.
Please note that even if you can make your code work with the above tricks, that code wouldn't be what you would expect in Scala. There are ways to make the code more typesafe (like pattern matching), but parsing a Json to a typesafe object is an old problem, I'm sure jackson provides a way to parse a json into case class that represent your data. If not take a look to circe it does.
Try the below code :
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import
com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
val input = scala.io.Source.fromFile("data.json").getLines()
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
val obj = mapper.readValue[Map[String, Any]](input)
val data_collection = mutable.HashMap.empty[Int, String]
for (i <- c) {
data_collection.put(
obj.get("id").fold(0)(_.toString.toInt),
obj.get("text").fold("")(_.toString)
)
}
println(data_collection) // Map(1 -> Hello How are you)
I have a Grails controller action that return a JSON object
def someAction(String customerParameter) {
JsonBuilder json = new JsonBuilder()
def jsonObject = json {
someAttribute "someValue"
} as JSON
// TODO: if customerParamter is not null add it to json object
render (contentType: 'application/json;charset=UTF-8', text:json)
}
As the above code mentioned, I'd like to modify the json object without rebuilding it with or without the given customerParameter.
Well, I found a solution using the "content" property:
if (customerParameter) {
json.content << ["custmoerParameter": customerParameter]
}