what is the equivalent of python id in kotlin? - kotlin

In python you can get a unique numeric ID for any object via id(object):
person = Person()
person_id = id(person)
What is the equivalent function in Kotlin?

Classes in Kotlin do not automatically have a unique ID. As mentioned in the comments, you can get the identityHashCode. It is not guaranteed unique, but in practice if you are just using it to compare items in a log, it is probably sufficient.
class Person() {
val id: Int get() = System.identityHashCode(this)
}
If you need unique IDs, you could assign them at construction time using a counter in a companion object.
class Person() {
val id: Long = nextId
companion object {
private var nextId: Long = 0L
get() = synchronized(this) { ++field }
set(_) = error("unsupported")
}
}
// Or simpler on JVM:
class Person() {
val id: Long = idCounter.getAndIncrement()
companion object {
private val idCounter = AtomicLong(1L)
}
}
Or if you are on JVM, you can use the UUID class to generate a statistically unique ID for each class as it is instantiated, but this is probably not very useful just for logging.
class Person() {
val id = UUID.randomUUID()
}

Related

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.

Deserialize a nested json field with Jackon 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)
}

What is the benefit of using primarykey and references method in class jooq

I'm start the learn jooq. I have mssql server. I create some class the represent table on my server. But I don't understand what is the benefit when I was using getPrimaryKey and getReferences methods in my table class?
class User : TableImpl<Record>("users") {
companion object {
val USER = User()
}
val id: TableField<Record, Int> = createField("id", SQLDataType.INTEGER)
val name: TableField<Record, String> = createField("name", SQLDataType.NVARCHAR(50))
val countryId: TableField<Record, Short> = createField("country_id", SQLDataType.SMALLINT)
override fun getPrimaryKey(): UniqueKey<Record> = Internal.createUniqueKey(this, id)
override fun getReferences(): MutableList<ForeignKey<Record, *>> =
mutableListOf(Internal.createForeignKey(primaryKey, COUNTRY, COUNTRY.id))
}
class Country : TableImpl<Record>("country") {
companion object {
val COUNTRY = Country()
}
val id: TableField<Record, Short> = createField("id", SQLDataType.SMALLINT)
val name: TableField<Record, String> = createField("name", SQLDataType.NVARCHAR(100))
override fun getPrimaryKey(): UniqueKey<Record> =
Internal.createUniqueKey(this, id)
}
The generated meta data is a mix of stuff that's useful...
to you, the API user
to jOOQ, which can reflect on that meta data for a few internal features
For instance, in the case of getPrimaryKey(), that method helps with all sorts of CRUD related operations as you can see in the manual:
https://www.jooq.org/doc/latest/manual/sql-execution/crud-with-updatablerecords/simple-crud
If you're not using the code generator (which would generate all of these methods for you), then there is no need to add them to your classes. You could shorten them to this:
class User : TableImpl<Record>("users") {
companion object {
val USER = User()
}
val id: Field<Int> = createField("id", SQLDataType.INTEGER)
val name: Field<String> = createField("name", SQLDataType.NVARCHAR(50))
val countryId: Field<Short> = createField("country_id", SQLDataType.SMALLINT)
}
However, using the code generator is strongly recommended for a variety of advanced jOOQ features which you might not get, otherwise.

How to use init construct in Kotlin in elegant way

There are many examples how to use init during class construction, but they are mostly without using actual class properties.
What I need to do is init a class property from database on object init. So far I have this:
class MyObj constructor(val id: Long){
var data: MutableMap? = null
init {
data = db.find(id) // more like pseudocode, db fetch is done and result assigned to data property
}
}
But it seems a little overcomplicated to me. Is there any better, more elegant way to do this ?
You can initialize the property directly in the class body:
class MyObj(val id: Long) {
val data: MutableMap = db.find(id)
}
Then you won't need to declare it as a nullable type, and may be able to use val instead of var. (I've also removed the constructor keyword which is redundant.).
You can just write:
class MyObj constructor(val id: Long){
val data: Map<String, String> = mapOf()
}
It's equivalent to:
class MyObj constructor(val id: Long){
val data: Map<String, String>
init {
data = mapOf()
}
}
Init block is useful if you need introduce some logic for your object initialization, like error checking etc. Ofc you can do it with or without init block. But remember what is more readable:
class MyObj constructor(val id: Long){
val data: Map<String, String> = if (id == 0L) {
mapOf(Pair("", ""))
} else {
throw IllegalStateException()
}
} // without init
class MyObj constructor(val id: Long){
val data: Map<String, String>
init {
data = if (id == 0L) {
mapOf(Pair("", ""))
} else {
throw IllegalStateException()
}
}
} // with init
You can also init this value using lazy (what means property will be initialized as function, but value will be assigned when you first ask for it, not when object is created). I guess taking value from db can be quite long, so it may be useful:
val data: Map<String, String> by lazy { mapOf() }

Cannot save data model that contains List<Model> with Room ORM Kotlin

I have a problem with Room ORM working on Kotlin. My task is having ability to save and get data models RouteTemplateModel, that contains list of addresses of type AddressModel and object of class RouteModel that contains title of the specific route. Here is my code:
AddressModel.kt
#Entity(foreignKeys = arrayOf(
ForeignKey(entity = RouteModel::class,
parentColumns = arrayOf("routeId"),
childColumns = arrayOf("parentId"))))
data class AddressModel(
#PrimaryKey(autoGenerate = true)
var addressId: Long,
var parentId: Long,
var street: String,
var house: String,
var entrance: String,
var title: String){
constructor(): this(0, 0, "", "", "", "")
}
RouteModel.kt
#Entity
data class RouteModel(
#PrimaryKey(autoGenerate = true)
var routeId: Long,
var title: String) {
constructor() : this(0, "")
}
Here is my simple models, I found in documentation of Room that for creating relations between models I need to use #ForeignKey and #Relation
So with code samples in doc and tutorials I create RouteTemplateModel that contains object of RouteModel and list of AddressModels. Here is the class
RouteTemplateModel
class RouteTemplateModel{
private var id: Long = 0
#Embedded
private var routeModel: RouteModel = RouteModel()
#Relation(parentColumn = "routeId", entityColumn = "parentId")
private var addressList: List<AddressModel> = listOf()
constructor()
constructor(id: Long, routeModel: RouteModel, title: String,
addressList: List<AddressModel>){
this.id = id
this.routeModel = routeModel
this.addressList = addressList
}
fun getId(): Long{
return id
}
fun getRouteModel(): RouteModel{
return routeModel
}
fun getAddressList(): List<AddressModel>{
return addressList
}
fun setId(id: Long){
this.id = id
}
fun setRouteModel(routeModel: RouteModel){
this.routeModel = routeModel
}
fun setAddressList(addressList: List<AddressModel>){
this.addressList = addressList
}
}
So what`s a problem? I am getting such errors:
Error:The columns returned by the query does not have the fields [id]
in com.innotech.webcab3kotlin.model.RouteTemplateModel even though
they are annotated as non-null or primitive. Columns returned by the
query: [routeId,title]
And
Error:Type of the parameter must be a class annotated with #Entity or
a collection/array of it.
It is a real problem, because if my trying to fix first error and annotate in RouteTemplateModel id variable to return this column too, I need annotate class as Entity (like in second error), but when I do it I am getting an error
Error:Entities cannot have relations.
Here is AppDatabase.kt
#Database(entities = arrayOf(RouteModel::class, AddressModel::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun getRouteDao(): RouteDao
}
and RouteDao.kt
#Dao
interface RouteDao {
#Query("SELECT routeId, title FROM RouteModel")
fun getAll(): List<RouteTemplateModel>
#Insert
fun insertAll(vararg models: RouteTemplateModel)
#Delete
fun delete(model: RouteTemplateModel)
}
Thats really confusing. Please, help me)
Your "parentId" column is capable of holding long value only, make its type to "Text" then create a TypeConverter from "List" to String and vice a versa for reference please have a look at link .