1

I have a data class like so:

data class Session(
    val sessionId: String,
    val duration: Int,
    val creationTimestamp: String,  //example: "2021-04-20T15:45:23.160599+00:00"
    val status: String
) {
    val creationTime: ZonedDateTime
        get() {
            return ZonedDateTime.parse(creationTimestamp)
        }
}

The creation time is a string but I have added a ZonedDateTime property which returns it parsed.

Even though this works, I wanted to find a way to avoid the time getting parse every time it is accessed. So I just wrote

val creationTime: ZonedDateTime = ZonedDateTime.parse(creationTimestamp)

in the hopes that this would run after the data class constructor. Nope.

One. Is there a way to achieve this without making the property nullable like this?

private var creationTime: ZonedDateTime? = null
    get() {
        if(field == null) {
            field = ZonedDateTime.parse(creationTimestamp)
        }
        return field
    }

Two. What is the conventional wisdom on this. Is it good practice to have convenience properties in a data class?

2 Answers 2

3

As I understand it, you want to create a derived property (creationTime) which is calculated from the creationTimestamp property?

This is a data class, so properties defined in the body and not the constructor won't be included in the autogenerated toString(), equals(), etc.  But since the data property creationTimestamp is a val (and immutable), this probably doesn't matter too much.

You can optimise it using by lazy:

val creationTime by lazy { ZonedDateTime.parse(creationTimestamp) }

This will create the ZonedDateTime the first time it's needed (which will be after construction), so it should be safe and efficient.


As for whether this is good practice, I don't think there's an obvious answer; it depends on the circumstance.

If the property conceptually ‘fits’ within the class, is likely to be widely useful, and/or if the lazy optimisation is very important, then it should be fine.  OTOH, if it's more related to only one particular usage, if it conceptually couples this class to others, or if bulks out the class unnecessarily, then you would probably consider it a code smell.


However, in this particular case, there's no reason why you shouldn't be able to do:

val creationTime = ZonedDateTime.parse(creationTimestamp)

in the class body, as in the question.  creationTimestamp is set at that point; I've just tried it, and it works fine for me.  Is there some other problem in code you've not posted?

Sign up to request clarification or add additional context in comments.

2 Comments

will the fact that this class in initialized by Retrofit using Gson be a cause for issue? the property is always null. an when I do the lazy init, I get an error: Attempt to invoke interface method 'java.lang.Object kotlin.Lazy.getValue()' on a null object reference
@ShahiM Gson uses reflection to set fields in an unsafe way, bypassing constructor calls etc. So this may very well be the reason. Kotlin plays nicer with kotlinx-serialization, or jackson-module-kotlin.
0

Is there any reason why this would not suffice?

data class Session(
    val sessionId: String,
    val duration: Int,
    val creationTimestamp: String,
    val status: String
) {
    val creationTime = ZonedDateTime.parse(creationTimestamp)
}

Writing val creationTime: ZonedDateTime = ZonedDateTime.parse(creationTimestamp) in the constructor itself won't work because it represents a default value there, but having it as a property of the class itself should work just fine.

5 Comments

It just returns null. I guess it is because creationTimestamp is null when the parse executes.
sorry but that is empirically not true. You have either discovered a massive bug that nobody else knows about and ever encountered, or you have made a mistake somewhere. What version of kotlin are you using?
turns out the data class is deserialized by retrofit using Gson. might that be the cause?
@ShahiM Gson uses reflection to set fields in an unsafe way, bypassing constructor calls etc. So this may very well be the reason. Kotlin plays nicer with kotlinx-serialization, or jackson-module-kotlin.
well I guess I will stick with my initial code for now. I will look into your suggestion down the line. thanks

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.