JSON Deserialization of collections that contain null values - kotlin

I get the following JSON string:
{"userIds":[null, "FA9C67A8-1C22-4393-A701-136C85BB0D6F"]}
I would like to deserialize this to a set with one element (the UUID). My target class looks somewhat like this:
data class TestDto(
val userIds: Set<UUID> = emptySet(),
[...]
)
I am using Kotlin 1.5.21, Spring-Boot 2.4.4. The type of userIds does not allow null values in the set. However, when deserializing the JSON string the set consists of two values, the null value and the UUID. This leads to a NullPointerException later which I actually thought would be eliminated by setting the type to Set<UUID> instead of Set<UUID?>.
How can I sort of ignore/filter out any null values on deserialization if provided?

I think
#JsonSetter(contentNulls = Nulls.SKIP)
val userIds: Set<UUID> = emptySet(),
[...]
Should do the trick

Related

Jooq: JsonConverter not converting jsonb into list of class when fetching data

This is a continuation of a first question I asked here: Jooq: How can I map a JSONB column to a Kotlin data class field?
Although I'm able to create new records just fine with the changes mentioned there, I'm not being able to fetch data like so:
fun findAllTrackedEvents(): List<TrackedEvent> {
return dslContext.select(*TRACKED_EVENT.fields())
.from(TRACKED_EVENT)
.fetchInto(TrackedEvent::class.java)
}
It seems that jackson is mapping the rows into LinkedHashMaps instead of mapping them into the fields of the Metadata data class. This is the error I'm getting:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON:
object is not an instance of declaring class; nested exception is com.fasterxml.jackson.databind.JsonMappingException:
object is not an instance of declaring class (through reference chain: java.util.ArrayList[0]->com.my.project.tracked_event.TrackedEvent["metadata"]->java.util.ArrayList[0]->java.util.LinkedHashMap["tableRef"])]
data class TrackedEvent(
val id: UUID,
// other fields
val metadata: List<Metadata> // this metadata field in the database is of type jsonb
)
data class Metadata(
val tableRef: String,
val value: UUID
)
So it can convert the field properly when inserting but not when fetching?
In my previous answer, I suggested you use arrays instead of lists. This had a reason. Consider this:
fun main() {
val a: Array<Int?> = arrayOf(1)
println(a::class.java)
val b: List<Int?> = listOf(1)
println(b::class.java)
}
It prints:
class [Ljava.lang.Integer;
class java.util.Collections$SingletonList
As you can see, while arrays are reified on the JVM, other generic types are not, and the T type variable of List<T> is erased. It is possible that Jackson cannot figure out the correct type to unmarshal at runtime using reflection, despite all the type information being available at compile time.
I would just use Array<Metadata> instead. Alternatively, of course, you can attach a custom converter to the column, instead of using the out of the box <jsonConverter>. That way, you're in full control of the mapping.

Kotlin Retrofit: Need to convert Empty Array to Null

I'm new to Retrofit2. I have a legacy backend that's swapping a data object values with an empty array when the value is empty.
Scenario #1 Normal data response:
{data:{"name","john"}}
#Scenario 2 Alternate data response:
{data:[]}
Retrofit obviously can't map both data types without a converter (that I know of) automatically. When the data returns the empty array, the parser errors as expected.
I've attempted to use EmptyToNull converters, which fixes the error, but it also nulls out the normal data object returned in scenario #1.
My data classes are as follows:
data class ResponseData(
error: String,
message: String,
#SerializedName("data")
details: Details?
)
data class Details(
id: Int,
info: String
)
I've searched all over StackOverflow and other tutorials online, but I can't figure out how to detect the data / details value to ensure it's converted to null.
Thank you in advance!

How to replace null values to in map during deserialization in kotlin

I have data class, which is used for place response from some REST service:
data class RestResponse(
val themes: List<Map<String, String?>> = emptyList(),
)
and some values inside map could be empty strings.
But there is configuration in project that affect this:
enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
so, all empty strings are casted to null values.
I don't want to fix this setting for the whole project, but only want to empty strings for map in this response don't be cast to null values.
What is the best way to solve this?
As i see there could be different solutions:
Write custom deserializer
Fix null values after creating an response object
But it would be better for me if i could customize setting
enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
for only this data class.Is it possible?
P.S. - i use Jackson lib for mapping

Translate API response to nullable String

An API I consume returns (among other fields) a mandatory telephone1 and an optional telephone2. However, the JSON I fetch always contains both fields and a missing entry is displayed as an empty string.
{
"telephone1": "+1 555 1234",
"telephone2": ""
}
When the response is mapped to a pojo, is it preferable to translate the empty string to null? Such that:
data class(
val telephone1: String,
val telephone2: String?
}
To me, this better communicates the possible states. Should I, though? Are there drawbacks?
At the first sight, problem boils down to different checks before further data processing: x == null or x.isEmpty(). But while nullability check is generally enforced by kotlin compiler (unlike unemptiness), it seems to be a better option.
But there are still some situations when usage of null (without any compiler errors) may lead to problems in runtime (mainly related to interop with languages without enforced nullability): like implicit convertion of null to literal string "null" (when concatenating with another string), or even NPE when passed to method accepting String! (platform type) and not annotated properly.
Sticking to DDD principles, the better option would be declaration of separate datatypes:
sealed class OptionalPhoneNumber
data class PhoneNumber(val value: String) : OptionalPhoneNumber() //You may also add some checks in init block that `value` is really a phone number, not just a set of random chars
object EmptyPhoneNumber : OptionalPhoneNumber()
and defining your data class as:
data class Data (
val telephone1: PhoneNumber,
val telephone2: OptionalPhoneNumber
)
Type system will enforce you to do x is PhoneNumber checks, and thanks to smart casts it's further usage will be type-safe:
if (telephone2 is PhoneNumber) {
println(telephone2.value)
}

Jackson's SORT_PROPERTIES_ALPHABETICALLY for Map

I use Jackson 2.5.0.
I would like to write a method which takes arbitrary JSON-string and sorts every property by key alphabetically with Jackson. Including nested ones.
I learned there is a SORT_PROPERTIES_ALPHABETICALLY feature of Jackson's ObjectMapper which I wanted to use in order to achieve my goal. So my initial code based on this idea is:
class FooBar {
String foo
String bar
}
def sortFields(String source) {
def om = new ObjectMapper().configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
def obj = om.readValue(source, Map.class)
return om.writeValueAsString(obj)
}
println sortFields('{"foo":"f","bar":"b"}')
Notice that I can't know ahead what structure input JSON has so Jackson unmarshalls it as a Map by default (LinkedHashMap to be more precise).
I expected it to output a string with keys sorted alphabetically:
{"bar":"b","foo":"f"}
Unfortunately with the snippet above SORT_PROPERTIES_ALPHABETICALLY does not work when object to serialize is a Map. If I replace Map.class with FooBar.class my JSON properties will be sorted as expected. But as I said, I can't know ahead a type of input JSON and have a class in my code for any type possible. What other options do I have with Jackson?
This works for the top level map:
objectMapper.configure(
SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true
);
Sadly it doesn't sort keys in any potential nested map you may have.