Openapi how to avoid anyOf showing Null for optional response - kotlin

I have openapi response schema like this
ProfileMetrics:
description: List of metrics available in Field Profile
type: object
properties:
fieldProfileMetrics:
type: array
items:
type: object
summaryProfileMetrics:
type: array
items:
type: object
anyOf:
- required: [ fieldProfileMetrics ]
- required: [ summaryProfileMetrics ]
Which results in a Response for example where I don't want fieldProfileMetrics in the Response.
{
"fieldProfileMetrics": null,
"summaryProfileMetrics": [
{
"name": "count_events",
"description": "Number of rows/events in the dataset for particular timeframe",
"subMetrics": []
}]
}
So the response can be either {fieldProfileMetric : SomeValue} , {summaryProfileMetric : SomeValue} or {fieldProfileMetric : SomeValue, summaryProfileMetric : SomeValue}
The Service Class is like this
#Service
class ProfileMetricsService : ProfileMetricsApiService {
override fun v1MetricsGet(profile: ProfileType?): ProfileMetrics {
return when (profile) {
ProfileType.field -> convertToProfileMetrics(profile, getFieldProfileMetrics())
ProfileType.summary -> convertToProfileMetrics(
profile, getSummaryProfileMetrics()
)
else -> convertToProfileMetrics(
getFieldProfileMetrics(),
getSummaryProfileMetrics()
)
}
}
private fun getFieldProfileMetrics(): MutableList<Map<String, Serializable>> {
val fieldProfileList = mutableListOf<Map<String, Serializable>>()
FieldProfileMetrics.values().forEach { it ->
fieldProfileList.add(
mapOf(
Pair("name", it.value),
Pair(FieldProfileMetrics::description.name, it.description),
Pair(FieldProfileMetrics::subMetrics.name, it.subMetrics.map { it.value }.toTypedArray())
)
)
}
return fieldProfileList
}
private fun getSummaryProfileMetrics(): MutableList<Map<String, Serializable>> {
val summaryProfileList = mutableListOf<Map<String, Serializable>>()
SummaryProfileMetrics.values().forEach { profileMetric ->
summaryProfileList.add(
mapOf(
Pair("name", profileMetric.value),
Pair(SummaryProfileMetrics::description.name, profileMetric.description),
Pair(SummaryProfileMetrics::subMetrics.name, profileMetric.subMetrics.map { it.value }.toTypedArray())
)
)
}
return summaryProfileList
}
private fun convertToProfileMetrics(
profile: ProfileType,
metricList: MutableList<Map<String, Serializable>>
): ProfileMetrics {
return if (profile == ProfileType.field)
ProfileMetrics(fieldProfileMetrics = metricList)
else
ProfileMetrics(summaryProfileMetrics = metricList)
}
private fun convertToProfileMetrics(
fieldMetricList: MutableList<Map<String, Serializable>>,
summaryMetricList: MutableList<Map<String, Serializable>>
): ProfileMetrics {
return ProfileMetrics(
fieldProfileMetrics = fieldMetricList,
summaryProfileMetrics = summaryMetricList
)
}
companion object {
val logger = LoggerFactory.getLogger(ProfileMetricsService::class.java)
}
}
The interface generated after generate-sources gives this
interface ProfileMetricsApiService {
/**
* GET /v1/metrics : Get list of field and summary profile metrics
* Retrieves list of metrics for field and summary profile
*
* #param profile Profile Type (optional)
* #return OK (status code 200)
* or Bad Request. (status code 400)
* or The specified resource was not found. (status code 404)
* or Unexpected error. (status code 200)
* #see ProfileMetricsApi#v1MetricsGet
*/
fun v1MetricsGet(profile: ProfileType?): ProfileMetrics
}
ProfileMetrics after compilation generates this
/**
* List of metrics available in Field Profile
* #param fieldProfileMetrics
* #param summaryProfileMetrics
*/
data class ProfileMetrics(
#field:Valid
#Schema(example = "null", description = "")
#field:JsonProperty("fieldProfileMetrics") val fieldProfileMetrics: kotlin.collections.List<kotlin.Any>? = null,
#field:Valid
#Schema(example = "null", description = "")
#field:JsonProperty("summaryProfileMetrics") val summaryProfileMetrics: kotlin.collections.List<kotlin.Any>? = null
) {
}
This is how the response looks like
Any help is appreciated.

Related

Encoding a password with argon2 and save on PostgreSQL

I'm trying to encox user password to save in postgresql database. Using kotlin and argon2. I did the functions and in the tests, it works perfectly. But when I save it in the database, and I try to compare the passwords, it always gives an error. Could anyone help?
I created the following functions:
private val argon2 = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id, 32, 64)
fun String.encode(): String = argon2.hash(3, 64 * 1024, 1, this.toCharArray())
fun String.checkEncoding(hash: String): Boolean = argon2.verify(hash, this.toCharArray())
and in tests, everything works perfectly
class HashingTest : ShouldSpec({
context("Encoding a string") {
should("encode correctly") {
val dataOne = "S#!sc_%kah"
val encondeOne = dataOne.encode()
val dataTwo = "S#!sc_%kah"
dataTwo.checkEncoding(encondeOne) shouldBe true // works fine!
}
}
})
When I save to the database, and try to compare, I always get an error
//ENTITY
data class User(
override val id: UUID,
val username: String,
val password: String,
) : IEntity
//INSERT SCRITP -> EXPOSED FRAMEWORK
private fun newUser(schema: String, entity: User) {
val table = toUserTable(schema)
table
.insert {
it[id] = entity.id
it[username] = entity.username
it[password] = entity.password.encode()
}
}
//FIND USER
fun findUser(schema: String, model: SignInModel): User? {
val table = toUserTable(schema)
val user = table
.select { table.username eq model.username }
.firstNotNullOfOrNull { toUser(schema, it) }
val verifying = model.password.checkEncoding(user!!.password) // ERROR HERE, ALWAYS RETURNS FALSE
return when (verifying) {
true -> user
false -> null
}
}
I don't know if you used your findUser() and newUser() in a transaction but queries must be called in a transaction like below:
private fun newUser(schema: String, entity: User) {
val table = toUserTable(schema)
transaction {
table
.insert {
it[id] = entity.id
it[username] = entity.username
it[password] = entity.password.encode()
}
}
}
and:
fun findUser(schema: String, model: SignInModel): User? {
val table = toUserTable(schema)
val user = transaction {
table
.select { table.username eq model.username }
.firstNotNullOfOrNull { toUser(schema, it) }
}
val verifying = model.password.checkEncoding(user!!.password)
return when (verifying) {
true -> user
false -> null
}
}

Returning one of different object types from single function in kotlin

I have the following structure at present:
#Entity
#Table(name = "table_app_settings")
data class AppSetting(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "app_setting_id")
val id: Long? = null,
#Column(name = "app_setting_name")
val name: String = "",
#Column(name = "app_setting_value")
var value: String = "",
#Column(name = "app_setting_type")
val type: AppSettingType,
)
enum class AppSettingType {
CHAR,
STRING,
BYTE,
SHORT,
INT,
LONG,
DOUBLE,
FLOAT,
BOOLEAN,
}
This is then saved to the database with the following:
override fun saveAppSetting(setting: AppSetting): DatabaseResult<AppSetting> {
log.info("Saving App Setting ${setting.name} to database.")
return try {
// Attempt to save the entity to the database. If we do not throw an exception, return success.
val savedSetting = appSettingsRepository.save(setting)
DatabaseResult(
code = ResultCode.CREATION_SUCCESS,
entity = savedSetting
)
} catch(exception: DataAccessException) {
log.error("Unable to save App Setting ${setting.name} to database. Reason: ${exception.message}")
DatabaseResult(
code = ResultCode.CREATION_FAILURE
)
}
}
Now, let's say that I wish to save a Char type to database, I figure I would use the following:
override fun saveAppSetting(name: String, value: Char): DatabaseResult<Char> {
val appSettingResult = saveAppSetting(AppSetting(
name = name,
value = value.toString(),
type = AppSettingType.CHAR,
))
return if(appSettingResult.code != ResultCode.CREATION_FAILURE) {
val entity = getAppSetting<Char>(appSettingResult.entity?.name!!).entity.toString().first()
DatabaseResult(
code = appSettingResult.code,
entity = entity
)
} else {
DatabaseResult(
code = ResultCode.CREATION_FAILURE,
)
}
}
I also figured that I would need to do the following in order to retrieve the correct object type:
override fun getAppSetting(name: String): DatabaseResult<Any?> {
log.info("Getting App Setting $name from database.")
val appSetting = appSettingsRepository.findAppSettingByName(name)
return if(appSetting != null) {
log.info("App Setting $name has ID of ${appSetting.id} within the database")
when(appSetting.type) {
AppSettingType.CHAR -> {
DatabaseResult<Char>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.first(),
)
}
AppSettingType.STRING -> {
DatabaseResult<String>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value,
)
}
AppSettingType.BYTE -> {
DatabaseResult<Byte>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toByte(),
)
}
AppSettingType.SHORT -> {
DatabaseResult<Short>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toShort(),
)
}
AppSettingType.INT -> {
DatabaseResult<Int>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toInt(),
)
}
AppSettingType.LONG -> {
DatabaseResult<Long>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toLong(),
)
}
AppSettingType.DOUBLE -> {
DatabaseResult<Double>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toDouble(),
)
}
AppSettingType.FLOAT -> {
DatabaseResult<Float>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toFloat()
)
}
AppSettingType.BOOLEAN -> {
DatabaseResult<Boolean>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toBoolean()
)
}
}
} else {
log.error("App Setting $name does not seem to exist within the database.")
DatabaseResult(
code = ResultCode.FETCH_FAILURE
)
}
However, when I then wish to use said object, I still have to write something like the following:
val newBarcode = getAppSetting("barcode_value").entity.toString().toInt()
Assuming I've "initialised" barcode_value with a value of 177 (for example).
How can I get the function to return what I need without having to do .toString.to...()?
Yes this all possible, here is a simplified demo, firstly
import kotlin.reflect.KClass
data class AppSetting(
val id: Long? = null,
val name: String = "",
var value: String = "",
val type: AppSettingType,
)
enum class AppSettingType(val clazz: KClass<out Any>) {
CHAR(Char::class),
STRING(String::class),
INT(Int::class),
}
So I added a clazz so from the enum we know the Kotlin type
and now a function to simulate your repository fetch
fun findAppSettingByName(name: String): AppSetting? {
return when(name) {
"Char thing" -> AppSetting(value= "C", type = AppSettingType.CHAR)
"String thing" -> AppSetting(value= "Str", type = AppSettingType.STRING)
"Int thing" -> AppSetting(value= "42", type = AppSettingType.INT)
else -> throw IllegalArgumentException()
}
}
Next in the function declaration I have made it generic with T and for the purposes of the demo removed the DatabaseResult container. Then I added a clazz parameter which is the typical Java way of carrying the required class information into the function:
fun <T : Any> getAppSetting(name: String, clazz: KClass<T>): T? {
val appSetting: AppSetting? = findAppSettingByName(name)
return appSetting?.let {
require(clazz == appSetting.type.clazz) {
"appSetting.type=${appSetting.type.clazz} mismatched with requested class=${clazz}"
}
when (appSetting.type) {
AppSettingType.CHAR -> appSetting.value.first()
AppSettingType.STRING -> appSetting.value
AppSettingType.INT -> appSetting.value.toInt()
} as T
}
}
the as T is important to cast the values into the required return type - this is unchecked but the when() clause should be creating the correct types.
Now let's test it:
val c1: Char? = getAppSetting("Char thing", Char::class)
val s1: String? = getAppSetting("String thing", String::class)
val i1: Int? = getAppSetting("Int thing", Int::class)
println("c1=$c1 s1=$s1 i1=$i1")
val c2: Char? = getAppSetting("Char thing")
val s2: String? = getAppSetting("String thing")
val i2: Int? = getAppSetting("Int thing")
println("c2=$c2 s2=$s2 i2=$i2")
}
The output is
c1=C s1=Str i1=42
c2=C s2=Str i2=42
But how do c2/s2/i2 work, the final part is this function
inline fun <reified T : Any> getAppSetting(name: String) = getAppSetting(name, T::class)
This is reified generic parameters... there is no need to pass the clazz because this can be found from the data type of the receiving variable.
There are many articles about this advanced topic, e.g.
https://typealias.com/guides/getting-real-with-reified-type-parameters/
https://medium.com/kotlin-thursdays/introduction-to-kotlin-generics-reified-generic-parameters-7643f53ba513
Now, I didn't completely answer what you wanted because you wanted to receive a DatabaseResult<T> wrapper. What might be possible, is to have a function that returns DatabaseResult<T> and you can obtain the T from it as the "clazz" parameter, but I'll leave that for someone else to improve on :-) but I think that gets you pretty close.

Wait for all volley request in a for loop

In my function, I need to return a list that is populated by a for loop with some Volley Request. So I need to wait that all of these requests to be terminated before return the list.
I think I need the async CoroutineScope to do this work but I don't know how can I wait for all of that response.
This is my code:
suspend fun getListOfAbility(pokemon: Pokemon) : MutableList<Ability> {
val listOfAbility: MutableList<Ability> = emptyList<Ability>() as MutableList<Ability>
CoroutineScope(Dispatchers.IO).launch {
/**
* get the pokemon json
*/
val pokemonJsonObjectRequest = JsonObjectRequest(
Request.Method.GET,
"$pokemonUrl${pokemon.id}",
null,
{
/**
* onResponse
*
* get the list of pokemon abilities
*/
val abilitiesJO = it.getJSONObject("abilities")
val abilityObjectType = object : TypeToken<List<PokemonGson.AbilityObjectGson>>() { }.type
val abilityListGson = Gson().fromJson<List<PokemonGson.AbilityObjectGson>>(abilitiesJO.toString(), abilityObjectType)
/**
* for each ability listed on pokemon info get the full Ability Object
*/
for((index, abilityObjectGson) in abilityListGson.withIndex()) {
val abilityJsonObjectRequest = JsonObjectRequest(
Request.Method.GET,
abilityObjectGson.ability.url,
null,
{
abilityJson ->
/**
* onResponse
*
* get the full ability info
*/
val abilityType = object : TypeToken<AbilityGson>() { }.type
val abilityGson = Gson().fromJson<AbilityGson>(abilityJson.toString(), abilityType)
/**
* fill the Ability entry of listOfAbility with the correct language
*/
val ability = Ability(abilityGson, abilityListGson[index].is_hidden)
listOfAbility.add(ability)
},
{
/**
* onError
*/
Log.d("POKEMON", "Pokemon ability error")
}
)
requestQueue.add(abilityJsonObjectRequest)
}
},
{
/**
* onError
*/
Log.d("POKEMON", "Pokemon request error")
}
)
requestQueue.add(pokemonJsonObjectRequest)
}
//wait
return listOfAbility
}
To use callback-based code in a suspend function, you need to convert it to a suspend function using suspendCoroutine or suspendCancellableCoroutine. So in this case to replace the action of creating a JSONObjectRequest and listener, queuing it to the RequestQueue, and waiting for it somehow, I would create a suspend function like this:
suspend inline fun RequestQueue.getJSONObjectOrNull(
method: Int,
url: String,
jsonRequest: JSONObject?,
crossinline onError: (VolleyError)->Unit = {}
): JSONObject? = suspendCancellableCoroutine { continuation ->
val request = JsonObjectRequest(
method,
url,
jsonRequest,
{ result: JSONObject -> continuation.resume(result) },
{ error ->
onError(error)
continuation.resume(null)
}
)
add(request)
continuation.invokeOnCancellation { request.cancel() }
}
It directly returns the JSONObject result, or null if there's a failure. You can optionally run a callback on errors in case you want to log it.
Then you can use it to write a more sequential version of your function instead of the callback-based version. You can use the pattern of coroutineScope { async { list.map { ... } } }.awaitAll() to convert each item of a list to something else using parallel coroutines.
Here is an untested version of your function. I am having it return an empty list on failure. You could alternatively return null on failure, which might be more useful so the calling function can decide to do something differently when there's a failure.
private fun VolleyError.logDebug() {
Log.d("POKEMON", "Pokemon request error: $this")
}
suspend fun getListOfAbility(pokemon: Pokemon): List<Ability> {
val pokemonJsonObject = requestQueue.getJSONObjectOrNull(Request.Method.GET, "$pokemonUrl${pokemon.id}", null, VolleyError::logDebug)
pokemonJsonObject ?: return emptyList()
val abilitiesJO = pokemonJsonObject.getJSONObject("abilities")
val abilityObjectType = object : TypeToken<List<PokemonGson.AbilityObjectGson>>() {}.type
val abilityListGson: List<Wrapper> = Gson().fromJson<List<PokemonGson.AbilityObjectGson>>(
abilitiesJO.toString(),
abilityObjectType
)
return coroutineScope {
abilityListGson.map {
async {
requestQueue.getJSONObjectOrNull(Request.Method.GET, it.ability.url, null, VolleyError::logDebug)
}
}
}
.awaitAll()
.filterNotNull()
.map { abilityJson ->
val abilityType = object : TypeToken<AbilityGson>() {}.type
val abilityGson = Gson().fromJson<AbilityGson>(abilityJson.toString(), abilityType)
Ability(abilityGson, abilityListGson[index].is_hidden)
}
}

Having trouble with deserialising JSON nullable fields in Kotlin with custom decoder

I am having difficulty decoding this JSON with Kotlin Serialization. It works well when the data field is not empty. However when the data field is null and the errors field is present I get this runtime exception:
kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 7: Expected start of the object '{', but had ':' instead
JSON input: {"data":null,"errors":[{"path":null,"locations":[{"line":3,"column":5,"sourceName":null}],"message":"Validation error of type FieldUndefined: Field 'mee' in type 'Query' is undefined # 'mee'"}]})
The JSON pretty printed:
{
"data": null,
"errors": [{
"path": null,
"locations": [{
"line": 3,
"column": 5,
"sourceName": null
}],
"message": "Validation error of type FieldUndefined: Field 'mee' in type 'Query' is undefined # 'mee'"
}]
}
The code which is mostly stolen from How to serialize a generic class with kontlinx.serialization? :
class ApiResponse<T>(
#SerialName("data")
val data: T? = null,
#SerialName("errors")
val errors: List<ErrorResponse>? = null
)
#Serializable
class ErrorResponse(
val path: String? = null,
val locations: List<Location>? = null,
val errorType: String? = null,
val message: String? = null
)
#Serializable
data class Location(
val line: Int? = 0,
val column: Int? = 0,
val sourceName: String? = null
)
#ExperimentalSerializationApi
class ApiResponseSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<ApiResponse<T>> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ApiResponseDataSerializer") {
val dataDescriptor = dataSerializer.descriptor
element("data", dataDescriptor)
element("errors", ErrorResponse.serializer().descriptor)
}
override fun deserialize(decoder: Decoder): ApiResponse<T> =
decoder.decodeStructure(descriptor) {
var data: T? = null
var errors: List<ErrorResponse>? = null
val listSerializer = ListSerializer(ErrorResponse.serializer())
loop# while (true) {
when (val i = decodeElementIndex(descriptor)) {
0 -> data = decodeSerializableElement(descriptor, i, dataSerializer, null)
1 -> errors = decodeSerializableElement(descriptor, i, ListSerializer(ErrorResponse.serializer()), null)
CompositeDecoder.DECODE_DONE -> break
else -> throw SerializationException("Unknown index $i")
}
}
ApiResponse(data, errors)
}
override fun serialize(encoder: Encoder, value: ApiResponse<T>) {
encoder.encodeStructure(descriptor) {
val listSerializer = ListSerializer(ErrorResponse.serializer())
encodeNullableSerializableElement(descriptor, 0, dataSerializer, value.data)
value.errors?.let {
encodeNullableSerializableElement(descriptor, 1, listSerializer, it)
}
}
}
}
I tried using decodeNullableSerializableElement, but I got a compilation error. I couldn't find a way to fix that.
Type mismatch: inferred type is KSerializer<T> but DeserializationStrategy<TypeVariable(T)?> was expected
Any help would be appreciated, I am very new to Android and Kotlin.
Always pays to come back after a good nights sleep. Not sure why I had so much trouble with decodeNullableSerializableElement yesterday, but today I played around and got it working.
Made 3 changes
Made T optional in class parameter
Added .nullable (could not get this to work yesterday) to both serialisers
Changed decodeSerializableElement to decodeNullableSerializableElement
Relevant changes below:
#ExperimentalSerializationApi
class ApiResponseSerializer<T>(private val dataSerializer: KSerializer<T?>) : KSerializer<ApiResponse<T>> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ApiResponseDataSerializer") {
val dataDescriptor = dataSerializer.descriptor
element("data", dataDescriptor.nullable)
element("errors", ErrorResponse.serializer().descriptor.nullable)
}
override fun deserialize(decoder: Decoder): ApiResponse<T> =
decoder.decodeStructure(descriptor) {
var data: T? = null
var errors: List<ErrorResponse>? = null
val listSerializer = ListSerializer(ErrorResponse.serializer()).nullable
loop# while (true) {
when (val i = decodeElementIndex(descriptor)) {
0 -> data = decodeNullableSerializableElement(descriptor, i, dataSerializer, null)
1 -> errors = decodeNullableSerializableElement(descriptor, i, listSerializer, null)
CompositeDecoder.DECODE_DONE -> break
else -> throw SerializationException("Unknown index $i")
}
}
ApiResponse(data, errors)
}
override fun serialize(encoder: Encoder, value: ApiResponse<T>) {
encoder.encodeStructure(descriptor) {
val listSerializer = ListSerializer(ErrorResponse.serializer())
encodeNullableSerializableElement(descriptor, 0, dataSerializer, value.data)
value.errors?.let {
encodeNullableSerializableElement(descriptor, 1, listSerializer, it)
}
}
}
}

Kotlin data class create dynamically json of its fields using GSON

I have a data class like this:
data class TestModel(
val id: Int,
val description: String,
val picture: String)
If I create JSON from this data class using GSON and it generates a result like this
{"id":1,"description":"Test", "picture": "picturePath"}
What to do if I need the following JSON from my data class:
{"id":1, "description":"Test"}
And other times:
`{"id":1, "picture": "picturePath"}
`
Thanks in advance!
You can solve this problem with writing custom adapter and with optional types:
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
data class TestModel(
val id: Int,
val description: String? = "",
val picture: String? = "")
class TesModelTypeAdapter : TypeAdapter<TestModel>() {
override fun read(reader: JsonReader?): TestModel {
var id: Int? = null
var picture: String? = null
var description: String? = null
reader?.beginObject()
while (reader?.hasNext() == true) {
val name = reader.nextName()
if (reader.peek() == JsonToken.NULL) {
reader.nextNull()
continue
}
when (name) {
"id" -> id = reader.nextInt()
"picture" -> picture = reader.nextString()
"description" -> description = reader.nextString()
}
}
reader?.endObject()
return when {
!picture.isNullOrBlank() && description.isNullOrBlank() -> TestModel(id = id ?: 0, picture = picture)
!description.isNullOrBlank() && picture.isNullOrBlank() -> TestModel(id = id ?: 0, description = description)
else -> TestModel(id ?: 0, picture, description)
}
}
override fun write(out: JsonWriter?, value: TestModel?) {
out?.apply {
beginObject()
value?.let {
when {
!it.picture.isNullOrBlank() && it.description.isNullOrBlank() -> {
name("id").value(it.id)
name("picture").value(it.picture)
}
!it.description.isNullOrBlank() && it.picture.isNullOrBlank() -> {
name("id").value(it.id)
name("description").value(it.description)
}
else -> {
name("id").value(it.id)
name("picture").value(it.picture)
name("description").value(it.description)
}
}
}
endObject()
}
}
}
class App {
companion object {
#JvmStatic fun main(args: Array<String>) {
val tm = TestModel(12, description = "Hello desc")
val tm2 = TestModel(23, picture = "https://www.pexels.com/photo/daylight-forest-glossy-lake-443446/")
val tm3 = TestModel(12, "Hello desc", "https://www.pexels.com/photo/daylight-forest-glossy-lake-443446/")
val gson = GsonBuilder().registerTypeAdapter(TestModel::class.java, TesModelTypeAdapter()).create()
System.out.println(gson.toJson(tm))
System.out.println(gson.toJson(tm2))
System.out.println(gson.toJson(tm3))
}
}
}
Here is actually a way to ignore fields, that are not marked via #Exposed annotation. In order for this to work, special configuration should be used when instantiating Gson. Here is how you can to this.
Easy way is to mark the field as #Transient. Then it would not be either serialized and deserialized.
I want to give you alternative ways without manually serialization/deserialization.
data class TestModel(
val id: Int,
val description: String? = null,
val picture: String? = null)
When you create json from data class
val params = TestModel(id = 1, description = "custom text")
or
val params = TestModel(id = 1, picture = "picture path")
If one of them field is null of data class GSON skips that field
automatically.