Jackson SNAKE_CASE How to generate underscore in field names before number - kotlin

I have the next peace of code
#Test
fun `simple test`() {
val objectMapper = ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
.registerModule(KotlinModule())
val value = objectMapper.writeValueAsString(MyClass(myField1 = "something", myField2 = "something2"))
assertNotNull(value)
}
data class MyClass (
val myField1: String? = null,
#JsonProperty("my_field_2")
val myField2: String? = null,
)
the result of deserialization is next
{"my_field1":"something","my_field_2":"something2"}
Is it possible to configure objectMapper to automatically populate _ value, before digits in object property names, without specifying it in #JsonProperty?

Yes, this is possible using a PropertyNamingStrategy:
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
Note that you named your snake-case fields inconsistently, because there is my_field1 without a _ before the digit, and my_field_2 with a _ before the digit. The configuration above using PropertyNamingStrategies.SNAKE_CASE works fine for the first naming (like in my_field1).
If you want to use the second naming (like in my_field_2), then you would have to write your own naming strategy like this:
class MySnakeCaseStrategy : NamingBase() {
override fun translate(input: String?): String? =
if (input == null) null
else "([A-Z]+|[0-9]+)".toRegex().replace(input) { "_${it.groupValues[1]}".lowercase() }
}
That naming strategy can then be used to configure your object-mapper:
objectMapper.setPropertyNamingStrategy(MySnakeCaseStrategy())
I do not know if and how it would be possible to support both naming strategies at the same time.

Related

How does Kotlin's data class copy idiom look for nullable types?

I have some code which looks like this, where param is of a data class type:
val options = if (param.language == null) {
param.copy(language = default())
} else {
param
}
Now, however, the language object has been moved into a hierarchy of nullable objects, so the check must look like this:
if (param.subObj?.nextObj?.language == null) { ... }
How do I use the copy idiom in this case?
One way to do this is:
val newParam = when {
param.subObj == null -> param.copy(subObj = SubObj(nextObj = NextObj(language = Language())))
param.subObj.nextObj == null -> param.copy(subObj = param.subObj.copy(nextObj = NextObj(language = Language())))
param.subObj.nextObj.language == null -> param.copy(subObj = param.subObj.copy(nextObj = param.subObj.nextObj.copy(language = Language())))
else -> param
}
I agree that this doesn't look very clean but this seems to be the only way to me, because at each step you need to check if the current property is null or not. If it is null, you need to use the default instance otherwise you need to make a copy.
Could you do something like this?
// you could create a DefaultCopyable interface if you like
data class SubObj(val prop1: Double? = null, val nextObj: NextObj? = null) {
fun copyWithDefaults() =
copy(prop1 = prop1 ?: 1.0, nextObj = nextObj?.copyWithDefaults() ?: NextObj())
}
data class NextObj(val name: String? = null) {
fun copyWithDefaults() = copy(name = name ?: "Hi")
}
I think you need a special function because you're not using the standard copy functionality exactly, you need some custom logic to define defaults for each class. But by putting that function in each of your classes, they all know how to copy themselves, and each copy function that works with other types can just call their default-copy functions.
The problem there though is:
fun main() {
val thing = SubObj(3.0)
val newThing = thing.copyWithDefaults()
println("$thing\n$newThing")
}
> SubObj(prop1=3.0, nextObj=null)
> SubObj(prop1=3.0, nextObj=NextObj(name=null))
Because nextObj was null in SubObj, it has to create one instead of copying it. But the real default value for name is null - it doesn't know how to instantiate one with the other defaults, that's an internal detail of NextObj. You could always call NextObj().copyWithDefaults() but that starts to look like a code smell to me - why isn't the default value for the parameter the actual default value you want? (There are probably good reasons, but it might mean there's a better way to architect what you're up to)

How to get the string in Kotlin to readable format?

I don't know how to get the string in readable format in my app. My code is:
val allergyList = recipeItem.allergies
allergyList.joinToString()
var allergyString: String = ""
for (allergy in allergyList) {
allergyList[1]
allergyString += " ${allergy}"
println(allergy.toString())
}
holder.recipeSearchPageAllergies.text = allergyString
When I print this I get the allergy string memory space?
Result for each one is something like this:
Allergy#4e8f238
How do I 'decode' it into something readable for a human? It should say 'nut allergy'.
you have some options. If you have the control over the Allergy source code,
you could override toString method:
class Allergy(val name: String) {
override fun toString(): String = "Allergy[name=${name}]"
}
println(Allergy("flowers"))
// Allergy[name=flowers]
also, you can make a data class of it. Data class has sane toString by default. It also has a few nice perks, like by default equals/hashCode generation, and deconstruction to components, allowing you to use it in destructing:
data class Allergy(val name: String)
println(Allergy("peanuts"))
// Allergy(name=peanuts)
otherwise, if you can't modify the source of the Allregy, you can make up an extension method for that:
class Allergy(val name: String)
fun Allergy.readable() = "Allergy[name=${name}]"
println(Allergy("cats").readable())
// Allergy[name=cats]
in your case, you could also make an extension method for collections of allergies to have the format you need:
fun Collection<Allergy>.readable() = joinToString { "Allergy[name=${it.name}]" }
println(
listOf(Allergy("cats"), Allergy("peanuts"), Allergy("flowers")).readable()
)
// Allergy[name=cats], Allergy[name=peanuts], Allergy[name=flowers]
// in your case:
holder.recipeSearchPageAllergies.text = recipeItem.allergies.readable()
// or just
holder.recipeSearchPageAllergies.text = recipeItem.allergies.joinToString { "Allergy[name=${it.name}]" }
You can make it simplier:
val allergiesStr = recipeItem.allergies.map { allergy ->
// your allergy name from allergy variable
}.joinToString(separator = " ")

Why filtering out null map keys doesn't change type to not nullable in kotlin?

I have a list of objects with an optional id as String and I want to make a map out of it.
I want to have the keys of my map as non nullable: so something like this:
data class Foo(
val id: String? = null
val someStuff: String? = null,
)
val foo = listOf(Foo("id1"), Foo())
val bar = foo.filterNot { it.id == null }.associateBy { it.id }
Here bar type is Map<String?, Foo> but not Map<String, Foo>
My workaround is to add a non null asserted call: !!, but it doesn't seem clean.
Is there an easy and safe way to do this?
This looks like something that contracts could help with, but currently a contract expression can't access properties of the class in use.
As a workaround, you could define a 2nd class that has a non-null id, like so
data class Foo(
val id: String? = null,
val someStuff: String? = null
)
data class Foo2(
val id: String,
val someStuff: String? = null
)
val foo = listOf(Foo("id1"), Foo())
val bar = foo
.mapNotNull { if (it.id != null) Foo2(it.id, it.someStuff) else null }
.associateBy { it.id }
There's a six-year-old open feature request for Map.filterNotNullKeys() and a four-year old open feature request for Map.associateByNotNull().
In my opinion, the associateBy { it.id!! } would be cleanest for readability. But you could do it like this:
val bar = foo.mapNotNull { it.id?.run { it.id to it } }.toMap()
As for your actual question, that logic is way too many steps for the compiler to infer. Your last function call to associateBy sees a nullable, so it infers a nullable. For the compiler to figure this out, it would have to step back and see that the List that you call associateBy on happens to have filtered out certain objects in a way that happens to ensure that a certain nullable property won't be null within this specific list, and it's the same property that you are associating with. Now imagine it has to do this for every call to any generic function, and the various lambdas involved could potentially have multiple lines of code. Compile times would skyrocket.

Kotlin data classes JSON Deserialization

I am trying convert ApiEmployee to Employee and have written a test around it. I am confused about nulls in Kotlin as I am new to it.
ApiEmployee would be used for JSON conversion so it can have missing name field or or empty or can come as null. In that case, I don't want to add into list and safely ignore it.
I am getting Method threw 'kotlin.KotlinNullPointerException at exception. at apiEmployee.name!!.isNotBlank()
ApiEmployee
data class ApiEmployee(val image: String? = "image",
val name: String? = "name test",
val description: String? = "",
val id: String? = "")
Employee
data class Employee(val imagePath: String, val id: String)
EmployeeConverter(converts ApiEmployee to Employee)
fun apply(apiEmployees: List<ApiEmployee>): List<Employee> {
val employees = mutableListOf<Employee>()
for (apiEmployee in apiEmployees) {
if (apiEmployee.name!!.isNotBlank()){
employees.add(Employee(apiEmployee.image!!, apiEmployee.id!!)
}
}
}
EmployeeConverterTest
#Test
fun `should not add employee without name into employee list`() {
val invalidApiEmployee = ApiEmployee("image", null, "description", "id")
val convertedEmployees : List< Employee > = employeeConverter.apply(listOf( invalidApiEmployee))
assertThat(convertedEmployees.size).isEqualTo(0)
}
What you want to do is check if the name is null first and then if it is empty.
val employeeNameIsNotEmpty = apiEmployee.name?.isNotBlank() ?: false
if (employeeNameIsNotEmpty) {
// do stuff
}
The apiEmployee.name?.isNotBlank() will run and return a value only if name is not null. If name is null then the statment on the right side of ?: will return its value, which in this case should be false.
In this case however Kotlin has already put this particular example into an extension function
.isNullOrBlank()
So you could change it to:
if (!apiEmployee.name.isNullOrBlank()) {
// do stuff
}
As a side note you really don't whant to do this Employee(apiEmployee.image!!, apiEmployee.id!!).
Because image and id could still be null and crash your code with the same error.
Either pass the value for name.
ApiEmployee("image", "name", "description", "id")
(or)
Change the if condition as mentioned below (with ? operator):-
if (apiEmployee.name?.isNotBlank()){
?. performs a safe call (calls a method or accesses a property if the
receiver is non-null)
!! asserts that an expression is
non-null
The code asserts that name is not null and checking for not blank.
Probably, I think you are trying to do null and not blank check. You can use ? operator (safe call) for that. This means isNotBlank() gets executed only if the name is not null.

How to use jooq to update table when column names and it's values determined at runtime

I have list of table column names and it's values which will be determined # run time. Right now I am using following way to achieve the feet which requires casting Filed to TableField for every single column name. Is there any better way ?
override fun updateFields(job: Job, jsonObject: JsonObject, handler: Handler<AsyncResult<Job?>>): JobQService {
val updateFieldsDsl = dslContext.update(JOB)
var feildSetDsl: UpdateSetMoreStep<*>? = null
jsonObject.map.keys.forEach { column ->
feildSetDsl = if (feildSetDsl == null) {
updateFieldsDsl.set(JOB.field(column) as TableField<Record, Any>, jsonObject.getValue(column))
} else {
feildSetDsl!!.set(JOB.field(column) as TableField<Record, Any>, jsonObject.getValue(column))
}
}
val queryDsl = feildSetDsl!!.where(JOB.ID.eq(job.id))
jdbcClient.rxUpdateWithParams(queryDsl.sql, JsonArray(queryDsl.bindValues)).subscribeBy(
onSuccess = { handler.handle(Future.succeededFuture(job)) },
onError = { handler.handle(Future.failedFuture(it)) }
)
return this;
}
I'm not sure what you mean by "better" but there is a method UpdateSetStep.set(Map), which seems to be helpful for what you're trying to do. See the javadoc:
UpdateSetMoreStep set(Map<?,?> map)
Set a value for a field in the UPDATE statement.
Keys can either be of type String, Name, or Field.
Values can either be of type <T> or Field<T>. jOOQ will attempt to convert values to their corresponding field's type.