shared-models
Kotlin Multiplatformで動作する、データ検証とシリアライゼーション機能を備えた共通データモデルを構築し、様々なプラットフォームで利用できるドメインモデルを効率的に開発するSkill。
📜 元の英語説明(参考)
Shared data models for Kotlin Multiplatform using kotlinx.serialization. Cross-platform domain models with validation and serialization.
🇯🇵 日本人クリエイター向け解説
Kotlin Multiplatformで動作する、データ検証とシリアライゼーション機能を備えた共通データモデルを構築し、様々なプラットフォームで利用できるドメインモデルを効率的に開発するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o shared-models.zip https://jpskill.com/download/10680.zip && unzip -o shared-models.zip && rm shared-models.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/10680.zip -OutFile "$d\shared-models.zip"; Expand-Archive "$d\shared-models.zip" -DestinationPath $d -Force; ri "$d\shared-models.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
shared-models.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
shared-modelsフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
KMPのための共有モデル
shared/commonMain内のすべてのプラットフォームで動作するデータモデルを設計および実装します。
コア依存関係
// build.gradle.kts (shared module)
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:1.6.0")
}
}
}
シリアライゼーションプラグインを有効にします。
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization") version "1.9.20"
}
ドメインモデル
1. イミュータブルなデータクラス
// commonMain/kotlin/com/example/shared/model/User.kt
@Serializable
data class User(
val id: String,
val name: String,
val email: String,
val avatarUrl: String?,
val createdAt: Instant,
val lastActiveAt: Instant?
)
2. シールドされた階層
// commonMain/kotlin/com/example/shared/model/UiState.kt
@Serializable
sealed class UiState<out T> {
@Serializable
data object Loading : UiState<Nothing>()
@Serializable
data class Success<T>(val data: T) : UiState<T>()
@Serializable
data class Error(val message: String, val code: String? = null) : UiState<Nothing>()
}
// 型パラメータ付きでの使用例
@Serializable
sealed class HomeState {
@Serializable
data object Loading : HomeState()
@Serializable
data class Loaded(val user: User, val items: List<Item>) : HomeState()
@Serializable
data class Error(val message: String) : HomeState()
}
3. Resultラッパー
// commonMain/kotlin/com/example/shared/model/Result.kt
@Serializable
sealed class Result<out T> {
@Serializable
data class Success<T>(val data: T) : Result<T>()
@Serializable
data class Error(val code: String, val message: String) : Result<Nothing>()
}
// Kotlin Resultから変換するヘルパー
fun <T> Result<T>.toKotlinResult(): kotlin.Result<T> = when (this) {
is Result.Success -> kotlin.Result.success(data)
is Result.Error -> kotlin.Result.failure(RuntimeException("$code: $message"))
}
4. ページネーションされたレスポンス
// commonMain/kotlin/com/example/shared/model/Pagination.kt
@Serializable
data class PaginatedResponse<T>(
val items: List<T>,
val page: Int,
val pageSize: Int,
val totalPages: Int,
val totalItems: Long
) {
val hasMorePages: Boolean get() = page < totalPages
val nextPage: Int? get() = if (hasMorePages) page + 1 else null
}
// カーソルベースのページネーション用
@Serializable
data class CursorResponse<T>(
val items: List<T>,
val nextCursor: String?,
val hasMore: Boolean
)
5. リクエスト/レスポンスモデル
// commonMain/kotlin/com/example/shared/model/auth/AuthRequests.kt
@Serializable
data class LoginRequest(
val email: String,
val password: String
)
@Serializable
data class RegisterRequest(
val name: String,
val email: String,
val password: String
)
// commonMain/kotlin/com/example/shared/model/auth/AuthResponses.kt
@Serializable
data class AuthResponse(
val user: User,
val accessToken: String,
val refreshToken: String,
val expiresAt: Instant
)
@Serializable
data class RefreshTokenRequest(
val refreshToken: String
)
バリデーション
インラインバリデーション
// commonMain/kotlin/com/example/shared/model/Validation.kt
@Serializable
data class Email(val value: String) {
init {
require(value.contains("@")) { "Invalid email format" }
require(value.length > 5) { "Email too short" }
}
companion object {
fun of(value: String?): Email? {
return if (!value.isNullOrBlank()) Email(value) else null
}
}
}
@Serializable
data class PhoneNumber(val value: String) {
init {
require(value.matches(Regex("^\\+?[1-9]\\d{1,14}$"))) {
"Invalid phone number format"
}
}
}
バリデーション結果
// commonMain/kotlin/com/example/shared/model/ValidationError.kt
@Serializable
data class ValidationError(
val field: String,
val message: String
)
@Serializable
data class ValidationResult(
val isValid: Boolean,
val errors: List<ValidationError> = emptyList()
) {
companion object {
fun success() = ValidationResult(isValid = true)
fun failure(errors: List<ValidationError>) = ValidationResult(
isValid = false,
errors = errors
)
}
}
// モデルでの使用例
@Serializable
data class CreateUserRequest(
val name: String,
val email: String,
val age: Int?
) {
fun validate(): ValidationResult {
val errors = buildList {
if (name.isBlank()) {
add(ValidationError("name", "Name is required"))
}
if (email.isBlank() || !email.contains("@")) {
add(ValidationError("email", "Invalid email"))
}
if (age != null && age < 0) {
add(ValidationError("age", "Age cannot be negative"))
}
}
return if (errors.isEmpty()) ValidationResult.success()
else ValidationResult.failure(errors)
}
}
プラットフォーム固有のフィールド
シリアル名を使用する
// commonMain/kotlin/com/example/shared/model/PlatformData.kt
@Serializable
data class PlatformData(
val platform: Platform,
val deviceInfo: DeviceInfo
)
@Serializable
enum class Platform {
ANDROID,
IOS,
DESKTOP,
WEB
}
@Serializable
data class DeviceInfo(
val model: String,
val osVersion: String,
val appVersion: String,
// プラットフォーム固有のオプションフィールド
val pushToken: String? = null,
val advertisingId: String? = null
)
カスタムシリアライザ
// commonMain/kotlin/com/example/shared/model/InstantSerializer.kt
object InstantSerializer : KSerializer<Instant> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("Instant", PrimitiveKind.LONG)
override f 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Shared Models for KMP
Design and implement data models that work across all platforms in shared/commonMain.
Core Dependencies
// build.gradle.kts (shared module)
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:1.6.0")
}
}
}
Enable serialization plugin:
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization") version "1.9.20"
}
Domain Models
1. Immutable Data Classes
// commonMain/kotlin/com/example/shared/model/User.kt
@Serializable
data class User(
val id: String,
val name: String,
val email: String,
val avatarUrl: String?,
val createdAt: Instant,
val lastActiveAt: Instant?
)
2. Sealed Hierarchies
// commonMain/kotlin/com/example/shared/model/UiState.kt
@Serializable
sealed class UiState<out T> {
@Serializable
data object Loading : UiState<Nothing>()
@Serializable
data class Success<T>(val data: T) : UiState<T>()
@Serializable
data class Error(val message: String, val code: String? = null) : UiState<Nothing>()
}
// Usage with type parameter
@Serializable
sealed class HomeState {
@Serializable
data object Loading : HomeState()
@Serializable
data class Loaded(val user: User, val items: List<Item>) : HomeState()
@Serializable
data class Error(val message: String) : HomeState()
}
3. Result Wrapper
// commonMain/kotlin/com/example/shared/model/Result.kt
@Serializable
sealed class Result<out T> {
@Serializable
data class Success<T>(val data: T) : Result<T>()
@Serializable
data class Error(val code: String, val message: String) : Result<Nothing>()
}
// Helper to convert from Kotlin Result
fun <T> Result<T>.toKotlinResult(): kotlin.Result<T> = when (this) {
is Result.Success -> kotlin.Result.success(data)
is Result.Error -> kotlin.Result.failure(RuntimeException("$code: $message"))
}
4. Paginated Response
// commonMain/kotlin/com/example/shared/model/Pagination.kt
@Serializable
data class PaginatedResponse<T>(
val items: List<T>,
val page: Int,
val pageSize: Int,
val totalPages: Int,
val totalItems: Long
) {
val hasMorePages: Boolean get() = page < totalPages
val nextPage: Int? get() = if (hasMorePages) page + 1 else null
}
// For cursor-based pagination
@Serializable
data class CursorResponse<T>(
val items: List<T>,
val nextCursor: String?,
val hasMore: Boolean
)
5. Request/Response Models
// commonMain/kotlin/com/example/shared/model/auth/AuthRequests.kt
@Serializable
data class LoginRequest(
val email: String,
val password: String
)
@Serializable
data class RegisterRequest(
val name: String,
val email: String,
val password: String
)
// commonMain/kotlin/com/example/shared/model/auth/AuthResponses.kt
@Serializable
data class AuthResponse(
val user: User,
val accessToken: String,
val refreshToken: String,
val expiresAt: Instant
)
@Serializable
data class RefreshTokenRequest(
val refreshToken: String
)
Validation
Inline Validation
// commonMain/kotlin/com/example/shared/model/Validation.kt
@Serializable
data class Email(val value: String) {
init {
require(value.contains("@")) { "Invalid email format" }
require(value.length > 5) { "Email too short" }
}
companion object {
fun of(value: String?): Email? {
return if (!value.isNullOrBlank()) Email(value) else null
}
}
}
@Serializable
data class PhoneNumber(val value: String) {
init {
require(value.matches(Regex("^\\+?[1-9]\\d{1,14}$"))) {
"Invalid phone number format"
}
}
}
Validation Result
// commonMain/kotlin/com/example/shared/model/ValidationError.kt
@Serializable
data class ValidationError(
val field: String,
val message: String
)
@Serializable
data class ValidationResult(
val isValid: Boolean,
val errors: List<ValidationError> = emptyList()
) {
companion object {
fun success() = ValidationResult(isValid = true)
fun failure(errors: List<ValidationError>) = ValidationResult(
isValid = false,
errors = errors
)
}
}
// Usage in models
@Serializable
data class CreateUserRequest(
val name: String,
val email: String,
val age: Int?
) {
fun validate(): ValidationResult {
val errors = buildList {
if (name.isBlank()) {
add(ValidationError("name", "Name is required"))
}
if (email.isBlank() || !email.contains("@")) {
add(ValidationError("email", "Invalid email"))
}
if (age != null && age < 0) {
add(ValidationError("age", "Age cannot be negative"))
}
}
return if (errors.isEmpty()) ValidationResult.success()
else ValidationResult.failure(errors)
}
}
Platform-Specific Fields
Using Serial Names
// commonMain/kotlin/com/example/shared/model/PlatformData.kt
@Serializable
data class PlatformData(
val platform: Platform,
val deviceInfo: DeviceInfo
)
@Serializable
enum class Platform {
ANDROID,
IOS,
DESKTOP,
WEB
}
@Serializable
data class DeviceInfo(
val model: String,
val osVersion: String,
val appVersion: String,
// Platform-specific optional fields
val pushToken: String? = null,
val advertisingId: String? = null
)
Custom Serializers
// commonMain/kotlin/com/example/shared/model/InstantSerializer.kt
object InstantSerializer : KSerializer<Instant> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("Instant", PrimitiveKind.LONG)
override fun serialize(encoder: Encoder, value: Instant) {
encoder.encodeLong(value.toEpochMilliseconds())
}
override fun deserialize(decoder: Decoder): Instant {
return Instant.fromEpochMilliseconds(decoder.decodeLong())
}
}
@Serializable
data class Event(
val id: String,
@Serializable(with = InstantSerializer::class)
val timestamp: Instant
)
JSON Configuration
// commonMain/kotlin/com/example/shared/serialization/JsonFactory.kt
object JsonFactory {
val Default = Json {
ignoreUnknownKeys = true
isLenient = true
encodeDefaults = false
coerceInputValues = true
}
// Pretty printing for debug
val Pretty = Json {
ignoreUnknownKeys = true
isLenient = true
prettyPrint = true
indent = " "
}
// Strict parsing for API responses
val Strict = Json {
ignoreUnknownKeys = false
isLenient = false
encodeDefaults = false
coerceInputValues = false
}
}
File Organization
shared/commonMain/kotlin/com/example/shared/
├── model/
│ ├── User.kt
│ ├── Item.kt
│ ├── Pagination.kt
│ ├── UiState.kt
│ └── Result.kt
├── model/auth/
│ ├── AuthRequests.kt
│ ├── AuthResponses.kt
│ └── UserProfile.kt
├── serialization/
│ ├── JsonFactory.kt
│ └── InstantSerializer.kt
└── validation/
├── ValidationResult.kt
└── Validators.kt
Best Practices
✅ DO
// ✅ Use immutable data classes
@Serializable
data class User(val id: String, val name: String)
// ✅ Use sealed classes for fixed types
@Serializable
sealed class Result
// ✅ Provide default values for optional fields
@Serializable
data class Item(
val id: String,
val description: String? = null
)
// ✅ Use value classes for type safety
@JvmInline
@Serializable
value class UserId(val value: String)
// ✅ Group related models in packages
model/
auth/
payment/
social/
❌ DON'T
// ❌ Don't use platform-specific types
@Serializable
data class Event(val date: Date) // Date is platform-specific
// Use Instant or LocalDateTime instead
// ❌ Don't include complex logic in models
@Serializable
data class User(val id: String) {
// Heavy business logic doesn't belong here
fun calculateSomethingComplex(): Int { ... }
}
// ❌ Don't make everything nullable
@Serializable
data class Item(
val id: String?,
val name: String?,
val price: Double?
) // Use Optional pattern or separate fields
// ❌ Don't use var in data classes
@Serializable
data class User(var name: String) // Use val for immutability
Testing
// commonTest/kotlin/ModelTest.kt
class ModelTest {
@Test
fun `serialize and deserialize user`() {
val user = User(
id = "123",
name = "John Doe",
email = "john@example.com",
avatarUrl = null,
createdAt = Clock.System.now(),
lastActiveAt = null
)
val json = JsonFactory.Default.encodeToString(user)
val restored = JsonFactory.Default.decodeFromString<User>(json)
assertEquals(user, restored)
}
@Test
fun `validation catches invalid email`() {
val result = CreateUserRequest(
name = "John",
email = "not-an-email",
age = null
).validate()
assertFalse(result.isValid)
assertTrue(result.errors.any { it.field == "email" })
}
}
Remember: Shared models are your contract between platforms. Keep them simple, immutable, and focused on data.