Circe

バリデーションライブラリScalaJSON関数型プログラミング型安全性純粋関数型パフォーマンス

GitHub概要

circe/circe

Yet another JSON library for Scala

スター2,521
ウォッチ61
フォーク544
作成日:2015年7月31日
言語:Scala
ライセンス:Apache License 2.0

トピックス

generic-derivationjsonscala

スター履歴

circe/circe Star History
データ取得日時: 2025/10/22 09:50

ライブラリ

Circe

概要

Circeは「A JSON library for Scala powered by Cats」として開発された、純粋関数型プログラミングに基づくScala向けJSONライブラリです。CatsエコシステムとTypeClassesを活用し、JSONのエンコード・デコードにおける型安全性とパフォーマンスを両立。JSONデータの処理とバリデーションを同時に行う強力な機能を提供し、関数型プログラミングのパラダイムに忠実な設計により、コンパイル時の型安全性とランタイムの堅牢性を実現します。

詳細

Circe(発音:サーシー、ギリシャ神話の魔女キルケーから由来)は2025年現在、Scala 3対応の最新版0.14.xがリリースされており、Scala関数型プログラミングエコシステムにおける事実上の標準JSONライブラリです。TypeClassベースの設計により、カスタムデータ型に対する自動的なエンコーダー・デコーダー生成を提供。JSONスキーマバリデーション、カスタムバリデーションロジック、エラーハンドリングを統合的に扱い、コンパイル時の型安全性を保証します。14k+のGitHubスターを獲得し、Play Framework、Akka HTTP、ZIOなどのScalaエコシステムで広く採用されています。

主な特徴

  • 型安全なJSONバリデーション: コンパイル時の型チェックによる安全なJSON処理
  • 関数型エラーハンドリング: AccumulatingエラーによるFailureの詳細な収集
  • 自動derivation: case classからのEncoder/Decoderの自動生成
  • カスタムバリデーション: 柔軟なバリデーションルールの定義と組み合わせ
  • 高いパフォーマンス: 効率的なJSON処理とメモリ使用量の最適化
  • Catsエコシステム統合: Monad、Applicative、Validateなどの関数型抽象化

メリット・デメリット

メリット

  • Scalaの型システムとシームレスな統合
  • 関数型プログラミングによる予測可能で安全なコード
  • 豊富なカスタマイズオプションと拡張性
  • コンパイル時の最適化によるランタイムパフォーマンス
  • エラーの詳細な情報と適切なエラーハンドリング
  • Playframework、Akka、ZIOとの優れた統合

デメリット

  • 関数型プログラミングの学習コストが高い
  • TypeClassesやCatsの理解が必要
  • 他のJVMライブラリとの相互運用性が限定的
  • デバッグ時のスタックトレースが複雑になる場合がある
  • JSONスキーマの動的生成が困難
  • 初期設定とセットアップの複雑さ

参考ページ

書き方の例

SBTプロジェクトのセットアップ

// build.sbt
ThisBuild / scalaVersion := "3.3.1"

val circeVersion = "0.14.6"

libraryDependencies ++= Seq(
  "io.circe" %% "circe-core" % circeVersion,
  "io.circe" %% "circe-generic" % circeVersion,
  "io.circe" %% "circe-parser" % circeVersion,
  "io.circe" %% "circe-refined" % circeVersion, // refinedTypes統合用
  "io.circe" %% "circe-shapes" % circeVersion, // より高度なgeneric derivation
  "io.circe" %% "circe-literal" % circeVersion % Test // リテラル補間用
)

// コンパイルオプション(Scala 3)
scalacOptions ++= Seq(
  "-deprecation",
  "-feature",
  "-unchecked",
  "-Wunused:all",
  "-Yexplicit-nulls"
)

Maven設定

<properties>
    <scala.version>3.3.1</scala.version>
    <circe.version>0.14.6</circe.version>
</properties>

<dependencies>
    <dependency>
        <groupId>io.circe</groupId>
        <artifactId>circe-core_3</artifactId>
        <version>${circe.version}</version>
    </dependency>
    <dependency>
        <groupId>io.circe</groupId>
        <artifactId>circe-generic_3</artifactId>
        <version>${circe.version}</version>
    </dependency>
    <dependency>
        <groupId>io.circe</groupId>
        <artifactId>circe-parser_3</artifactId>
        <version>${circe.version}</version>
    </dependency>
</dependencies>

基本的なJSONバリデーション

import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._
import cats.syntax.either._

// ドメインモデルの定義
case class User(
  id: Long,
  name: String,
  email: String,
  age: Int
)

// 基本的なJSON解析とバリデーション
object BasicValidation {
  
  def parseUser(jsonString: String): Either[Error, User] = {
    decode[User](jsonString)
  }
  
  // 使用例
  def main(args: Array[String]): Unit = {
    val validJson = """
      {
        "id": 1,
        "name": "田中太郎",
        "email": "[email protected]",
        "age": 30
      }
    """
    
    val invalidJson = """
      {
        "id": "invalid",
        "name": "",
        "email": "invalid-email",
        "age": -1
      }
    """
    
    // 正常なケース
    parseUser(validJson) match {
      case Right(user) => println(s"バリデーション成功: $user")
      case Left(error) => println(s"バリデーション失敗: $error")
    }
    
    // エラーケース
    parseUser(invalidJson) match {
      case Right(user) => println(s"バリデーション成功: $user")
      case Left(error) => println(s"バリデーション失敗: $error")
    }
  }
}

カスタムバリデーションルール

import io.circe._
import io.circe.generic.semiauto._
import cats.data.Validated
import cats.syntax.validated._
import cats.syntax.apply._

// バリデーション済みのドメインオブジェクト
case class ValidatedUser(
  id: UserId,
  name: UserName,
  email: Email,
  age: Age
)

// 値オブジェクトによる型安全性
case class UserId(value: Long) extends AnyVal
case class UserName(value: String) extends AnyVal
case class Email(value: String) extends AnyVal
case class Age(value: Int) extends AnyVal

// カスタムバリデーション
object UserValidation {
  
  type ValidationResult[A] = Validated[List[String], A]
  
  // 個別フィールドのバリデーション
  def validateUserId(id: Long): ValidationResult[UserId] = {
    if (id > 0) UserId(id).valid
    else List("ユーザーIDは正の値である必要があります").invalid
  }
  
  def validateUserName(name: String): ValidationResult[UserName] = {
    if (name.nonEmpty && name.length <= 50) UserName(name).valid
    else List("ユーザー名は1文字以上50文字以下である必要があります").invalid
  }
  
  def validateEmail(email: String): ValidationResult[Email] = {
    val emailRegex = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$""".r
    if (emailRegex.matches(email)) Email(email).valid
    else List("有効なメールアドレス形式である必要があります").invalid
  }
  
  def validateAge(age: Int): ValidationResult[Age] = {
    if (age >= 0 && age <= 150) Age(age).valid
    else List("年齢は0以上150以下である必要があります").invalid
  }
  
  // 全体のバリデーション(Applicativeによるエラー蓄積)
  def validateUser(
    id: Long,
    name: String,
    email: String,
    age: Int
  ): ValidationResult[ValidatedUser] = {
    (
      validateUserId(id),
      validateUserName(name),
      validateEmail(email),
      validateAge(age)
    ).mapN(ValidatedUser.apply)
  }
  
  // JSONからのカスタムデコーダー
  implicit val validatedUserDecoder: Decoder[ValidatedUser] = {
    (c: HCursor) =>
      for {
        id <- c.downField("id").as[Long]
        name <- c.downField("name").as[String]
        email <- c.downField("email").as[String]
        age <- c.downField("age").as[Int]
        validated <- validateUser(id, name, email, age).toEither.leftMap { errors =>
          DecodingFailure(s"バリデーションエラー: ${errors.mkString(", ")}", c.history)
        }
      } yield validated
  }
}

複雑なJSONスキーマとネストされたバリデーション

import io.circe._
import io.circe.generic.auto._
import cats.data.{NonEmptyList, Validated}
import cats.syntax.validated._
import cats.syntax.traverse._
import cats.instances.list._

// 複雑なドメインモデル
case class Company(
  id: Long,
  name: String,
  address: Address,
  employees: List[Employee],
  metadata: Map[String, String]
)

case class Address(
  country: String,
  prefecture: String,
  city: String,
  streetAddress: String,
  postalCode: String
)

case class Employee(
  id: Long,
  name: String,
  email: String,
  department: Department,
  salary: Option[BigDecimal],
  joinDate: java.time.LocalDate
)

case class Department(
  id: Long,
  name: String,
  budget: BigDecimal
)

object ComplexValidation {
  
  type ValidationResult[A] = Validated[NonEmptyList[String], A]
  
  // 住所のバリデーション
  def validateAddress(address: Address): ValidationResult[Address] = {
    val countryValidation = if (address.country.nonEmpty) address.country.valid 
      else "国名は必須です".invalidNel
    
    val prefectureValidation = if (address.prefecture.nonEmpty) address.prefecture.valid
      else "都道府県は必須です".invalidNel
    
    val cityValidation = if (address.city.nonEmpty) address.city.valid
      else "市区町村は必須です".invalidNel
    
    val streetValidation = if (address.streetAddress.nonEmpty) address.streetAddress.valid
      else "住所は必須です".invalidNel
    
    val postalCodeValidation = {
      val postalRegex = """^\d{3}-\d{4}$""".r
      if (postalRegex.matches(address.postalCode)) address.postalCode.valid
      else "郵便番号はXXX-XXXX形式である必要があります".invalidNel
    }
    
    (countryValidation, prefectureValidation, cityValidation, streetValidation, postalCodeValidation)
      .mapN((_, _, _, _, _) => address)
  }
  
  // 部署のバリデーション
  def validateDepartment(dept: Department): ValidationResult[Department] = {
    val idValidation = if (dept.id > 0) dept.id.valid
      else "部署IDは正の値である必要があります".invalidNel
    
    val nameValidation = if (dept.name.nonEmpty) dept.name.valid
      else "部署名は必須です".invalidNel
    
    val budgetValidation = if (dept.budget > 0) dept.budget.valid
      else "予算は正の値である必要があります".invalidNel
    
    (idValidation, nameValidation, budgetValidation)
      .mapN((_, _, _) => dept)
  }
  
  // 従業員のバリデーション
  def validateEmployee(emp: Employee): ValidationResult[Employee] = {
    val idValidation = if (emp.id > 0) emp.id.valid
      else "従業員IDは正の値である必要があります".invalidNel
    
    val nameValidation = if (emp.name.nonEmpty && emp.name.length <= 100) emp.name.valid
      else "従業員名は1文字以上100文字以下である必要があります".invalidNel
    
    val emailValidation = {
      val emailRegex = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$""".r
      if (emailRegex.matches(emp.email)) emp.email.valid
      else "有効なメールアドレス形式である必要があります".invalidNel
    }
    
    val departmentValidation = validateDepartment(emp.department)
    
    val salaryValidation = emp.salary match {
      case Some(salary) if salary > 0 => emp.salary.valid
      case Some(_) => "給与は正の値である必要があります".invalidNel
      case None => emp.salary.valid
    }
    
    val joinDateValidation = {
      val now = java.time.LocalDate.now()
      val tenYearsAgo = now.minusYears(10)
      if (emp.joinDate.isAfter(tenYearsAgo) && emp.joinDate.isBefore(now.plusDays(1))) {
        emp.joinDate.valid
      } else {
        "入社日は過去10年以内かつ未来日ではない必要があります".invalidNel
      }
    }
    
    (idValidation, nameValidation, emailValidation, departmentValidation, salaryValidation, joinDateValidation)
      .mapN((_, _, _, dept, _, _) => emp.copy(department = dept))
  }
  
  // 会社全体のバリデーション
  def validateCompany(company: Company): ValidationResult[Company] = {
    val idValidation = if (company.id > 0) company.id.valid
      else "会社IDは正の値である必要があります".invalidNel
    
    val nameValidation = if (company.name.nonEmpty) company.name.valid
      else "会社名は必須です".invalidNel
    
    val addressValidation = validateAddress(company.address)
    
    val employeesValidation = company.employees.traverse(validateEmployee)
    
    val metadataValidation = {
      if (company.metadata.size <= 20) company.metadata.valid
      else "メタデータは20項目以下である必要があります".invalidNel
    }
    
    (idValidation, nameValidation, addressValidation, employeesValidation, metadataValidation)
      .mapN((_, _, addr, emps, meta) => company.copy(address = addr, employees = emps, metadata = meta))
  }
  
  // カスタムデコーダーの定義
  implicit val companyDecoder: Decoder[Company] = deriveDecoder[Company].validate { company =>
    validateCompany(company).toEither.leftMap { errors =>
      NonEmptyList.one(DecodingFailure(s"バリデーションエラー: ${errors.toList.mkString(", ")}", List.empty))
    }
  }
}

エラーハンドリングとカスタムエラーメッセージ

import io.circe._
import io.circe.syntax._
import cats.data.{NonEmptyList, Validated}
import cats.syntax.either._

// カスタムエラー型
sealed trait ValidationError
case class FieldError(field: String, message: String) extends ValidationError
case class BusinessRuleError(rule: String, message: String) extends ValidationError
case class StructuralError(message: String) extends ValidationError

object ErrorHandling {
  
  type ValidationResult[A] = Validated[NonEmptyList[ValidationError], A]
  
  // カスタムエラーハンドラー
  class ValidationErrorHandler {
    
    def handleFieldError(field: String, message: String): ValidationError = {
      FieldError(field, message)
    }
    
    def handleBusinessRule(rule: String, message: String): ValidationError = {
      BusinessRuleError(rule, message)
    }
    
    def handleStructuralError(message: String): ValidationError = {
      StructuralError(message)
    }
    
    // エラーの日本語メッセージ生成
    def toJapaneseMessage(error: ValidationError): String = error match {
      case FieldError(field, message) => s"フィールド '$field': $message"
      case BusinessRuleError(rule, message) => s"ビジネスルール '$rule': $message"
      case StructuralError(message) => s"構造エラー: $message"
    }
    
    // エラーレスポンスの生成
    def createErrorResponse(errors: NonEmptyList[ValidationError]): Json = {
      val errorMessages = errors.map(toJapaneseMessage).toList
      Json.obj(
        "status" -> "error".asJson,
        "message" -> "バリデーションエラーが発生しました".asJson,
        "errors" -> errorMessages.asJson,
        "timestamp" -> java.time.Instant.now().toString.asJson
      )
    }
  }
  
  // 高度なユーザーバリデーション
  case class AdvancedUser(
    id: Long,
    username: String,
    email: String,
    profile: UserProfile,
    preferences: UserPreferences
  )
  
  case class UserProfile(
    firstName: String,
    lastName: String,
    birthDate: java.time.LocalDate,
    phoneNumber: Option[String]
  )
  
  case class UserPreferences(
    language: String,
    timezone: String,
    notifications: NotificationSettings
  )
  
  case class NotificationSettings(
    email: Boolean,
    push: Boolean,
    sms: Boolean
  )
  
  class AdvancedUserValidator(errorHandler: ValidationErrorHandler) {
    
    def validateId(id: Long): ValidationResult[Long] = {
      if (id > 0) Validated.valid(id)
      else Validated.invalidNel(errorHandler.handleFieldError("id", "正の値である必要があります"))
    }
    
    def validateUsername(username: String): ValidationResult[String] = {
      val usernameRegex = """^[a-zA-Z0-9_]{3,20}$""".r
      if (usernameRegex.matches(username)) Validated.valid(username)
      else Validated.invalidNel(errorHandler.handleFieldError("username", "3-20文字の英数字とアンダースコアのみ使用可能です"))
    }
    
    def validateEmail(email: String): ValidationResult[String] = {
      val emailRegex = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$""".r
      if (emailRegex.matches(email)) Validated.valid(email)
      else Validated.invalidNel(errorHandler.handleFieldError("email", "有効なメールアドレス形式である必要があります"))
    }
    
    def validateProfile(profile: UserProfile): ValidationResult[UserProfile] = {
      val firstNameValidation = if (profile.firstName.nonEmpty) Validated.valid(profile.firstName)
        else Validated.invalidNel(errorHandler.handleFieldError("firstName", "名前は必須です"))
      
      val lastNameValidation = if (profile.lastName.nonEmpty) Validated.valid(profile.lastName)
        else Validated.invalidNel(errorHandler.handleFieldError("lastName", "姓は必須です"))
      
      val birthDateValidation = {
        val now = java.time.LocalDate.now()
        val eighteenYearsAgo = now.minusYears(18)
        val hundredYearsAgo = now.minusYears(100)
        
        if (profile.birthDate.isBefore(eighteenYearsAgo) && profile.birthDate.isAfter(hundredYearsAgo)) {
          Validated.valid(profile.birthDate)
        } else {
          Validated.invalidNel(errorHandler.handleBusinessRule("age_restriction", "18歳以上100歳以下である必要があります"))
        }
      }
      
      val phoneValidation = profile.phoneNumber match {
        case Some(phone) =>
          val phoneRegex = """^0\d{1,4}-\d{1,4}-\d{4}$""".r
          if (phoneRegex.matches(phone)) Validated.valid(profile.phoneNumber)
          else Validated.invalidNel(errorHandler.handleFieldError("phoneNumber", "日本の電話番号形式である必要があります"))
        case None => Validated.valid(profile.phoneNumber)
      }
      
      (firstNameValidation, lastNameValidation, birthDateValidation, phoneValidation)
        .mapN((_, _, _, _) => profile)
    }
    
    def validatePreferences(prefs: UserPreferences): ValidationResult[UserPreferences] = {
      val supportedLanguages = Set("ja", "en", "ko", "zh")
      val supportedTimezones = Set("Asia/Tokyo", "UTC", "America/New_York", "Europe/London")
      
      val languageValidation = if (supportedLanguages.contains(prefs.language)) {
        Validated.valid(prefs.language)
      } else {
        Validated.invalidNel(errorHandler.handleFieldError("language", s"サポートされている言語: ${supportedLanguages.mkString(", ")}"))
      }
      
      val timezoneValidation = if (supportedTimezones.contains(prefs.timezone)) {
        Validated.valid(prefs.timezone)
      } else {
        Validated.invalidNel(errorHandler.handleFieldError("timezone", s"サポートされているタイムゾーン: ${supportedTimezones.mkString(", ")}"))
      }
      
      val notificationValidation = {
        if (prefs.notifications.email || prefs.notifications.push || prefs.notifications.sms) {
          Validated.valid(prefs.notifications)
        } else {
          Validated.invalidNel(errorHandler.handleBusinessRule("notification_required", "少なくとも1つの通知方法を選択する必要があります"))
        }
      }
      
      (languageValidation, timezoneValidation, notificationValidation)
        .mapN((_, _, _) => prefs)
    }
    
    def validateAdvancedUser(user: AdvancedUser): ValidationResult[AdvancedUser] = {
      (
        validateId(user.id),
        validateUsername(user.username),
        validateEmail(user.email),
        validateProfile(user.profile),
        validatePreferences(user.preferences)
      ).mapN((_, _, _, _, _) => user)
    }
  }
  
  // カスタムデコーダーでのエラーハンドリング
  implicit val advancedUserDecoder: Decoder[AdvancedUser] = {
    val errorHandler = new ValidationErrorHandler()
    val validator = new AdvancedUserValidator(errorHandler)
    
    Decoder.instance { cursor =>
      for {
        rawUser <- Decoder[AdvancedUser].apply(cursor)
        validatedUser <- validator.validateAdvancedUser(rawUser).toEither.leftMap { errors =>
          DecodingFailure(errorHandler.createErrorResponse(errors).spaces2, cursor.history)
        }
      } yield validatedUser
    }
  }
}

パフォーマンス最適化とメモリ効率

import io.circe._
import io.circe.generic.semiauto._
import io.circe.streaming._
import cats.effect.IO
import fs2.Stream

// 大容量データの効率的な処理
object PerformanceOptimization {
  
  // 軽量なバリデーション専用データ型
  case class LightweightUser(
    id: Long,
    email: String,
    status: String
  )
  
  // 最適化されたデコーダー(不要なフィールドをスキップ)
  implicit val lightweightUserDecoder: Decoder[LightweightUser] = {
    Decoder.instance { cursor =>
      for {
        id <- cursor.downField("id").as[Long]
        email <- cursor.downField("email").as[String]
        status <- cursor.downField("status").as[String]
      } yield LightweightUser(id, email, status)
    }
  }
  
  // ストリーミング処理によるメモリ効率化
  def processLargeJsonStream(jsonStream: Stream[IO, String]): Stream[IO, Either[Error, LightweightUser]] = {
    jsonStream
      .through(stringStreamParser)
      .through(decoder[IO, LightweightUser])
  }
  
  // バッチバリデーション(並列処理)
  def validateUsersBatch(users: List[LightweightUser]): IO[List[Either[String, LightweightUser]]] = {
    import cats.effect.implicits._
    
    users.parTraverse { user =>
      IO.pure {
        validateLightweightUser(user).toEither.leftMap(_.mkString(", "))
      }
    }
  }
  
  def validateLightweightUser(user: LightweightUser): cats.data.Validated[List[String], LightweightUser] = {
    import cats.syntax.validated._
    import cats.syntax.apply._
    
    val idValidation = if (user.id > 0) user.id.valid else List("IDが無効です").invalid
    val emailValidation = if (user.email.contains("@")) user.email.valid else List("メールアドレスが無効です").invalid
    val statusValidation = if (Set("active", "inactive", "pending").contains(user.status)) {
      user.status.valid
    } else {
      List("ステータスが無効です").invalid
    }
    
    (idValidation, emailValidation, statusValidation).mapN((_, _, _) => user)
  }
  
  // キャッシュ機能付きバリデーター
  class CachedValidator {
    private val cache = collection.mutable.Map[String, Either[Error, LightweightUser]]()
    
    def validateWithCache(jsonString: String): Either[Error, LightweightUser] = {
      cache.getOrElseUpdate(jsonString, {
        decode[LightweightUser](jsonString)
      })
    }
    
    def clearCache(): Unit = cache.clear()
    def cacheSize: Int = cache.size
  }
  
  // プロファイリング用のメトリクス
  case class ValidationMetrics(
    totalValidations: Long,
    successfulValidations: Long,
    failedValidations: Long,
    averageProcessingTime: Double
  ) {
    def successRate: Double = if (totalValidations > 0) successfulValidations.toDouble / totalValidations else 0.0
    def failureRate: Double = if (totalValidations > 0) failedValidations.toDouble / totalValidations else 0.0
  }
  
  class MetricsCollector {
    private var metrics = ValidationMetrics(0, 0, 0, 0.0)
    private var totalTime = 0L
    
    def recordValidation[T](validation: => Either[Error, T]): Either[Error, T] = {
      val startTime = System.nanoTime()
      val result = validation
      val endTime = System.nanoTime()
      
      val processingTime = endTime - startTime
      totalTime += processingTime
      
      result match {
        case Right(_) =>
          metrics = metrics.copy(
            totalValidations = metrics.totalValidations + 1,
            successfulValidations = metrics.successfulValidations + 1,
            averageProcessingTime = totalTime.toDouble / metrics.totalValidations
          )
        case Left(_) =>
          metrics = metrics.copy(
            totalValidations = metrics.totalValidations + 1,
            failedValidations = metrics.failedValidations + 1,
            averageProcessingTime = totalTime.toDouble / metrics.totalValidations
          )
      }
      
      result
    }
    
    def getMetrics: ValidationMetrics = metrics
    def resetMetrics(): Unit = {
      metrics = ValidationMetrics(0, 0, 0, 0.0)
      totalTime = 0L
    }
  }
}

フレームワーク統合(Play Framework、Akka HTTP等)

// Play Framework統合
import play.api.mvc._
import play.api.libs.json.{Json => PlayJson}
import io.circe.parser._
import cats.data.Validated

class UserController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
  
  def createUser(): Action[AnyContent] = Action { implicit request =>
    request.body.asText match {
      case Some(jsonString) =>
        decode[ValidatedUser](jsonString) match {
          case Right(user) =>
            // ユーザー作成処理
            Ok(PlayJson.obj("status" -> "success", "user" -> user.asJson))
          case Left(error) =>
            BadRequest(PlayJson.obj("status" -> "error", "message" -> error.getMessage))
        }
      case None =>
        BadRequest(PlayJson.obj("status" -> "error", "message" -> "JSONデータが必要です"))
    }
  }
  
  def validateUserBatch(): Action[AnyContent] = Action { implicit request =>
    request.body.asText match {
      case Some(jsonString) =>
        decode[List[User]](jsonString) match {
          case Right(users) =>
            val validationResults = users.map { user =>
              UserValidation.validateUser(user.id, user.name, user.email, user.age) match {
                case Validated.Valid(validUser) => 
                  PlayJson.obj("status" -> "valid", "user" -> validUser.asJson)
                case Validated.Invalid(errors) =>
                  PlayJson.obj("status" -> "invalid", "errors" -> errors.toList)
              }
            }
            Ok(PlayJson.obj("results" -> validationResults))
          case Left(error) =>
            BadRequest(PlayJson.obj("status" -> "error", "message" -> error.getMessage))
        }
      case None =>
        BadRequest(PlayJson.obj("status" -> "error", "message" -> "JSONデータが必要です"))
    }
  }
}

// Akka HTTP統合
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model.{ContentTypes, HttpEntity, StatusCodes}
import akka.http.scaladsl.server.Route

object UserRoutes {
  
  def routes: Route = {
    pathPrefix("api" / "users") {
      concat(
        post {
          entity(as[String]) { jsonString =>
            decode[ValidatedUser](jsonString) match {
              case Right(user) =>
                complete(StatusCodes.OK, HttpEntity(ContentTypes.`application/json`, user.asJson.spaces2))
              case Left(error) =>
                val errorJson = Json.obj("error" -> error.getMessage.asJson)
                complete(StatusCodes.BadRequest, HttpEntity(ContentTypes.`application/json`, errorJson.spaces2))
            }
          }
        },
        get {
          parameters("id".as[Long]) { id =>
            // ユーザー取得処理(バリデーション含む)
            if (id > 0) {
              val userJson = Json.obj("id" -> id.asJson, "message" -> "ユーザーが見つかりました".asJson)
              complete(StatusCodes.OK, HttpEntity(ContentTypes.`application/json`, userJson.spaces2))
            } else {
              val errorJson = Json.obj("error" -> "無効なユーザーIDです".asJson)
              complete(StatusCodes.BadRequest, HttpEntity(ContentTypes.`application/json`, errorJson.spaces2))
            }
          }
        }
      )
    }
  }
}

// ZIO統合
import zio._
import zio.json._

object ZIOIntegration {
  
  case class ZIOUser(id: Long, name: String, email: String)
  
  implicit val zioUserEncoder: JsonEncoder[ZIOUser] = DeriveJsonEncoder.gen[ZIOUser]
  implicit val zioUserDecoder: JsonDecoder[ZIOUser] = DeriveJsonDecoder.gen[ZIOUser]
  
  def validateZIOUser(jsonString: String): ZIO[Any, String, ZIOUser] = {
    for {
      user <- ZIO.fromEither(jsonString.fromJson[ZIOUser].left.map(_.getMessage))
      validated <- if (user.id > 0 && user.name.nonEmpty && user.email.contains("@")) {
        ZIO.succeed(user)
      } else {
        ZIO.fail("バリデーションエラー: 無効なユーザーデータです")
      }
    } yield validated
  }
  
  def processUsersBatch(jsonStrings: List[String]): ZIO[Any, Nothing, List[Either[String, ZIOUser]]] = {
    ZIO.foreachPar(jsonStrings) { jsonString =>
      validateZIOUser(jsonString).either
    }
  }
}