JSON 코덱 (JSON Codec)

← 메인 | English →

API | 예제


개요

Sigilaris JSON 코덱은 Scala 데이터 구조를 JSON으로 인코딩/디코딩하는 경량의 라이브러리 독립적 추상화를 제공합니다. JSON은 해시값 계산과 관련된 크리티컬한 도메인이 아니라서(바이트 코덱과 달리), 외부 라이브러리를 어느 정도 의존해도 괜찮지만, 쉽게 외부 라이브러리를 바꿔치기할 수 있도록 하기 위해 최소한의 JsonValue ADT를 중간 표현으로 도입했습니다.

왜 커스텀 JSON 추상화인가?

주요 특징

빠른 시작 (30초)

import org.sigilaris.core.codec.json.*
import org.sigilaris.core.codec.json.backend.circe.CirceJsonOps

case class User(name: String, age: Int) derives JsonCodec

val user = User("Alice", 30)

// JsonValue로 인코딩
val jsonValue = JsonEncoder[User].encode(user)

// JSON 문자열로 출력 (Circe 백엔드 사용)
val jsonString = CirceJsonOps.print(jsonValue)

// JSON 문자열에서 파싱
val parsed = CirceJsonOps.parse(jsonString)
// User로 디코딩
parsed.flatMap(JsonDecoder[User].decode(_))
// res0: Either[SigilarisFailure, User] = Right(
//   value = User(name = "Alice", age = 30)
// )

이게 전부입니다! 코덱이 자동으로 인스턴스를 derivation하고, import를 변경하는 것만으로 JSON 백엔드를 교체할 수 있습니다.

문서

포함된 내용

코어 ADT

JsonValue enum은 6개 케이스로 구성:

타입클래스

백엔드 통합

자동 Derivation

import org.sigilaris.core.codec.json.*

case class Account(id: String, balance: BigDecimal) derives JsonCodec

sealed trait Status derives JsonCodec
case object Active extends Status
case object Inactive extends Status

case class UserAccount(
  username: String,
  account: Account,
  status: Status
) derives JsonCodec
val ua = UserAccount("alice", Account("acc-1", BigDecimal(100)), Active)
// ua: UserAccount = UserAccount(
//   username = "alice",
//   account = Account(id = "acc-1", balance = 100),
//   status = Active
// )
JsonEncoder[UserAccount].encode(ua)
// res2: JsonValue = JObject(
//   fields = Map(
//     "username" -> JString(value = "alice"),
//     "account" -> JObject(
//       fields = Map(
//         "id" -> JString(value = "acc-1"),
//         "balance" -> JString(value = "100")
//       )
//     ),
//     "status" -> JObject(fields = Map("Active" -> JObject(fields = Map())))
//   )
// )

설정 (Configuration)

JsonConfig로 인코딩/디코딩 동작 제어:

import org.sigilaris.core.codec.json.*

val config = JsonConfig(
  fieldNaming = FieldNamingPolicy.SnakeCase,      // firstName → first_name
  dropNullValues = true,                          // null 필드 생략
  treatAbsentAsNull = true,                       // 결측 → null (Option용)
  writeBigIntAsString = true,                     // BigInt → "123"
  writeBigDecimalAsString = false,                // BigDecimal → 123.45
  discriminator = DiscriminatorConfig(
    TypeNameStrategy.SimpleName                   // { "TypeName": {...} }
  )
)

필드 네이밍 정책

Discriminator 전략

Coproduct(sealed trait)는 wrapped-by-type-key 인코딩 사용:

sealed trait Color
case object Red extends Color
case object Blue extends Color

// 인코딩 결과: { "Red": {} } 또는 { "Blue": {} }

타입명 전략:

설계 철학

관심사의 분리

바이트 코덱(deterministic 해싱에 크리티컬)과 달리 JSON 인코딩은:

아키텍처는 다음을 보장합니다:

최소 의존성

코어 JsonValue ADT는 외부 JSON 라이브러리에 대한 의존성이 전혀 없습니다. 백엔드 어댑터는 별도 모듈이므로 다음이 쉽습니다:

예제: API 응답 인코딩

import org.sigilaris.core.codec.json.*
import java.time.Instant

case class Product(id: String, name: String, price: BigDecimal)
  derives JsonCodec

case class ApiResponse(
  data: Product,
  timestamp: Instant,
  status: String
) derives JsonCodec

val response = ApiResponse(
  data = Product("p-1", "Widget", BigDecimal("29.99")),
  timestamp = Instant.parse("2025-01-15T10:30:00Z"),
  status = "success"
)
val json = JsonEncoder[ApiResponse].encode(response)
// json: JsonValue = JObject(
//   fields = Map(
//     "data" -> JObject(
//       fields = Map(
//         "id" -> JString(value = "p-1"),
//         "name" -> JString(value = "Widget"),
//         "price" -> JString(value = "29.99")
//       )
//     ),
//     "timestamp" -> JString(value = "2025-01-15T10:30:00Z"),
//     "status" -> JString(value = "success")
//   )
// )

다음 단계

  1. API 레퍼런스: contramap, emap, 조합자 사용법 학습
  2. 예제: 설정 옵션과 고급 패턴 확인

한계 및 범위

성능 특성


← 메인 | English →

API | 예제