Search code examples
typescriptkotlindata-class

Kotlin equivalent of Omit TypeScript utility type


I have two data classes in Kotlin that are very similar. The only difference is that one has an ID field and the other doesn't (the ID is generated if the model has been stored in the database only).

data class RouteWithId(
    val id: String,
    val name: String,
    val description: String,
    val comments: List<Comment>,
    val media: List<Media>,
    val points: List<RoutePoint>,
    val userId: String,
    val status: RouteState,
    val tracks: List<TrackInfo>,
) {
    enum class RouteState(val value: String){
        IN_REVIEW("in-review"),
        PUBLISHED("published");
    }
}
data class Route(
    val name: String,
    val description: String,
    val comments: List<Comment>,
    val media: List<Media>,
    val points: List<RoutePoint>,
    val userId: String,
    val status: RouteState,
    val tracks: List<TrackInfo>,
) {
    enum class RouteState(val value: String){
        IN_REVIEW("in-review"),
        PUBLISHED("published");
    }
}

I've tried to unify them with a nullable id field, but many times, it just creates a lot of noise since I know that in several places, the ID would exist (any postprocessing after loading from the database).

I've seen that TypeScript has an utility type called Omit which can be used to derive a type from another type but omitting some field.

This would be perfect for my use case, since I know that after loading from a database the field is going to be there, and before that I also know it won't be there.

Do you know how to achieve the same thing in Kotlin?


Solution

  • There aren't union types in Kotlin, but you can use inheritance to achieve this:

    class Route(val name: String, val description: String, /* ... */) {
        enum class RouteState(val value: String){
            IN_REVIEW("in-review"),
            PUBLISHED("published");
        }
        
        fun withId(id: Int): DBRoute = DBRoute(id, name, description, /* ... */)
    }
    
    class DBRoute(val id: Int, name: String, description: String, /* ... */) : Route(name, description) {    
        fun asRoute(): Route = Route(name, description, /* ... */)
    }
    

    This way, you can convert a Route to a DBRoute and vice-versa. The drawback of this is that you'll have to write all the fields from Route in the constructor of DBRoute.

    I don't know if it would be good for you, but you could also use a Map<Int, Route> without needing to create a new class, but this will fit better to a class if you need some operations to envolving the ID somehow.