hello, i have a question related to inheritance an...
# announcements
a
hello, i have a question related to inheritance and data classes in my specific case i receive the same object as json once with an enclosing data wrapper, and once in an object which has a list of that type:
Copy code
case 1: { data: {props.....} } -> StoredVehicleUserSingle
case 2: { data: [ {props.....}, {props.....} ]  } -> StoreVehicleUsers with a list of StoredVehicleUsersList
so far i have solved it like this:
Copy code
data class StoredVehicleUsers(
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, defaultImpl = StoredVehicleUserList::class)
    @JsonProperty("data") val vehicleUsers: List<StoredVehicleUser>
)

/**
 * we need to implementations for this for:
 * [StoredVehicleUsers] its [StoredVehicleUserList]
 * if its a single [StoredVehicleUser] we receive its [StoredVehicleUserSingle]
 */
interface StoredVehicleUser {
    val vehicleId: String
    val userId: String
    val role: String?val roleTimestamp: Date?
    val resetRole: String?
    val resetRoleTimestamp: Date?
}

@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME)
@JsonTypeName(value = "data")
internal data class StoredVehicleUserSingle(
    override val vehicleId: String,
    override val userId: String,
    override val role: String? = null,
    override val roleTimestamp: Date? = null,
    override val resetRole: String? = null,
    override val resetRoleTimestamp: Date? = null
) : StoredVehicleUser

internal data class StoredVehicleUserList(
    override val vehicleId: String,
    override val userId: String,
    override val role: String? = null,
    override val roleTimestamp: Date? = null,
    override val resetRole: String? = null,
    override val resetRoleTimestamp: Date? = null
) : StoredVehicleUser
i think the solution is fine, the only thing that bugs me, is that i am repeating all properties in StoredVehicleUser, StoredVehicleUserSingle and StoredVehicleUserList, despite they are same. the implementations must be data classes to have euqal and hashcode. but if i use inheritance i actually must list all props in subclasses again and pass them to Super constructor. also a data class must have at least one constructor argument How can i do this better? (edited)
t
I think the cleanest way would be to process the json tree manually.
Copy code
- parse json as json tree
- if there is an array "data": convert every entry to StoredVehicleUser
- else there must be an object "data", convert to StoredVehicleUser and wrap in List<>
The incoming json is not well formed and so it's completely fine to have some special handling code to transform into a well formed business object which you can process further (imo).
It's better than having the strange json format leaking into your model
a
yeah, that is a possibility. i might consider that. but at the core of my question is actually a more general question. how can create data classes inheriting fields from a super class without repeating them?
t
If I need this, I usually wrap them:
data class User(val name: String, val email: ...)
data class UserWithPassword(val user: User, val password: String)
a
that would not work in my case. but yes its not a scenario which applies often
a
btw the answer to the Jackson problem is likely to set the relevant deserialization feature: https://stackoverflow.com/questions/35201036/jackson-deserialize-single-item-into-list
data classes extending other data classes is problematic. in Scala, they ended up banning case classes (basically equivalent) extending each other.
a
thx...the base class would not have to be a data class but....but i think its just not possible
here some weird expirement which works:
Copy code
@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME)
@JsonTypeName(value = "data")
data class StoredVehicleUserSingle( @JsonIgnore() val ignore: Int) : Base()

internal data class StoredVehicleUserList( @JsonIgnore() val ignore: Int) : Base()


abstract class Base: StoredVehicleUser, HasToJson {
    override val vehicleId: String = ""
    override val userId: String = ""
    override val role: String? = null
    override val roleTimestamp: Date? = null
    override val resetRole: String? = null
    override val resetRoleTimestamp: Date? = null
}
ok here my final weired solution: (which works)
Copy code
import com.fasterxml.jackson.annotation.*
import seat.b4.testing.serialize.HasToJson
import java.util.*

data class StoredVehicleUsers(
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, defaultImpl = StoredVehicleUserList::class)
    @JsonProperty("data") val vehicleUsers: List<StoredVehicleUser>
) : HasToJson

/**
 * we need to implementations for this for:
 * [StoredVehicleUsers] its [StoredVehicleUserList]
 * if its a single [StoredVehicleUser] we receive its [StoredVehicleUserSingle]
 */

abstract class StoredVehicleUser : HasToJson {
    lateinit var vehicleId: String
    lateinit var userId: String
    val role: String? = null
    val roleTimestamp: Date? = null
    val resetRole: String? = null
    val resetRoleTimestamp: Date? = null
}

@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME)
@JsonTypeName(value = "data")
@JsonInclude(JsonInclude.Include.NON_NULL)
data class StoredVehicleUserSingle(val ignore: Any? = null) : StoredVehicleUser(), HasToJson

@JsonInclude(JsonInclude.Include.NON_NULL)
internal data class StoredVehicleUserList(val ignore: Any? = null) : StoredVehicleUser()
m
mind I ask why you would want it in a data class to begin with if this is your final implementation? The only practical thing data classes are good for is because they have a predefined .equals() and .toString() method. By not encapsulating any data in the constructor of the data class, you are practically ignoring it's only usability and therefor can just use normal classes instead
a
sure.....the reason is i need equals and hashcode for comparison...and ur absolutely right....that non constructor fields are ignored....my weired solution...is not the one we are using...it was just a try...i still think that kotlin could provide a better way for inheritance in this case without repeating contructor args all over again...but its certainly a corner case....
m
I'm not really into the whole Json library, but since we have to defined cases of list and singular, can't you make this work?
Copy code
sealed class StoredVehicleUser{
    data class Single(
        ...fields
    ) : StoredVehicleUser()
    data class List(
        atLeastOne: Single,
        more: List<Single>
    ) : StoredVehicleUser()
}
your list needs at least one item so maybe it also needs a custom getter or something, but I don't think you need the two constructors
a
i was also thinking about sealed classes....the problem remains: i have to repeat the constructor args....