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")
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 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'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
I have xml like this:
<horo>
<aries>
<today>
Сегодня вас могут здорово огорчить. Если от расстройства все начнет валится из рук, просто спокойно сядьте и тихонько подождите хорошей новости.
</today>
</aries>
<taurus>
<today>
Сегодня у вас могут возникнуть проблемы на личном фронте. Спасти вас от перспективы оказаться не у дел может сухой, рациональный и в высшей степени объективный подход к проблеме.
</today>
</taurus>
</horo>
And now i learning kotlin whith retrofit. I include libraries for parse xml, and not i cant understand how create object for parsing this xml. I have object:
#Root(name = "horo", strict = false)
open class DailyHoroscope{
#get : Element(name = "aries") var aries : Aries? = null
}
#Root(name = "aries", strict = false)
open class Aries{
#get : Element(name = "today") var today : String? = null
}
but i have error:
rg.simpleframework.xml.core.ConstructorException: Default constructor
can not accept read only #org.simpleframework.xml.Element(data=false,
name=aries, required=true, type=void) on method 'aries' in class
ac.kotlintest.model.
upd
i writed code in java:
#Root(name = "horo", strict = false)
public class DailyHoroscopeJ {
#Element(name = "aries")
public Aries aries;
public Aries getAries() {
return aries;
}
public void setAries(Aries aries) {
this.aries = aries;
}
}
#Root(name = "aries", strict = false)
class Aries{
#Element(name = "today")
public String today;
public String getToday() {
return today;
}
public void setToday(String today) {
this.today = today;
}
}
and it work fine, then i convert to kotlin
#Root(name = "horo", strict = false)
class DailyHoroscope {
#get:Element(name = "aries")
var aries:Aries? = null
}
#Root(name = "aries", strict = false) class Aries {
#get:Element(name = "today")
var today:String? = null
}
but i have same problem((((
The answer of #daementus is almost perfect. If you want to use constructor injection with default parameters, you have to force Kotlin to generate constructor overloads:
data class Section #JvmOverloads constructor(
#field:Element(name = "id")
#param:Element(name = "id")
val id: Long,
#field:Attribute(name = "title", required = false)
#param:Attribute(name = "title", required = false)
val title: String = ""
)
Without it you will get Constructor not matched for class Section.
By default Kotlin generates a constructor with all parameters and a special constructor.
Note: I would prefer to answer in the comments but I don't have enough points.
Indeed, Simple XML Framework has a few problems with Kotlin attributes and it can be a little tricky to get things to work.
To be honest, I am not quite sure what is the problem in your specific case, but I'd guess that the annotation shouldn't be specified for the getter, but rather for the field.
Anyway, I am combining Simple XML and Kotlin data classes this way, and it seems to be working fine :)
data class Section (
#field:Element(name = "id", required = false)
#param:Element(name = "id", required = false)
val id: Long? = null,
#field:Attribute(name = "title", required = false)
#param:Attribute(name = "title", required = false)
val title: String? = null
)
Edit: If you don't want to use data classes (which I highly recommend, but you might have a reason), this should work without the "data" keyword just fine. If you don't wish to create a constructor, just move the attribute declarations directly into the class and get rid of the #param annotation (the #field must stay).
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