Is there a way to generate kotlin dsl using data - kotlin

We are using kotlin dsl to as a user friendly builder to take input and generate data. Is there a way to do the opposite of that ?
ie, convert existing data into dsl ?
Can this kotlin representation be converted to dsl ?
val person = Person("John", 25)
val person = person {
name = "John"
age = 25
}

Unless you're really crazy about { and some commas, below is an absolutely valid Kotlin code:
data class Person(
val name: String,
val age: Int
)
val person = Person(
name = "John",
age = 25
)
I seems really close to what you want and comes out-of-the-box.
Of course, you can achieve the syntax you want by writing some extra code, like:
import kotlin.properties.Delegates
data class Person(
val name: String,
val age: Int
)
class PersonDSL{
lateinit var name: String
var age: Int by Delegates.notNull<Int>()
fun toPerson(): Person = Person(this.name, this.age)
}
fun person(config: PersonDSL.() -> Unit): Person{
val dsl = PersonDSL()
dsl.config()
return dsl.toPerson()
}
fun main(){
val person = person {
name = "John"
age = 25
}
println(person) // Person(name=John, age=25)
}
But why do that?

Related

kotlin data class constructors not getting picked up

I am creating a data class in kotlin as such
data class User(val name: String, val age: Int)
{
constructor(name: String, age: Int, size: String): this(name, age) {
}
}
In my main function, I can access the objects as such:
fun main(){
val x = User("foo", 5, "M")
println(x.name)
println(x.age)
println(x.size) // does not work
}
My problem is that I can't get access to size.
What I am trying to do is, create a data class where top level params are the common items that will be accessed, and in the constructors, have additional params that fit certain situations. The purpose is so that I can do something like
// something along the lines of
if (!haveSize()){
val person = User("foo", 5, "M")
} else {
val person = User("foo", 5)
}
}
Any ideas?
In Kotlin you do not need separate constructors for defining optional constructor params. You can define them all in a single constructor with default values or make them nullable, like this:
data class User(val name: String, val age: Int, val size: String = "M")
fun main(){
val x = User("foo", 5, "L")
val y = User("foo", 5)
println(x.size) // "L" from call site
println(y.size) // "M" from default param
}
You can not access size variable, because this is from secondary construct, but we have alternative variant.
data class User(var name: String, var age: Int) {
var size: String
init {
size = "size"
}
constructor(name: String, age: Int, size: String) : this(name, age) {
this.size = size
}
}
In short, you want to have one property that can be one of a limited number of options. This could be solved using generics, or sealed inheritance.
Generics
Here I've added an interface, MountDetails, with a generic parameter, T. There's a single property, val c, which is of type T.
data class User(
val mountOptions: MountOptions,
val mountDetails: MountDetails<*>,
)
data class MountOptions(
val a: String,
val b: String
)
interface MountDetails<T : Any> {
val c: T
}
data class MountOneDetails(override val c: Int) : MountDetails<Int>
data class MountTwoDetails(override val c: String) : MountDetails<String>
Because the implementations MountDetails (MountOneDetails and MountTwoDetails) specify the type of T to be Int or String, val c can always be accessed.
fun anotherCaller(user: User) {
println(user.mountOptions.a)
println(user.mountOptions.b)
println(user.mountDetails)
}
fun main() {
val mt = MountOptions("foo", "bar")
val mountOneDetails = MountOneDetails(111)
anotherCaller(User(mt, mountOneDetails))
val mountTwoDetails = MountTwoDetails("mount two")
anotherCaller(User(mt, mountTwoDetails))
}
Output:
foo
bar
MountOneDetails(c=111)
foo
bar
MountTwoDetails(c=mount two)
Generics have downsides though. If there are lots of generic parameters it's messy, and it can be difficult at runtime to determine the type of classes thanks to type-erasure.
Sealed inheritance
Since you only have a limited number of mount details, a much neater solution is sealed classes and interfaces.
data class User(val mountOptions: MountOptions)
sealed interface MountOptions {
val a: String
val b: String
}
data class MountOneOptions(
override val a: String,
override val b: String,
val integerData: Int,
) : MountOptions
data class MountTwoOptions(
override val a: String,
override val b: String,
val stringData: String,
) : MountOptions
The benefit here is that there's fewer classes, and the typings are more specific. It's also easy to add or remove an additional mount details, and any exhaustive when statements will cause a compiler error.
fun anotherCaller(user: User) {
println(user.mountOptions.a)
println(user.mountOptions.b)
// use an exhaustive when to determine the actual type
when (user.mountOptions) {
is MountOneOptions -> println(user.mountOptions.integerData)
is MountTwoOptions -> println(user.mountOptions.stringData)
// no need for an 'else' branch
}
}
fun main() {
val mountOne = MountOneOptions("foo", "bar", 111)
anotherCaller(User(mountOne))
val mountTwo = MountTwoOptions("foo", "bar", "mount two")
anotherCaller(User(mountTwo))
}
Output:
foo
bar
111
foo
bar
mount two
This is really the "default values" answer provided by Hubert Grzeskowiak adjusted to your example:
data class OneDetails(val c: Int)
data class TwoDetails(val c: String)
data class MountOptions(val a: String, val b: String)
data class User(
val mountOptions: MountOptions,
val detailsOne: OneDetails? = null,
val detailsTwo: TwoDetails? = null
)
fun main() {
fun anotherCaller(user: User) = println(user)
val mt = MountOptions("foo", "bar")
val one = OneDetails(1)
val two = TwoDetails("2")
val switch = "0"
when (switch) {
"0" -> anotherCaller(User(mt))
"1" -> anotherCaller(User(mt, detailsOne = one))
"2" -> anotherCaller(User(mt, detailsTwo = two))
"12" -> anotherCaller(User(mt, detailsOne = one, detailsTwo = two))
else -> throw IllegalArgumentException(switch)
}
}

Why class member property reflection in Kotlin?

In 'Kotlin in Action', it says "if a memberProperty refers to the age property of the Person class, memberProperty.get(person) is a way to dynamically get the value of person.age" with code(10.2.1 The Kotlin Reflection API):
class Peron(val name: String, val age: Int)
>> val person = Person("Alice", 29)
>> val memberProperty = Person::age
>> println(memberProperty.get(person))
29
I can't understand why this example refers to "dynamically" getting the value of property. It just works when I run this code:
println(person.age)
Is there any other case or example of member property reflection?
For example, say you want to write a function which prints all the properties of an object along with their values, this is how you can do that:
inline fun <reified T: Any> T.printProperties() {
T::class.memberProperties.forEach { property ->
println("${property.name} = ${property.get(this)}") // You can't use `this.property` here
}
}
Usage:
data class Person(val firstName: String, val lastName: String)
data class Student(val graduate: Boolean, val rollNumber: Int)
fun main() {
val person = Person("Johnny", "Depp")
val student = Student(false, 12345)
person.printProperties()
student.printProperties()
}
Output:
firstName = Johnny
lastName = Depp
graduate = false
rollNumber = 12345

Re-use mapping code for immutable data class in Kotlin

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.

Kotlin - Overwrite Obj Props With Modified Obj Props if Not Null

TL;DR:
How do I make this less redundant (any approach that works helps)?
if (personModification.firstName != null) {person.firstName = personModification.firstName}
if (personModification.lastName != null) {person.lastName = personModification.lastName}
if (personModification.job != null) {person.job = personModification.job}
The long version: I have a simple problem. I have a class Person:
class Person (val firstName: String?,
val lastName: String?,
val job: String?)
and I have a class called PersonModification:
class PersonModification(val firstName: String?,
val lastName: String?,
val job: String?)
The task is to overwrite any Person property values with PersonModification values, IF the PersonModification property isn't null. If you care, the business logic behind this is an API endpoint which modifies Person and takes a PersonModification as an argument (but can change all, or any, of the properties, so we don't want to overwrite valid old values with nulls). The solution to this looks like this.
if (personModification.firstName != null) {person.firstName = personModification.firstName}
if (personModification.lastName != null) {person.lastName = personModification.lastName}
if (personModification.job != null) {person.job = personModification.job}
I was told this is redundant (and I agree). The solution pseudocode looks like this:
foreach(propName in personProps){
if (personModification["propName"] != null) {person["propName"] = personModification["propName"]}
}
Of course, this isn't JavaScript, so it's not that easy. My reflection solution is below, but imo, it's better to have redundancy than do reflection here. What are my other options to remove the redundancy?
Refelection:
package kotlin.reflect;
class Person (val firstName: String?,
val lastName: String?,
val job: String?)
class PersonModification(val firstName: String?,
val lastName: String?,
val job: String?)
// Reflection - a bad solution. Impossible without it.
//https://stackoverflow.com/questions/35525122/kotlin-data-class-how-to-read-the-value-of-property-if-i-dont-know-its-name-at
inline fun <reified T : Any> Any.getThroughReflection(propertyName: String): T? {
val getterName = "get" + propertyName.capitalize()
return try {
javaClass.getMethod(getterName).invoke(this) as? T
} catch (e: NoSuchMethodException) {
null
}
}
fun main(args: Array<String>) {
var person: Person = Person("Bob","Dylan","Artist")
val personModification: PersonModification = PersonModification("Jane","Smith","Placeholder")
val personClassPropertyNames = listOf("firstName", "lastName", "job")
for(properyName in personClassPropertyNames) {
println(properyName)
val currentValue = person.getThroughReflection<String>(properyName)
val modifiedValue = personModification.getThroughReflection<String>(properyName)
println(currentValue)
if(modifiedValue != null){
//Some packages or imports are missing for "output" and "it"
val property = outputs::class.memberProperties.find { it.name == "firstName" }
if (property is KMutableProperty<*>) {
property.setter.call(person, "123")
}
}
})
}
You can copy and paste here to run it: https://try.kotlinlang.org/
It should be pretty simple to write a 5 line helper to do this which even supports copying every matching property or just a selection of properties.
Although it's probably not useful if you're writing Kotlin code and heavily utilising data classes and val (immutable properties). Check it out:
fun <T : Any, R : Any> T.copyPropsFrom(fromObject: R, skipNulls: Boolean = true, vararg props: KProperty<*>) {
// only consider mutable properties
val mutableProps = this::class.memberProperties.filterIsInstance<KMutableProperty<*>>()
// if source list is provided use that otherwise use all available properties
val sourceProps = if (props.isEmpty()) fromObject::class.memberProperties else props.toList()
// copy all matching
mutableProps.forEach { targetProp ->
sourceProps.find {
// make sure properties have same name and compatible types
it.name == targetProp.name && targetProp.returnType.isSupertypeOf(it.returnType)
}?.let { matchingProp ->
val copyValue = matchingProp.getter.call(fromObject);
if (!skipNulls || (skipNulls && copyValue != null)) {
targetProp.setter.call(this, copyValue)
}
}
}
}
This approach uses reflection, but it uses Kotlin reflection which is very lightweight. I haven't timed anything, but it should run almost at same speed as copying properties by hand.
Also it uses KProperty instead of strings to define a subset of properties (if you don't want all of them copied) so it has complete refactoring support, so if you rename a property on the class you won't have to hunt for string references to rename.
It will skip nulls by default or you can toggle the skipNulls parameters to false (default is true).
Now given 2 classes:
data class DataOne(val propA: String, val propB: String)
data class DataTwo(var propA: String = "", var propB: String = "")
You can do the following:
var data2 = DataTwo()
var data1 = DataOne("a", "b")
println("Before")
println(data1)
println(data2)
// this copies all matching properties
data2.copyPropsFrom(data1)
println("After")
println(data1)
println(data2)
data2 = DataTwo()
data1 = DataOne("a", "b")
println("Before")
println(data1)
println(data2)
// this copies only matching properties from the provided list
// with complete refactoring and completion support
data2.copyPropsFrom(data1, DataOne::propA)
println("After")
println(data1)
println(data2)
Output will be:
Before
DataOne(propA=a, propB=b)
DataTwo(propA=, propB=)
After
DataOne(propA=a, propB=b)
DataTwo(propA=a, propB=b)
Before
DataOne(propA=a, propB=b)
DataTwo(propA=, propB=)
After
DataOne(propA=a, propB=b)
DataTwo(propA=a, propB=)
This can be solved without reflection using delegated properties. See: https://kotlinlang.org/docs/reference/delegated-properties.html
class Person(firstName: String?,
lastName: String?,
job: String?) {
val map = mutableMapOf<String, Any?>()
var firstName: String? by map
var lastName: String? by map
var job: String? by map
init {
this.firstName = firstName
this.lastName = lastName
this.job = job
}
}
class PersonModification(firstName: String?,
lastName: String?,
job: String?) {
val map = mutableMapOf<String, Any?>()
var firstName: String? by map
var lastName: String? by map
var job: String? by map
init {
this.firstName = firstName
this.lastName = lastName
this.job = job
}
}
fun main(args: Array<String>) {
val person = Person("Bob", "Dylan", "Artist")
val personModification1 = PersonModification("Jane", "Smith", "Placeholder")
val personModification2 = PersonModification(null, "Mueller", null)
println("Person: firstName: ${person.firstName}, lastName: ${person.lastName}, job: ${person.job}")
personModification1.map.entries.forEach { entry -> if (entry.value != null) person.map[entry.key] = entry.value }
println("Person: firstName: ${person.firstName}, lastName: ${person.lastName}, job: ${person.job}")
personModification2.map.entries.forEach { entry -> if (entry.value != null) person.map[entry.key] = entry.value }
println("Person: firstName: ${person.firstName}, lastName: ${person.lastName}, job: ${person.job}")
}
You can create a nice trait for this which you will be able to apply for any modification class you might have:
interface Updatable<T : Any> {
fun updateFrom(model: T) {
model::class.java.declaredFields.forEach { modelField ->
this::class.java.declaredFields
.filter { it.name == modelField.name && it.type == modelField.type }
.forEach { field ->
field.isAccessible = true
modelField.isAccessible = true
modelField.get(model)?.let { value ->
field.set(this, value)
}
}
}
}
}
Usage:
data class Person(val firstName: String?,
val lastName: String?,
val job: String?) : Updatable<PersonModification>
data class PersonModification(val firstName: String?,
val lastName: String?,
val job: String?)
Then you can try it out:
fun main(args: Array<String>) {
val person = Person(null, null, null)
val mod0 = PersonModification("John", null, null)
val mod1 = PersonModification(null, "Doe", null)
val mod2 = PersonModification(null, null, "Unemployed")
person.updateFrom(mod0)
println(person)
person.updateFrom(mod1)
println(person)
person.updateFrom(mod2)
println(person)
}
This will print:
Person(firstName=John, lastName=null, job=null)
Person(firstName=John, lastName=Doe, job=null)
Person(firstName=John, lastName=Doe, job=Unemployed)
model mapping utilities
You can also use one of the many model mapping utilities, like the ones listed in http://www.baeldung.com/java-performance-mapping-frameworks (there at least you already see some performance benchmarks regarding the different kind of model mappers).
Note that I cannot really recommend writing your own mapping utility if you do not test it thoroughly. Already seen examples where the custom mapping utility grew and grew and later on lead to strange behaviour as some corner cases weren't considered.
simplifying the != null
Otherwise, if you are not too lazy, I would rather recommend something like:
personModification.firstName?.also { person.firstName = it }
It doesn't require any reflection, is simple and still readable... somehow at least ;-)
delegated properties
Another thing that comes to my mind and somehow matches your Javascript approach are delegated properties (which I only recommend if the backed Map is a suitable model for you; actually what I am showing below is rather a delegated person map using a HashMap, which I can not really recommend, but which is quite an easy and useful way to get the Javascript look&feel; the reason why I don't recommend it: is Person a Map? ;-)).
class Person() : MutableMap<String, String?> by HashMap() { // alternatively use class Person(val personProps : MutableMap<String, String?> = HashMap()) instead and replace `this` below with personProps
var firstName by this
var lastName by this
var job by this
constructor(firstName : String?, lastName : String?, job : String?) : this() {
this.firstName = firstName
this.lastName = lastName
this.job = job
}
}
The PersonModification-class then basically looks the same. Applying the mapping would then look like:
val person = Person("first", "last", null)
val personMod = PersonModification("new first", null, "new job")
personMod.filterValues { it != null }
.forEach { key, value -> person[key] = value } // here the benefit of extending the Map becomes visible: person[key] instead of person.personProps[key], but then again: person.personProps[key] is cleaner
If you do not require that secondary constructor it's even better, then the class looks nearly as before and the properties can be set and get as before.
Thinking about it you do not really need the secondary constructor as you could still use apply and then just add the variables you are interested in (nearly as named parameters). Then the class would look similar to:
class PersonModification : MutableMap<String, String?> by HashMap() { // or again simply: class PersonModification(props : MutableMap<String, String?> = HashMap()) and replacing `this` with props below
var firstName by this
var lastName by this
var job by this
}
and instantiating it then looks as follows:
val personMod = PersonModification().apply {
firstName = "new first"
job = "new job"
}
Mapping would still be the same.
Already many people offered their solutions. But I want to offer one more:
There are interesting feature in jackson, you could try to merge json. So, you could merge src object with deserialization version of PersonModification
With it, it's possible to do something like this:
class ModificationTest {
#Test
fun test() {
val objectMapper = jacksonObjectMapper().apply {
setSerializationInclusion(JsonInclude.Include.NON_NULL)
}
fun Person.merge(personModification: PersonModification): Person = run {
val temp = objectMapper.writeValueAsString(personModification)
objectMapper.readerForUpdating(this).readValue(temp)
}
val simplePerson = Person("firstName", "lastName", "job")
val modification = PersonModification(firstName = "one_modified")
val modification2 = PersonModification(lastName = "lastName_modified")
val personAfterModification1: Person = simplePerson.merge(modification)
//Person(firstName=one_modified, lastName=lastName, job=job)
println(personAfterModification1)
val personAfterModification2: Person = personAfterModification1.merge(modification2)
//Person(firstName=one_modified, lastName=lastName_modified, job=job)
println(personAfterModification2)
}
}
Hope this will help you!
Create an extension function for Person:
fun Person.modify(pm: PersonModification) {
pm.firstName?.let { firstName = it }
pm.lastName?.let { lastName = it }
pm.job?.let { job = it }
}
fun Person.println() {
println("firstName=$firstName, lastName=$lastName, job=$job")
}
and use it like this:
fun main(args: Array <String> ) {
val p = Person("Nick", "Doe", "Cartoonist")
print("Person before: ")
p.println()
val pm = PersonModification("Maria", null, "Actress")
p.modify(pm)
print("Person after: ")
p.println()
}
Or choose one of the following:
fun Person.println() {
println("firstName=$firstName, lastName=$lastName, job=$job")
}
fun main(args: Array <String> ) {
val p = Person("Nick", "Doe", "Cartoonist")
print("Person before: ")
p.println()
val pm = PersonModification("John", null, null)
pm.firstName?.run { p.firstName = this }.also { pm.lastName?.run { p.lastName = this } }.also { pm.job?.run { p.job = this } }
// or
pm.firstName?.also { p.firstName = it }.also { pm.lastName?.also { p.lastName = it } }.also { pm.job?.also { p.job = it } }
// or
with (pm) {
firstName?.run { p.firstName = this }
lastName?.run { p.lastName= this }
job?.run { p.job= this }
}
print("Person after: ")
p.println()
}
It is nothing fancy, but it hides the complexity of mutating Person from the outside world.
class Person(
var firstName: String?,
var lastName: String?,
var job: String?
) {
fun modify(p: PersonModification){
p.firstName?.let { firstName = it }
p.lastName?.let { lastName = it }
p.job?.let { job = it }
}
}
class PersonModification(/* ... */)

Kotlin generate constructor that sets default values to nulled arguments

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.