Let's take the class of a data class:
data class User(
val userNumber: Int = -1,
val name: String,
val userGroups; List<String> = emptyList(),
val screenName: String = "new-user"
)
When calling this function from Kotlin, it is pretty straightforward. I can simply use the named-argument syntax to do so. Calling from Java, I have to specify all values, or use the #JvmOverloads annotation, which generates the following constructors (in addition to the constructor that kotlin generates with the bit-mask for default values):
User(int userNumber, #NotNull String name, #NotNull List userGroups,
#NotNull String screenName)
User(int userNumber, #NotNull String name, #NotNull List userGroups)
User(int userNumber, #NotNull String name)
User(#NotNull String name)
Now, if I want to create a User object in Java equivalent to User(name="John Doe", userGroups=listOf("admin", "super") I can't do it with the above constructors. I CAN however do it if I put val userNumber: Int = -1 at the end in the data class declaration (the generation of constructors seems to depend on the order the optional arguments are defined in). Which is fine, because expecting kotlin to generate all permutations is going to heavily bloat some classes.
The biggest problem that tools like Jackson simply don't work as they have no idea which constructor to use (and not like I can annotate one of the generated ones specially).
So, is there a way to generate a (single) constructor like:
User(Integer userNumber, String name, List<String> userGroups, String screenName) {
this.userNumber = (userNumber == null) ? -1 : userNumber;
this.userGroups = (userGroups == null) ? Collections.emptyList() : userGroups;
//...
}
Currently I am using the above approach, but manually defining the constructors where I need them.
EDIT
I should clarify, creating a similar constructor doesn't work, obviously because both the signatures would clash on the JVM. This is what it would like in my case:
data class User(
val userNumber: Int = -1,
val name: String,
val userGroups; List<String> = emptyList(),
val screenName: String = "new-user"
) {
companion object {
#JvmStatic
#JsonCreator
fun constructionSupport(
#JsonProperty("userNumber") userNumber : Int?,
#JsonProperty("name") name : String,
#JsonProperty("userGroups") userGroups : List<String>?,
#JsonProperty("screenName") screenName : String?
) = User(
userNumber = userNumber ?: -1,
name = name,
userGroups = userGroups ?: emptyList(),
screenName = screenName ?: "new-user"
)
}
}
Also note the redundancy where I have to write the default values for the properties twice. I Now that I look at it, I doubt there exists a solution for this. Maybe this is a good use-case for a kapt based side-project of mine :)
Better solution is to add possibility to library understand Kotlin functional. For example, for Jackson exists jackson-module-kotlin. With this library we can use default arguments in data classes.
Example:
data class User(
val userNumber: Int = -1,
val name: String,
val userGroups: List<String> = emptyList(),
val screenName: String = "new-user"
)
fun main(args: Array<String>) {
val objectMapper = ObjectMapper()
.registerModule(KotlinModule())
val testUser = User(userNumber = 5, name = "someName")
val stringUser = objectMapper.writeValueAsString(testUser)
println(stringUser)
val parsedUser = objectMapper.readValue<User>(stringUser)
println(parsedUser)
assert(testUser == parsedUser) {
println("something goes wrong")
}
}
After kicking this around for a minute, I think I found a solution that may work well here. Simply define a top level function in the same source file, that will build the object. Perhaps like so:
fun build_user(userNumber: Int?, name: String, userGroups: List<String>?, screenName: String?) : User {
return User(if(userNumber !== null) userNumber else -1, name, if(userGroups !== null) userGroups else emptyList(),
if(screenName !== null) screenName else "new-user")
}
Then when you need it, you simply call it from Java:
User user = UserKt.build_user(null, "Hello", null, "Porterhouse Steak");
System.out.println(user);
Output from the example:
User(userNumber=-1, name=Hello, userGroups=[], screenName=Porterhouse Steak)
The method is somewhere between a constructor and a builder. It beats hammering out a full-blown Builder object, and avoids cluttering your data class with unnecessary Java-interop glue code messiness.
See Package Level Functions for more information.
Related
I have the following class and query. I want to use multiset to map the result of the images into Map<String, String>(Key: OrderNumber / Value: FileKey), but I don't know how to do it. Could you help me how to map the multiset result into hashmap?
data class User(
val id: UUID,
val name: String,
val images: Map<String, String>?
)
#Repository
#Transactional(readOnly = true)
class FetchUserRepository(private val ctx: DSLContext) {
private val user = JUser.USER
private val userImage = JUserImage.USER_IMAGE
override fun fetch(): List<User> {
return ctx.select(
user.ID,
user.NAME,
multiset(
select(userImage.ORDER_NUMBER.cast(String::class.java), userImage.FILE_KEY)
.from(userImage)
.where(userImage.USER_ID.eq(user.ID))
).convertFrom { r -> r.map(mapping(???)) } // I'm not sure how to map the result to hashmap
)
.from(user)
.fetchInto(User::class.java)
}
jOOQ 3.16 solution
The type of your multiset() expression is Result<Record2<String, String>>, so you can use the Result.intoMap(Field, Field) method, or even Result.collect(Collector) using the Records.intoMap() collector, which allows for avoiding the repetition of field names:
{ r -> r.collect(Records.intoMap()) }
I've explained this more in detail in a blog post, here.
jOOQ 3.17 solution
In fact, this seems so useful and powerful, let's add some convenience on top of the existing API using some extensions (located in the jOOQ-kotlin extensions module):
// New extension functions, e.g.
fun <R : Record, E> Field<Result<R>>.collecting(collector: Collector<R, *, E>)
= convertFrom { it.collect(collector) }
fun <K, V> Field<Result<Record2<K, V>>>.intoMap(): Field<Map<K, V>>
= collecting(Records.intoMap())
// And then, you can write:
multiset(...).intoMap()
The feature request is here: https://github.com/jOOQ/jOOQ/issues/13538
In addition to Lukas's answer, I would like to provide an alternative option with jsonObject & jsonObjectAgg.
The result of this query would be returned as JSON format, and it can be easily projected to the target class via Jackson or whatever. (It is really powerful feature when it comes to nested collection within the target class)
I believe it is the one of the coolest features of jOOQ as MULTISET :)
data class User(
val id: UUID,
val name: String,
val images: Map<String, String>?
)
#Repository
#Transactional(readOnly = true)
class FetchUserRepository(private val ctx: DSLContext) {
private val user = JUser.USER
private val userImage = JUserImage.USER_IMAGE
override fun fetch(): List<User> {
return ctx.select(
jsonObject(
key("id").value(user.ID),
key("name").value(user.NAME),
key("images").value(
field(
select(
jsonObjectAgg(
userImage.ORDER_NUMBER.cast(String::class.java),
userImage.FILE_KEY
)
)
.from(userImage)
.where(userImage.USER_ID.eq(user.ID))
)
)
)
)
.from(user)
.fetchInto(User::class.java)
}
}
My use case:
I have a large number of POJO models that are different types of requests for a third-party API. All of them have several common fields and a couple unique ones.
I was hoping to build something that conceptually looks like this
class RequestBase(
val commonField1: String,
val commonField2: String,
...
val commonFieldX: String
)
class RequestA(
val uniqueFieldA: String
): RequestBase()
class RequestB(
val uniqueFieldB: String
): RequestBase()
fun main() {
val requestA = RequestA(
commonField1 = "1",
commonField2 = "2",
...
uniqueFieldA = "A"
)
}
I can of course override the common fields in every child request and then pass them to the parent constructor, but this ends up producing a lot of boilerplate code and bloats the model. Are there any options I can explore here?
Notice that what you are doing in the parentheses that follow a class declaration is not "declaring what properties this class has", but "declaring the parameters of this class' primary constructor". The former is just something you can do "along the way", by adding var or val.
Each class can have its own primary constructor that take any number and types of parameters that it likes, regardless of what class its superclass is. Therefore, it is not unreasonable to have to specify all the parameters of the constructor:
open class RequestBase(
val commonField1: String,
val commonField2: String,
...
val commonFieldX: String
)
class RequestA(
// notice that the parameters for the inherited properties don't have the
// "val" prefix, because you are not declaring them in the subclass again.
// These are just constructor parameters.
commonField1: String,
commonField2: String,
...
commonFieldX: String,
val uniqueFieldA: String,
): RequestBase(
commonField1,
commonField2,
...
commonFieldX,
)
If you find this unpleasant, there are a bunch of ways to work around this.
One way is to use composition and delegation - create an interface having the common properties. The specific requests' primary constructors will take a RequestBase and their unique properties, and implement the interface by delegating to the RequestBase:
interface Request {
val commonField1: String
val commonField2: String
val commonFieldX: String
}
open class RequestBase(
override val commonField1: String,
override val commonField2: String,
override val commonFieldX: String
): Request
class RequestA(
val requestBase: RequestBase,
val uniqueField: String
): Request by requestBase
This allows you to access someRequestA.commonFieldX directly, without doing someRequestA.requestBase.commonFieldX, but to create a RequestA, you need to create a RequestBase first:
RequestA(
RequestBase(...),
uniqueField = ...
)
Another way is to change your properties to vars, give them default values, and move them out of the constructor parameters:
open class RequestBase {
var commonField1: String = ""
var commonField2: String = ""
var commonFieldX: String = ""
}
class RequestA: RequestBase() {
var uniqueField: String = ""
}
Then to create an instance of RequestA, you would just call its parameterless constructor, and do an apply { ... } block:
RequestA().apply {
commonField1 = "foo"
commonField2 = "bar"
commonFieldX = "baz"
uniqueField = "boo"
}
The downside of this is of course that the properties are all mutable, and you have to think of a default value for every property. You might have to change some properties to nullable because of this, which might not be desirable.
You can't do it with constructors of base class. Without constructors it's possible:
open class RequestBase {
lateinit var commonField1: String
lateinit var commonField2: String
...
lateinit var commonFieldX: String
}
class RequestA(
val uniqueFieldA: String
): RequestBase()
class RequestB(
val uniqueFieldB: String
): RequestBase()
fun main() {
val requestA = RequestA(
uniqueFieldA = "A"
).apply {
commonField1 = "1"
commonField2 = "2"
...
commonFieldX = "X"
}
}
How can I create an instance of InfoA that contains also title. Do I need to modify the classes?
Can't specify the title.
Also, do I need to create setters for it? To not access with the _
val info = InfoA(_subtitle = "SUBTITLE", title = ...)
open class Info(
open val action: Action = Action(),
open val title: String? = ""
) {
fun hasAction(): Boolean = action.hasAction()
}
class InfoA(
private val _subtitle: String? = "",
private val _image: String? = "",
private val _backgroundImage: String? = "",
private val _backgroundColor: String? = null,
private val _foregroundColor: String? = null,
private val _borderColor: String? = null
) : Info() {
val subtitle: String
get() = _subtitle.orEmpty()
val image: String
get() = _image.orEmpty()
val backgroundImage: String
get() = _backgroundImage.orEmpty()
val backgroundColor: Int?
get() = if (_backgroundColor != null) convertRgbStringToColorInt(_backgroundColor) else null
val foregroundColor: Int?
get() = if (_foregroundColor != null) convertRgbStringToColorInt(_foregroundColor) else null
val borderColor: Int?
get() = if (_borderColor != null) convertRgbStringToColorInt(_borderColor) else null
}
As the code is written, title is a val, so it can't be changed from its initial value — which is empty string if (as in the case of InfoA) something calls its constructor without specifying another value.
If it were changed to be a var, then it could be changed later, e.g.:
val info = InfoA(_subtitle = "SUBTITLE").apply{ title = "..." }
Alternatively, if you want to keep it a val, then InfoA would need to be changed: the most obvious way would be to add a title parameter in its constructor, and pass that up to Info:
class InfoA(
title: String? = "",
// …other fields…
) : Info(title = title) {
Note that this way, InfoA can never use Info's default value for title, so you may need to duplicate that default in InfoA's constructor.
The need to duplicate superclass properties in a subclass constructor is awkward, but there's currently no good way around it. (See e.g. this question.) If there are many parameters, you might consider bundling them together into a single data class, which could then be passed easily up to the superclass constructor — but of course users of the class would need to specify that. (Some people think that having more than a few parameters is a code smell, and that bundling them together can often improve the design.)
Updated: added some clarifications from the comments
I would like to use the same 'mapping' code for the primary constructor and copy() method of an immutable data class. How can I do this without creating an empty object first, and then using copy() on it?
The issue with how it is now is that if I add a new attribute with default value to Employee and EmployeeForm it would be easy to only add it in one of the two mapping functions and forget about the other (toEmployeeNotReusable / copyEmployee).
These are the data classes I'd like to map between:
#Entity
data class Employee(
val firstName: String,
val lastName: String,
val jobType: Int,
#OneToMany(mappedBy = "employee", cascade = [CascadeType.ALL], fetch = FetchType.EAGER)
private val _absences: MutableSet<Absence> = mutableSetOf(),
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0 // prevents #Joffrey's answer from working
) {
init {
_absences.forEach { it.employee = this }
}
val absences get() = _absences.toSet()
fun addAbsence(newAbsence: Absence) {
newAbsence.employee = this
_absences += newAbsence
}
#Entity
#Table(name = "absence")
data class Absence(
// ... omitted fields
) {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "employee_id")
lateinit var employee: Employee
}
}
data class EmployeeForm(
var firstName: String = "",
var lastName: String = "",
var jobType: Int = 0
) {
// not reusable
fun toEmployeeNotReusable(): Employee {
return Employee(firstName, lastName, jobType)
}
// works but hacky
fun toEmployee(): Employee {
return copyEmployee(Employee("", "", 0))
}
fun copyEmployee(employee: Employee): Employee {
return employee.copy(
firstName = firstName,
lastName = lastName,
jobType = jobType
)
}
}
While mutability would be fine, in my case, I'd be interested to know how this would be possible.
One way to avoid listing the attributes 4 times would be to declare Employee as an interface instead, and use the "mutable" version, the form, as the only data class implementing it. You would have the "read-only" view using the interface, but you would technically only use the mutable instance behind the scenes.
This would follow what Kotlin designers have done for List vs MutableList.
interface Employee {
val firstName: String
val lastName: String
val jobType: Int
}
data class EmployeeForm(
override var firstName: String = "",
override var lastName: String = "",
override var jobType: Int = 0
): Employee {
fun toEmployee(): Employee = this.copy()
fun copyEmployee(employee: Employee): Employee = this.copy(
firstName = firstName,
lastName = lastName,
jobType = jobType
)
}
However, this implies that the form has all fields of an employee, which you probably don't want.
Also, I would personally prefer what you had done in the beginning, listing twice the field would not be a problem, just write tests for your functions, and when you want to add functionality, you'll add tests for that functionality anyway.
You should be able to do this using reflection: check list of properties in Employee and EmployeeForm, call the constructor by the matching names (using callBy to handle default parameters). The drawback, of course, is that you won't get compile-time errors if any properties are missing (but for this case, any test would probably fail and tell you about the problem).
Approximate and untested (don't forget to add the kotlin-reflect dependency):
inline fun <reified T> copy(x: Any): T {
val construct = T::class.primaryConstructor
val props = x::class.memberProperties.associate {
// assumes all properties on x are valid params for the constructor
Pair(construct.findParameterByName(it.name)!!,
it.call(x))
}
return construct.callBy(props)
}
// in EmployeeForm
fun toEmployee() = copy<Employee>(this)
You can make an equivalent which is compile-time checked with Scala macros, but I don't think it's possible in Kotlin.
I've already deserialized some nested field in the past in Java, following instructions from https://www.baeldung.com/jackson-nested-values (section 5) :
#JsonProperty("brand")
private void unpackNested(Map<String,Object> brand) {
this.brandName = (String)brand.get("name");
Map<String,String> owner = (Map<String,String>)brand.get("owner");
this.ownerName = owner.get("name");
}
ownerName being a field in the bean.
Now, I need to do something similar in Kotlin, but I am not happy with what I have so far. Assuming I have a MyPojo class that has a createdAt field, but in the JSON that represents it, the field is nested under a metadata attribute:
data class MyPojo(var createdAt: LocalDateTime = LocalDateTime.MIN) {
#JsonProperty("metadata")
private fun unpackNested(metadata: Map<String, Any>) {
var createdAtAsString = metadata["createdAt"] as String
this.createdAt = LocalDateTime.parse(createdAtAsString,DateTimeFormatter.ISO_DATE_TIME)
}
}
One of the thing I don't like here is that I am forced to make createdAt a var, not a val.
Is there a Kotlin trick to make things overall better here?
For the sake of simplicity, I used Int as type for createdAt.
You could do it like this:
class JsonData(createdAt: Int = 0) {
private var _createdAt: Int = createdAt
val createdAt: Int
get() = _createdAt
#JsonProperty("metadata")
private fun unpackNested(metadata: Map<String, Any>) {
_createdAt = metadata["createdAt"] as Int
}
}
createdAt will be a parameter with a default value. Since a data classe's constructor can only have properties (var/val) you will loose the advantages of a data class (toString() out of the box etc.).
You will assign this parameter to a private var _createdAt when the class is instantiated.
The only thing that will be exposed to the outside is a property without a backing field createAt (just a getter in Java terms). So, _createdAt cannot be changed after instantiation.
There are two cases now:
If you instantiate the class, _createdAt will be set to the value you specify.
If Jackson instantiates the class the value of _createdAt will be overwritten by the unpackNested call.
Here is an example:
val jsonStr = """{
"metadata": {
"createdAt": 1
}
}
""".trimIndent()
fun main() {
val objectMapper = ObjectMapper()
// Jackson does instantiation
val jsonData = objectMapper.readValue(jsonStr, JsonData::class.java)
// you do it directly
JsonData(5)
}