I'm new with Kotlin and I try to rework a small Java project to this new language. I use mongodb in my project and I have a class, for example:
class PlayerEntity {
constructor() {} //for mongodb to create an instance
constructor(id: ObjectId, name: String) { //used in code
this.id = id
this.name = name
}
#org.mongodb.morphia.annotations.Id
var id: ObjectId? = null
var name: String? = null
}
I have to mark id field as nullable (var id: ObjectId?) because of empty constructor. When I try to access this field from another class I have to use non-null check: thePlayer.id!!. But the logic of my application is that id field is never null (mongo creates an instance of Player and immediately sets id field). And I don't want to make a non-null check everywhere.
I tried to make a non-null getter, but it does not compile:
var id: ObjectId? = null
get(): ObjectId = id!!
I can also make some stub for id and use it in constructor, but this looks like a dirty hack:
val DUMMY_ID = new ObjectId("000000000000000000000000");
So is there a workaround to solve the issue?
I personally use a private var prefixed by _ + public val in similiar situations.
class Example<out T> {
private var _id: T? = null
val id: T
get() = _id!!
}
For your situation, it would look like this:
#org.mongodb.morphia.annotations.Id
private var _id: ObjectId? = null
val id: ObjectId
get() = _id!!
Alternatively, declare your variable as lateinit like this (but note that this exposes the setter publicly):
#org.mongodb.morphia.annotations.Id
lateinit var id: ObjectId
Related
I would like to do mapping of nested object that needs value from the parent object. I could use solution mentioned here mapstruct - Propagate parent field value to collection of nested objects - either directly after mapping to set some value to the child object or to use context. But in my case I work with immutable objects.
example:
data class Worker(
val name: String,
val businessCard: BusinessCard? = null,
)
data class BusinessCard(
val companyName: String,
)
data class WorkerDto(
val name: String,
val businessCard: BusinessCardDto? = null,
)
data class BusinessCardDto(
val text: String, // "worker name | company name"
)
Is there a way how to directly map value without #AfterMapping modifications?
Something like this?
#Mapper(config = CustomMappingConfig::class, uses = [ComputerMapper::class])
abstract class WorkerMapper {
#Mapping(target = "businessCard.text", expression = "java(mapBcText(worker))")
abstract fun mapWorker(worker: Worker): WorkerDto
protected fun mapBcText(worker: Worker) = "${worker.name} | ${worker.businessCard?.companyName}"
}
But sadly the code above generates:
#Override
public WorkerDto mapWorker(Worker worker) {
if ( worker == null ) {
return null;
}
String name = null;
BusinessCardDto businessCard = null;
name = worker.getName();
businessCard = businessCardToBusinessCardDto( worker.getBusinessCard() );
WorkerDto workerDto = new WorkerDto( name, businessCard );
return workerDto;
}
protected BusinessCardDto businessCardToBusinessCardDto(BusinessCard businessCard) {
if ( businessCard == null ) {
return null;
}
BusinessCardDto businessCardDto = new BusinessCardDto();
businessCardDto.setText( mapBcText(worker) ); // WORKER IS NOT ACCESSIBLE HERE
return businessCardDto;
}
Does anybody have an idea how to achieve this mapping?
...I also tried to create custom BusinessCard mapper, but then I cannot access the parent data (Worker) in it then...
you need to use var instead of val in your dataclass.
mapstruct don't seem to manage immutable kotlin class for the moment.
I have a class with #JvmSynthetic setters in order to provide only fluent builder-like setters for Java clients:
class PersonBuilder {
#set:JvmSynthetic // hide a void setter from Java
var age: Int? = null
#set:JvmSynthetic
var name: String? = null
fun setAge(age: Int?) = apply { this.age = age }
fun setName(name: String?) = apply { this.name = name }
fun build() = Person(age!!, name!!)
}
And call it like this on the Java side:
new PersonBuilder()
.setAge(22) // <- "error"
.setName("Peter")
.build();
It does compile & run, but Android Studio shows the error "Ambiguous method call. Both setAge (Integer) in PersonBuilder and setAge (Integer) in PersonBuilder match" and doesn't provide any auto-suggestions and code formating after this "error".
I believe there is no need to use the #set:JvmSynthetic notation, just changing the visibility of variables should do the trick. Try this:
class PersonBuilder {
private var age: Int? = null
private var name: String? = null
fun setAge(age: Int) = apply { this.age = age }
fun setName(name: String) = apply { this.name = name }
fun build() = Person(age!!, name!!) //be careful!
}
Anyway, be careful with the build function. There are no guarantees that the functions setAge and setName will be called before it, so you can get a NullPointerException
Update
Another way to solve this problem by keeping #JvmSynthetic is to add the #JvmField notation as well. See this article for more information.
I hava a class Packet.java(can't modify) in a package.
public class Packet implements java.io.Serializable, Cloneable {
private static final AtomicLong ID_ATOMICLONG = new AtomicLong();
private Long id = ID_ATOMICLONG.incrementAndGet();
}
I use own class LoginPacket.kt (can modify)
class LoginPacket : Packet () {
var id = "" ( this name must be id )
fun parsePacket(input: String): Boolean {
val map = HashMap<String,Any>()
map["id"] = "5d6ff3433354b4d43076419"
var wrapper: BeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(this)
wrapper.isAutoGrowNestedPaths = true
// question is here , I can not set id as String use BeanWrapper, Only can set id as Long
// and also I can replace id's getter and setter method
val pd = wrapper.getPropertyDescriptor("id")
pd.readMethod = LoginPacket::id.getter.javaMethod
pd.writeMethod = LoginPacket::id.setter.javaMethod
wrapper.setPropertyValues(map)
}
}
So what I can do next?
Thanks very much for sharing!
Beanwrapper link
It is not possible to override the type of a field.
What you can do instead depends on what you are trying to do, and which libraries you are using.
I can think of one way that may work, assuming your library does not need an instance nor subclass of Packet.
And that is creating your own class that only implements the interfaces:
class LoginPacket(): java.io.Serializable, Cloneable {
// You may or may not need this.
// Since the original version uses it to generate the ID,
// I think you can skip this part.
companion object {
#JvmStatic
private val ID_ATOMICLONG = AtomicLong()
}
var id : String = ""
fun parsePacket(input: String): Boolean {
val map = HashMap<String,Any>()
map["id"] = "5d6ff3433354b4d43076419"
var wrapper: BeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(this)
wrapper.isAutoGrowNestedPaths = true
val pd = wrapper.getPropertyDescriptor("id")
pd.readMethod = LoginPacket::id.getter.javaMethod
pd.writeMethod = LoginPacket::id.setter.javaMethod
wrapper.setPropertyValues(map)
}
}
It is hard to provide better answers without more context.
I used Android studio's Kotlin plugin to convert my Java class to Kotlin. The thing is it's not Kotlin style still. I want to have Kotlin Data Class instead. But whenever I create it with a primary and secondary constructors it won't work. What would be the correct DATA Class implementation in my case?
class Task {
#SerializedName("_id")
var id: String? = null
#SerializedName("text")
var taskTitle: String? = null
#SerializedName("completed")
var isCompleted: Boolean? = null
constructor(taskTitle: String) {
this.taskTitle = taskTitle
}
constructor(taskTitle: String, completed: Boolean?) {
this.taskTitle = taskTitle
this.isCompleted = completed
}
constructor(id: String, taskTitle: String, isCompleted: Boolean?) {
this.id = id
this.taskTitle = taskTitle
this.isCompleted = isCompleted
}
}
Kotlin introduces default values for parameters in constructor. You can use them to create data class with only one constructor using Kotlin.
It would look like this
data class Task(
#SerializedName("_id") var id: String? = null,
#SerializedName("text") var taskTitle: String? = null,
#SerializedName("completed") var isCompleted: Boolean? = null
)
So you can use your data class with any number of arguments for example:
var task = Task(taskTitle = "title")
var task = Task("id", "title", false)
var task = Task(id = "id", isCompleted = true)
You can even replace argument order
var task = Task(taskTitle = "title", isCompleted = false, id = "id")
I am very new to Gson and Json. I have simple Events that I want to serialize through Json with the help of Gson.
Note: Code in Kotlin.
public abstract class Event() {
}
public class Move : Event() {
var from: Point? = null
var to: Point? = null
}
public class Fire : Event() {
var damage: Int = 0
var area: ArrayList<Point> = ArrayList(0)
}
public class Build : Event() {
var to: Point? = null
var type: String = ""
var owner: String = ""
}
I am persisting bunch of these via this way:
val list: ArrayList<Event>() = ArrayList()
list.add(move)
list.add(fire)
val str = gson.toJson(events)
And unpersisting:
val type = object : TypeToken<ArrayList<Event>>(){}.getType()
val eventStr = obj.getString("events")
val events: ArrayList<Event> = gson.fromJson(eventStr, type)
I have tried both creating a serializer & deserializer for Event-class, and registering it via registerTypeAdapter, and I have also tried the RuntimeTypeAdapterFactory, but neither will persist the information required to unpersist the correct type.
For example, the RuntimeTypeAdapterFactory says:
"cannot deserialize Event because it does not define a field named type"
EDIT: Here's the code for the "Adapter", which was.. well, adapted from another StackOverflow post:
public class Adapter :
JsonSerializer<Event>,
JsonDeserializer<Event> {
final val CLASSNAME = "CLASSNAME"
final val INSTANCE = "INSTANCE"
override fun serialize(src: Event?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement? {
val obj = JsonObject()
val className = (src as Event).javaClass.getCanonicalName()
obj.addProperty(CLASSNAME, className)
val elem = context!!.serialize(src)
obj.add(INSTANCE, elem)
return obj
}
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Event? {
val jsonObject = json!!.getAsJsonObject()
val prim = jsonObject.get(CLASSNAME)
val className = prim.getAsString()
val klass = Class.forName(className)
return context!!.deserialize(jsonObject.get(INSTANCE), klass)
}
}
This code fails with NullPointerException on line:
val className = prim.getAsString()
You can't do it this way.
The example you are referring is not targeted to your case. It works in only one case: if you register base type (not type hierarchy) and serialize using gson.toJson(obj, javaClass<Event>()). It will never work for array except you write custom serializer for you events container object too
Generally you need another approach: use TypeAdapterFactory and delegate adapters: GSON: serialize/deserialize object of class, that have registered type hierarchy adapter, using ReflectiveTypeAdapterFactory.Adapter and https://code.google.com/p/google-gson/issues/detail?id=43#c15
I believe this approach is overcomplicated so if you have few types the easiest solution is two serialize these types by hand, field by field via custom serializer and forget about attempts to delegate to default