Add Companion Object Factory Methods to Kotlin Data Classes

Learn how to enhance generated Kotlin data classes with companion object factory methods for JSON parsing, validation, and default instances. Covers invoke operator and named constructors.

Advanced Patterns

Detailed Explanation

Companion Objects in Kotlin Data Classes

After converting JSON to a Kotlin data class, you can enhance it with a companion object that provides factory methods, constants, and parsing utilities.

Basic Data Class from JSON

{
  "latitude": 37.7749,
  "longitude": -122.4194,
  "label": "San Francisco"
}
data class Location(
    val latitude: Double,
    val longitude: Double,
    val label: String
) {
    companion object {
        val ORIGIN = Location(0.0, 0.0, "Origin")

        fun fromJson(jsonString: String): Location =
            Json.decodeFromString(jsonString)

        fun parse(lat: String, lng: String, label: String = "Unknown"): Location =
            Location(lat.toDouble(), lng.toDouble(), label)
    }
}

Usage

val sf = Location.fromJson(jsonString)
val origin = Location.ORIGIN
val parsed = Location.parse("40.7128", "-74.0060", "New York")

Factory Method Patterns

Validation factory:

companion object {
    fun create(lat: Double, lng: Double, label: String): Location {
        require(lat in -90.0..90.0) { "Invalid latitude" }
        require(lng in -180.0..180.0) { "Invalid longitude" }
        return Location(lat, lng, label)
    }
}

Default instance:

companion object {
    val DEFAULT = Location(0.0, 0.0, "Unknown")
    val EMPTY = Location(0.0, 0.0, "")
}

The invoke Operator

companion object {
    operator fun invoke(jsonString: String): Location =
        Json.decodeFromString(jsonString)
}

// Looks like a constructor call:
val location = Location(jsonString)

Why Companion Objects?

  • Encapsulation -- parsing logic lives with the data class, not scattered across the codebase
  • Named constructors -- Location.fromJson(), Location.fromCsv(), Location.fromDatabase()
  • Constants -- shared instances like EMPTY and DEFAULT avoid re-allocation
  • Interop -- companion objects compile to Java static methods via @JvmStatic

Companion Object with Serialization

@Serializable
data class Config(
    val apiUrl: String,
    val timeout: Int,
    val retries: Int
) {
    companion object {
        val DEFAULT = Config(
            apiUrl = "https://api.example.com",
            timeout = 30,
            retries = 3
        )
    }
}

This pairs JSON deserialization with sensible defaults, useful for configuration objects that may partially exist in the JSON payload.

Use Case

In Android development, companion objects on data classes provide clean factory methods for creating instances from JSON, database cursors, or Intent bundles. This pattern centralizes construction logic and makes unit testing straightforward.

Try It — JSON to Kotlin

Open full tool