I want to deserialize NASA asteroids that I get from an API call in json format like this:
data class Asteroid(
val id: Int,
val name: String = "",
val meanDiameter: Int,
)
class Deserializer : ResponseDeserializable<Asteroid> {
override fun deserialize(content: String) = Gson().fromJson(content, Asteroid::class.java)
}
How can I ignore the first top items links and page and only deserialize near_earth_objects in my Asteroid data class? And how can I access the nested items inside of near_earth_objects?
You can just ignore them.
data class NearEarthObjects(#SerializedName("near_earth_objects") val nearEarthObjects: List<Objects>)
data class Objects(val id: String, val name: String)
If you then fetch the json you can just do this:
Gson().fromJson(yourJson, NearEarthObjects::class.java)
And you will get a list of all the objects name and id.
Related
I am struggling to achieve the following functionality: deserialize dynamic properties (incoming json) into array field/map.
Input:
{
dynamic_field1: "value1",
dynamic_field2: "value2",
...
name: "John",
age: 20
...
}
into Kotlin data class (although being a kotlin data class is not that relevant):
#JsonDeserialize(using = PersonDeserializer::class)
data class Person(
val personName: String,
#JsonProperty(using = SpecificIntDeserializer::class)
val age: Int,
val dymamicProperties: Map<String, String>
)
So essentially use (delegate) provided or default bean deserializer (whenever possible) BUT also get all fields that start with dynamic_ and process them separately.
What I tried so far:
find the ValueInstantiator, iterate through the main constructor (instantiator.withArgsCreator) and for each parameter find the right deserializer; however, I am unable to connect this with right JsonToken
tried ContextualDeserializer and ResolvableDeserializer but got lost.
Any suggestions?
All the API JSON responses would have the following structure:
{
"status": <Integer>
"data": <Object or List of Objects>
"message": <String>
}
the only property that changes is the 'data', which can be any object or list of object.
So is there a way to create a BaseResponse class like
open class BaseResponse<T> (
#SerializedName("status")
val status: Int,
#SerializedName("data")
abstract val `data`: T,
#SerializedName("message")
val message: String
)
and the response classes
data class HelloResponse (
override val `data`: Hello
) : BaseResponse<Hello> {
data class Hello (
#SerializedName("hello")
val hello: String
)
}
data class HellosResponse (
override val `data`: List<Hello>
) : BaseResponse<List<Hello>> {
data class Hello (
#SerializedName("hello")
val hello: String
)
}
What i really want is to only override the data property, so that i don't have to write status and message property for each Response sub data class i write. I dont want to write status and message in my sub class and pass it to base class, cause i'd still write both the properties, so no difference than creating a data class with status and message.
so cannot be like
data class HelloResponse (
val status: Int,
override val `data`: Hello,
val message: String
) : BasicResponse<Hello>(status, `data`, message) {
data class Hello (
#SerializedMessage("hello")
val hello: String
)
}
Edit: Own Answer
Well I realized that the HelloResponse is actually a waste since i'm only using it to access the actual Hello class.
So what i did was to use the Base class directly in Retrofit2 service.
Eg:
fun hello(): Call<BaseResponse<Hello>>
or
fun hellos(): Call<BaseResponse<List<Hello>>>
Well you have to directly specify the type with BaseResponse everywhere you use it. Maybe create typeallias
Or you can create alias
typealias HelloResponse = BaseResponse<Hello>
typealias HellosResponse = BaseResponse<List<Hello>>
To manually deserialize json string with Gson, you need to use TypeToken parameter instead of class type.
val hello = Gson().fromJson<BaseResponse<Hello>>(jsonStr, object: TypeToken<BaseResponse<Hello>>(){}.type)
If you use
val hello = Gson().fromJson<BaseResponse<Hello>>(jsonStr, BaseResponse::class.java)
The data property doesn't convert to Hello instead converts to LinkedHashMap
Note:
Retrofit2's GsonConverterFactory uses TypeToken internally, so no problem.
If you don't want to write status and message properties for data subclasses then you cannot expect subclass to have a constructor with status and message magically.
I strongly suggest you to make BaseResponse abstract and make subclasses like following
abstract class BaseResponse<T> {
#SerializedName("status")
abstract val status: Int
#SerializedName("message")
abstract val message: String
#SerializedName("data")
abstract val `data`: T
}
data class HelloResponse (
override val status: Int,
override val message: String,
override val `data`: Hello,
) : BaseResponse<Hello>() {
data class Hello (
#SerializedMessage("hello")
val hello: String
)
}
You can achieve it in a way you don't need to write override val properties for subclass declarations by sacrificing data classes. However you lose all bounties provided by data class.
abstract class BaseResponse<T> {
#SerializedName("status")
abstract val status: Int
#SerializedName("message")
abstract val message: String
#SerializedName("data")
abstract val `data`: T
}
class HelloResponse: BaseResponse<Hello>() {
data class Hello (
#SerializedMessage("hello")
val hello: String
)
}
Just a kind reminder, you don't need to use #SerializedName annotation if class property name and json property name are same.
Below is the simplified code that has a Map with key and value as case class.
Key and value object are successfully serialized with circe.
But facing challenge to serialize Map[CaseClassKey, CaseClassValue] with circe.
//////case classes and common vals starts here//////
case class Person(personId : Int, phoneNumber : Int)
case class Item(name : String)
case class Basket(items : List[Item], bills : Map[Int, Int])
def createBasketInstance() : Basket = {
val milk = Item("milk")
val coffee = Item("coffee")
val bills = Map(1 -> 20, 2 -> 75)
Basket( List(milk, coffee), bills )
}
val basket = createBasketInstance()
val person = Person(1, 987654)
//////case classes and common vals ends here//////
import io.circe._
import io.circe.generic.semiauto._
import io.circe.syntax._
import io.circe.parser._
//Serializing Person instance that is used as Key in Map
{
implicit val personCodec :Codec[Person] = deriveCodec[Person]
val jsonString = person.asJson.spaces2
println(jsonString)
}
println("-" * 50)
//Serializing Basket instance that is used as Value in Map
{
implicit val itemCodec :Codec[Item] = deriveCodec[Item]
implicit val basketCodec :Codec[Basket] = deriveCodec[Basket]
val jsonString = basket.asJson.spaces2
println(jsonString)
}
println("-" * 50)
//Serializing Map[Person, Basket]
//TODO : not able to make it work
{
implicit val itemCodec :Codec[Item] = deriveCodec[Item]
implicit val basketCodec :Codec[Basket] = deriveCodec[Basket]
val map = Map(person -> basket)
//TODO : How to make below lines work
//val jsonString = map.asJson.spaces2
//println(jsonString)
}
scalafiddle link : https://scalafiddle.io/sf/SkZNa1L/2
Note : Looking to just serialize and deserialize data (Map[Person, Basket]) correctly. How json looks is not really important in this particular case.
Strictly speaking, you are trying to create an invalid json.
Json map structure isn't an arbitrary map, it's an object structure where keys are property names.
https://www.json.org/json-en.html
See also Can we make object as key in map when using JSON?
Update:
I suggest a slight change to your model to get the job done.
Instead of Map use array of objects, each having two properties: key and value
Something like this:
case class Entry(key: Person, value: Basket)
So you can replace Map[Person, Basket] with Seq[Entry], and convert it back to Map if needed.
I need to parse an object that contains a property "triggers" which is an List<Trigger>. This list can contain 2 type of triggers: Custom and Event.
Here are my Trigger classes :
#JsonClass(generateAdapter = true)
open class Trigger(open val type: String,
open val source: String,
open val tags: Properties? = mutableMapOf())
#JsonClass(generateAdapter = true)
data class CustomTrigger(override val type: String,
override val source: String,
override val tags: Properties?,
//some other fields
) : Trigger(type, source, tags)
#JsonClass(generateAdapter = true)
data class EventTrigger(override val type: String,
override val source: String,
override val tags: Properties?,
//some other fields
) : Trigger(type, source, tags)
My object that I receive from server looks like this :
#JsonClass(generateAdapter = true)
data class Rule(val id: String,
val triggers: MutableList<Trigger>,
//some other fields
)
Using generated adapter on parsing I get on triggers only the fields from Trigger class. I need to implement a logic to parse an EventTrigger is type is "event" or an CustomTrigger if type is "custom".
How can I do this with Moshi?
Do I need to write a manual parser for my Rule object?
Any idea is welcome. Thank you
Take a look at the PolymorphicJsonAdapterFactory.
Moshi moshi = new Moshi.Builder()
.add(PolymorphicJsonAdapterFactory.of(HandOfCards.class, "hand_type")
.withSubtype(BlackjackHand.class, "blackjack")
.withSubtype(HoldemHand.class, "holdem"))
.build();
Note that it needs the optional moshi-adapters dependency.
This example from Moshi helped me to solve the parsing problem :
https://github.com/square/moshi#another-example
Consider this JSON:
{
"myDocument": {
"static_key": "value",
"dynamic_key": "value",
"static_key2": "value2",
"dynamic_key2": {
"dynamic_key3": "value3"
}
}
}
The JSON documents that I'm going to process have some static keys (fields that I know that are going to be present always) but has some others that could be or not be present, with names unbeknownst in advance for mapping them to some case class.
I have the absolute path to the field in a String (retrieved from a configuration stored in a Database) with the following structure:
"myDocument.dynamic_key2.dynamic_key3"
I know that I need to have an ADT for mapping back and forth, so I came to this:
sealed trait Data
final case class StringTuple(key: String, value: String) extends Data
object StringTuple {
implicit val encoder: Encoder[StringTuple] = deriveEncoder[StringTuple]
implicit val decoder: Decoder[StringTuple] = deriveDecoder[StringTuple]
}
final case class NumericTuple(key: String, value: Double) extends Data
object NumericTuple {
implicit val encoder: Encoder[NumericTuple] = deriveEncoder[NumericTuple]
implicit val decoder: Decoder[NumericTuple] = deriveDecoder[NumericTuple]
}
final case class DateTuple(key: String, value: OffsetDateTime) extends Data
object DateTuple {
implicit val encoder: Encoder[DateTuple] = deriveEncoder[DateTuple]
implicit val decoder: Decoder[DateTuple] = deriveDecoder[DateTuple]
}
final case class TransformedJson(data: Data*)
object TransformedJson {
def apply(data: Data*): TransformedJson = new TransformedJson(data: _*)
implicit val encoder: Encoder[TransformedJson] = deriveEncoder[TransformedJson]
implicit val decoder: Decoder[TransformedJson] = deriveDecoder[TransformedJson]
}
Based on this discussion, it makes no sense to use Map[String, Any] with circe, so I divided the three possible key-value cases that I will encounter when parsing individual fields:
A numeric field, that I'm going to parse as Double.
A String field parsed as is (String).
A date field parsed as OffsetDateTime.
For that reason, I've created three case classes that model these combinations (NumericTuple, StringTuple and DateTuple) and my idea is to produce an output JSON like this:
{
"dynamic_key": "extractedValue",
"dynamic_key3": "extractedValue3",
...
}
("Plain", with no nesting at all).
My idea is to create a list of Data objects to achieve this and I have something like this:
def extractValue(confElement: Conf, json: String) = {
val cursor: HCursor = parse(json).getOrElse(Json.Null).hcursor
val decodeDynamicParam = Decoder[NumericTuple].prepare(
/*
Here I think (not sure) that I can extract the value with the decoder,
but, how can I extract the key name and set it, alongside with the extracted
value?
*/
_.downField(confElement.path)
)
}
Some considerations:
Based on Travis' response to this question I'm trying to model the JSON as closely as possible for working with circe. That's why I tried with the tuples model.
Based (again) on Travis's response to this SO question is that I'm trying with Decode.prepare(...) method. And here comes my question...
Question: How do I extract the specific key name of the current cursor position and map it to the Tuple?
I need only the current key, not all the key set that .keys method of ACursor returns. With that key, I want to map manually the Tuple with the current key name and the extracted value.
For summing it up, my need is to transform a structure that has some unknown keys (name and position), extract their values based on the absolute-dot-separated path that I have and lift both the key name and the value name to a case class that I suffixed as Tuple.
Can you shed some light about this?
Thanks