I need to store my schedule in a single JSON file so I can load it again to my app if needed. The schedules are stored using the following three classes:
data class DayScheduleItem(
var hourAndMin: Calendar = Calendar.getInstance(),
): ScheduleItem()
data class ExceptionItem(
var fromDate: Calendar = Calendar.getInstance(),
var toDate: Calendar = Calendar.getInstance(),
): ScheduleItem()
data class WeekDayScheduleItem(
var name: String,
): ScheduleItem() {
var isOpen: Boolean = false
}
There is then the super class:
class ScheduleItem {
var id: Int? = null
get() = field
set(value) {
field= value
}
var data: List<Byte> = emptyList()
get() = field
set(value) {
field = value
}
}
Now I have a list as follows:
[
[
DayScheduleItem1,
DayScheduleItem2
],
[
WeekDayScheduleItem1,
WeekDayScheduleItem2,
WeekDayScheduleItem3,
WeekDayScheduleItem4
],
[
]
]
The questions are:
How to parse this json using GSON.fromJson()?
Do I need to simplify my classes?
Do I need to use another library like Moshi?
Shall I change the structure of my JSON?
1. How to parse this json using GSON.fromJson()?
Gson does not provide polymorphism support out of the box, see related questions such as this one. Normally you would have to include some kind of type identifier in the JSON data for each schedule item and then on deserialization based on this identifier decide as which schedule item subclass to deserialize.
The Jackson library has built-in support for this and it also supports deduction-based polymorphism where it only decides based on the properties which class the data represents, without requiring a separate property in JSON, though this might be a bit more difficult to maintain when adding or removing properties in the future.
3. Do I need to use another library like Moshi?
You could achieve this with Gson, but for your use case another library might be more suitable nonetheless:
As mentioned above Jackson provides built-in support for polymorphism
Gson is mainly designed for Java and its Kotlin support is limited; Moshi and Jackson explicitly support Kotlin and might therefore be easier to use for a Kotlin project
4. Shall I change the structure of my JSON?
If for your use case the values are always grouped by schedule type in the JSON data, then you might not actually need polymorphism support. It would be simpler if you use a JSON object to group the schedule items:
{
"day": [
DayScheduleItem1,
DayScheduleItem2
],
"weekday": [
WeekDayScheduleItem1,
WeekDayScheduleItem2
],
...
}
Then you could write a data class which has all these entries as properties, e.g. val day: List<DayScheduleItem>, and serialize and deserialize that data class.
Related
I have an app which is mixed Java and Kotlin.
In the Kotlin code I use Moshi to convert an object to Json in a convertor for a Room database table.
I have one case that works perfectly but another one produces the error:
Not enough information to infer type variable T
This is what my code looks like:
val type: Type = Types.newParameterizedType(
MutableMap::class.java,
LayerTwoConn::class.java,
TWeFiState::class.java,
WfMeasureFileMgr::class.java,
Traffic::class.java,
ThroughputCalculator::class.java,
CellSubTechThroughput::class.java,
LongValuesAverageCalculator::class.java,
LayerTwoConn.SenselessTraffic::class.java
)
val json = Moshi.Builder().build().adapter(type).toJson(layerTwoConn)
I have included all the classes that are used in the objects.
What have I missed?
This case works perfectly:
val type: Type = Types.newParameterizedType(
MutableList::class.java,
CnrScan::class.java,
)
val jsonAdapter: JsonAdapter<List<CnrScan>> = Moshi.Builder().build().adapter(type)
val json = jsonAdapter.toJson(list)
In this object, all the internally used classes are standard Java class and not my own.
Have I missed something simple?
I don't know if this is important but the class LayerTwoConn's constructor is private.
I think you are trying to convert too many classes into one type, try to convert MutableMap class and LayerTwoConn class.
Do note that Room uses SQL architecture, so try to predict what you want your table to contain
I have an API which its response is kind of dynamic. I mean sometimes it return a Jason object with "token" value, and sometimes it returns with "message" value. For handling this scenario I decided to have both field in my data class like below:
data class response {
val message:String;
val token:String;
}
Now I want to make both fields optional in Kotlin serialization. I mean, I want to tell Kotlin serialization that if you couldn't find token in response JSON it's ok to ignore it.
How can I achieve this?
All Kotlin properties with default values are automatically optional.
All I need to do is this:
data class response {
val message:String="";
val token:String="";
}
I would like to know what is the best way to integrate Retrofit with MoShi on my Android Studio project.
First of all, I use the moshi converter of retrofit :
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
I use the popular plugin "Json to Kotlin class" for the generation of my POJO :
https://plugins.jetbrains.com/plugin/9960-json-to-kotlin-class-jsontokotlinclass-
When I create a new POJO, I use the annotation "MoShi (Reflect)" :
It generate me a Pojo with fields like this one :
#Json(name = "image_url")
val imageUrl: String?
The problem is Retrofit2 seem don't like underscore in my fields name because I get null results with the underscored names.
Searching in the Github issues, I found a solution. It work if I use this annotation :
#field:Json(name = "image_url")
val imageUrl: String?
So my questions are :
Using the #field:Json annotation is the best way to use MoShi with Retrofit ?
If yes, how to generate easily a Kotlin class with this annotation (using the same plugin or an other) ?
As apparent from this part of Moshi's documentation: "There’s no field naming strategy, versioning, instance creators, or long serialization policy. Instead of naming a field visibleCards and using a policy class to convert that to visible_cards, Moshi wants you to just name the field visible_cards as it appears in the JSON."
The preferred way is for you to not use the annotation, but instead name the fields the same way as in the JSON. With Kotlin you can use backticks for names that wouldn't otherwise be valid, e.g.val `image-url`: String?
Of course you wouldn't want to be working with such names in other parts of your code, and for that you should have separate objects for describing the data as it appears on the back-end, and another object for how it should appear in your application. We call those DTOs (Data-Transfer Objects.)
I'm trying to implement JSON parsing in my Android application written in Kotlin using com.squareup.moshi (v1.10.0).
Within the JSON file there are some properties that are not interesting in my case. Let's say, I only need the position to be able to mark the place on a map and the JSON looks like this:
"location":{
"address":{
"country":"..."
},
"position":{
"lat":47.469866,
"lon":19.062435
}
}
If I'm right, the data class in Kotlin should look like this if I'd like to parse that JSON:
#Parcelize
data class Location(
val address: Address,
val position: Position
): Parcelable
#Parcelize
data class Address(
val country: String
): Parcelable
#Parcelize
data class Position(
val lat: Double,
val lon: Double
): Parcelable
In Moshi's documentation I could find the transient keyword to skip values which in Kotlin works as an annotation (#Transient). As the documentation says:
Transient fields are omitted when writing JSON. When reading JSON, the field is skipped even if the JSON contains a value for the field. Instead it will get a default value.
Does it mean that if I don't want to have the address object, I should use the following code?
#Parcelize
data class Location(
#Transient val address: Address? = null,
val position: Position
): Parcelable
Also, what about in general terms? What if I have huge list of properties within a JSON object but I know I only need the 'position' object? Do I still have to create null values to parse the JSON file field-by-field?
I think you are looking for something similar to GSON's #Expose annotations, wherein all model fields are excluded from parsing except those annotated.
This functionality is currently not available in Moshi, so your current implementation using the #Transient annotation seems to be the most optimal solution. (See Moshi issues conversation here.)
Extra food for thought:
You may also wish to use #IgnoredOnParcel on your transient fields since you are implementing the parcelable interface. (Have a look here for some implementation pointers.)
Alternatively you could separate your data model into 2 models - one for use in your app and one which reflects the server (JSON) schema (just as you have done above). The main data model for your app (which could implement parcelable) would contain only the fields you use (for example, the position field). When you parse your data, you then convert that data to your primary data model using some simple adapter. (This is often good practice anyhow, since server-side schemas are inherent to change. This way, any changes in the JSON schema wouldn't end having any ripple effect throughout your code.)
https://github.com/square/moshi#omit-fields-with-transient
Omit fields with transient
Some models declare fields that shouldn’t be included in JSON. For example, suppose our blackjack hand has a total field with the sum of the cards:
public final class BlackjackHand {
private int total;
...
}
By default, all fields are emitted when encoding JSON, and all fields are accepted when decoding JSON. Prevent a field from being included by adding Java’s transient keyword:
public final class BlackjackHand {
private transient int total;
...
}
Transient fields are omitted when writing JSON. When reading JSON, the field is skipped even if the JSON contains a value for the field. Instead it will get a default value.
Is there any way to automatically apply naming conventions to Moshi when serializing data using Moshi?
e.g.
I have this class which follows Kotlin naming conventions for properties (camel case)
class Player {
var currentHealth: Int = 100
var firstName = "John"
var lastName = "Doe"
}
I would like to serialize it to something which will stick to the naming conventions we have on the server (snake case):
{
current_health: 100,
first_name: "John",
last_name: "Doe"
}
I already know we can customize column names on each property manually. Just looking for a way to do that automatically (for instance if we add properties to the POJO or on server side).
I'm one of the Moshi developers and it doesn't have this feature because I think it is harmful. I've described why in full detail here. In brief, case mapping breaks your ability to search across your codebase.