JSON to Swift 변환기는 샘플 JSON을 읽어 Codable을 따르는 struct 선언을 만들고, 프로퍼티는 let으로 둡니다("let 대신 var 사용" 옵션을 켜면 var). 중첩 객체는 각각 구조체가 되고 키는 camelCase 프로퍼티 이름이 됩니다. 키가 하나라도 바뀌면 그 구조체의 모든 프로퍼티를 적은 CodingKeys 열거형이 추가됩니다.
같은 위치에 있는 객체는 합쳐집니다. 객체 배열이면 모든 원소의 키를 합친 구조체 하나가 만들어지고, 일부 원소에 없거나 일부에서 null인 키는 String? 같은 옵셔널 프로퍼티가 됩니다. 루트는 객체와 배열 모두 가능하고 "루트 타입 이름" 옵션(기본값 Payload)이 이름을 정합니다. 파일은 import Foundation으로 시작합니다.
변환은 브라우저 안에서 이루어지고 서버로 전송되지 않습니다. 입력 한도는 20 MiB입니다. 타입은 샘플에서 나오므로 샘플 하나로는 키가 항상 있다는 것을 증명할 수 없고, 모든 샘플에서 null인 키는 타입 정보가 없어 JSONValue가 됩니다.
사용 방법
JSON 붙여넣기
객체나 배열을 붙여넣습니다. 최상위 배열은 받아서
typealias를 만들고, 문자열, 숫자, 불리언 단일 값은 거부됩니다.루트 이름과 가변성 정하기
Payload가 싫다면 "루트 타입 이름"을 바꿉니다. PascalCase로 바뀌고 숫자로 시작하면Type접두사가 붙습니다. 프로퍼티를 바꿀 수 있어야 한다면 "let 대신 var 사용"을 켭니다.변환 실행
출력은
import Foundation으로 시작해 루트 구조체, 이어서 처음 만난 순서대로 중첩 구조체가 나옵니다.JSONValue열거형은 어떤 위치에 필요할 때만 맨 끝에 붙습니다.CodingKeys 확인
키가 하나라도 바뀐 구조체에는 모든 프로퍼티를 적은
CodingKeys가 붙습니다. 이름이 그대로인 프로퍼티는 일반 케이스로, 바뀐 프로퍼티는 JSON 키와 함께 적힙니다.Swift 코드 복사
.swift파일에 붙여넣고try JSONDecoder().decode(Payload.self, from: data)로 디코딩합니다.
작동 원리
JSON과 Swift 타입의 대응
Codable에는 임의의 JSON을 담는 기본 타입이 없어서, 모든 샘플에서 null이거나 모양이 섞이거나 빈 배열에서 온 위치가 있으면 도구가 직접 만든 indirect enum JSONValue: Codable을 뒤에 붙입니다. 필요한 곳이 없으면 이 열거형은 쓰지 않습니다.
절댓값 2^63 미만인 정수 표기는 Int, 소수점·지수 표기와 소수 값 및 2^63 이상의 정수는 Double입니다. 한 위치에 둘이 있으면 Double로 합쳐집니다. null이거나 없는 키는 옵셔널이 됩니다.
| JSON 값 | Swift 타입 | 비고 |
|---|---|---|
| string | String | |
| true / false | Bool | |
| 2^63 미만의 정수 표기 | Int | 현재 애플 플랫폼에서는 64비트 |
| 소수, 또는 정수와 소수가 섞인 값 | Double | |
| 타입과 null이 함께 있는 값 | T? | 예: String? |
| 일부 객체에 없는 키 | T? | 자동 합성 Codable이 decodeIfPresent를 씁니다 |
| 모든 샘플에서 null, 또는 섞인 모양 | JSONValue | JSONValue 열거형이 추가되며 키가 빠질 수 있을 때만 옵셔널 |
| 객체 | struct 이름: Codable | let 프로퍼티, 옵션을 켜면 var |
| 배열 | [T] | 중첩 배열은 [[Int]], 원소가 null일 수 있으면 [Int?] |
| 빈 배열 | [JSONValue] | 배울 원소가 없음 |
| 최상위 배열 | typealias Payload = [PayloadItem] | 구조체들보다 앞에 나옴 |
프로퍼티 이름과 CodingKeys
프로퍼티 이름은 camelCase 경계와 영문자·숫자가 아닌 문자에서 나눈 뒤 lowerCamelCase로 잇습니다. first_name은 firstName입니다. 숫자로 시작하면 field가 붙고, class, default, func, init, in, self 같은 Swift 키워드에는 Value가 붙습니다. Type 키는 type으로 정규화되어 그대로 유지됩니다. 배열 원소 구조체는 키의 단수형을 쓰고, 이름이 겹치면 2부터 숫자가 붙습니다.
Swift의 자동 합성 Codable은 열거형이 있으면 모든 저장 프로퍼티가 CodingKeys에 있어야 합니다. 그래서 생성기는 키가 하나라도 바뀌면 그 구조체의 모든 프로퍼티에 케이스를 쓰고, 바뀐 키가 없으면 열거형을 아예 쓰지 않습니다.
enum CodingKeys: String, CodingKey {
case id
case firstName = "first_name"
}JSONDecoder로 디코딩하기
let payload = try JSONDecoder().decode(Payload.self, from: data)로 구조체를 디코딩합니다. 단순한 snake_case 키라면 decoder.keyDecodingStrategy = .convertFromSnakeCase를 설정하고 CodingKeys 없이 쓸 수도 있습니다.
옵셔널이 아닌 프로퍼티는 필수 키라서 키가 없으면 keyNotFound, null이 오면 valueNotFound가 던져집니다. 옵셔널 프로퍼티는 둘 다 받아 줍니다. 자동 합성 디코딩은 옵셔널 JSONValue? 프로퍼티의 JSON null을 .null이 아니라 nil로 만든다는 점에 주의하세요.
실제 예시
{"id":42,"user":{"name":"Ada","active":true},"roles":["admin","editor"],"ratio":0.5}import Foundation
struct Payload: Codable {
let id: Int
let user: PayloadUser
let roles: [String]
let ratio: Double
}
struct PayloadUser: Codable {
let name: String
let active: Bool
}객체마다 Codable 구조체가 루트부터 만들어집니다. 이름이 바뀐 키가 없으면 CodingKeys 열거형은 만들어지지 않고 ratio는 Double입니다.
{"first_name":"Ada","class":"A","in":"x","2fa":true,"age":36}import Foundation
struct Payload: Codable {
let firstName: String
let classValue: String
let inValue: String
let field2fa: Bool
let age: Int
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case classValue = "class"
case inValue = "in"
case field2fa = "2fa"
case age
}
}프로퍼티는 camelCase입니다. class와 in에는 Value 접미사가, 숫자로 시작하는 이름에는 field 접두사가 붙고, 키가 하나라도 바뀌었으므로 age를 포함한 모든 프로퍼티가 CodingKeys에 적힙니다.
{"deleted_at":null,"tags":[],"mixed":[1,"a"],"scores":[1,null,3]}import Foundation
struct Payload: Codable {
let deletedAt: JSONValue
let tags: [JSONValue]
let mixed: [JSONValue]
let scores: [Int?]
enum CodingKeys: String, CodingKey {
case deletedAt = "deleted_at"
case tags
case mixed
case scores
}
}
indirect enum JSONValue: Codable {
case string(String)
case number(Double)
case bool(Bool)
case object([String: JSONValue])
case array([JSONValue])
case null
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() { self = .null; return }
if let value = try? container.decode(Bool.self) { self = .bool(value); return }
if let value = try? container.decode(Double.self) { self = .number(value); return }
if let value = try? container.decode(String.self) { self = .string(value); return }
if let value = try? container.decode([String: JSONValue].self) { self = .object(value); return }
if let value = try? container.decode([JSONValue].self) { self = .array(value); return }
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported JSON value")
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let value): try container.encode(value)
case .number(let value): try container.encode(value)
case .bool(let value): try container.encode(value)
case .object(let value): try container.encode(value)
case .array(let value): try container.encode(value)
case .null: try container.encodeNil()
}
}
}null만 있는 키, 빈 배열, 섞인 배열에는 JSONValue가 필요하므로 열거형이 한 번 붙습니다. 정수 사이에 null이 있는 배열은 [Int?]로 남습니다.
[{"id":1,"name":"Ada"},{"id":2,"email":"grace@example.com"}]import Foundation
typealias Payload = [PayloadItem]
struct PayloadItem: Codable {
let id: Int
let name: String?
let email: String?
}원소들이 PayloadItem으로 합쳐집니다. name과 email은 한쪽 원소에만 있어 옵셔널이고 typealias Payload가 배열 이름을 정합니다.
{"id":1,"user":{"name":"Ada"}}import Foundation
struct Payload: Codable {
var id: Int
var user: PayloadUser
}
struct PayloadUser: Codable {
var name: String
}let 대신 var 사용을 켜면 모든 프로퍼티가 var로 선언됩니다.
자주 하는 실수
null만 있는 키는 JSONValue가 됩니다
붙여넣은 모든 객체에서
deleted_at이 null이면 타입을 알 수 없어 프로퍼티가JSONValue가 되고 도우미 열거형이 붙습니다. 값이 들어 있는 레코드를 붙여넣거나 실제 옵셔널 타입으로 직접 바꾸세요.샘플 하나로는 키가 항상 있다고 증명할 수 없습니다
붙여넣은 모든 원소에 있는 키는 옵셔널이 아니게 생성되고, 그 키가 없는 레코드를 디코딩하면
keyNotFound가 던져집니다. 선택 키가 빠진 레코드를 포함해 여러 건을 붙여넣으면 그 키들이 옵셔널이 됩니다.Int와 Double은 샘플에 따라 달라집니다
Swift 출력은 원문의 숫자 표기를 보존합니다.
19.0과1e3은Double, 정수 토큰19는Int로 추론됩니다. Codable이 의도한 숫자 타입을 받도록 API가 보내는 표기를 샘플에 넣으세요.섞인 모양은 JSONValue가 됩니다
한 키가 어떤 원소에서는 문자열이고 다른 원소에서는 숫자인 경우나 타입이 섞인 배열은
JSONValue나[JSONValue]가 됩니다. 구체적인 타입 대신 케이스를 switch로 분기해야 합니다.큰 정수는 변환 전에 정밀도를 잃습니다
타입 추론에서 2^53을 넘는 값을 JavaScript 숫자로 바꾸면 반올림될 수 있으므로 64비트 ID는
Int로 나와도 샘플 값이 부정확할 수 있습니다. 정확한 ID는 문자열로 입력하고 API가 허용하면String으로 디코딩하세요.구조체는 Equatable이나 Hashable이 아닙니다
출력은
Codable만 따릅니다.Equatable,Hashable,Sendable이 필요하면 직접 추가하세요.JSONValue도 Codable만 따릅니다.
자주 묻는 질문
클래스인가요, 구조체인가요?
Codable을 따르는 구조체이고, 프로퍼티는 기본적으로 let이며 "let 대신 var 사용" 옵션을 켜면 var입니다.
출력에 왜 JSONValue 열거형이 있나요?
Codable에는 임의의 JSON 타입이 없습니다. 모든 샘플에서 null인 키, 섞인 모양, 빈 배열이 있으면 어떤 JSON 값이든 디코딩하는 작은 재귀 열거형을 붙입니다. 필요 없으면 쓰지 않습니다.
최상위 배열을 붙여넣어도 되나요?
네. 원소들이 구조체 하나로 합쳐지고 typealias Payload = [PayloadItem] 같은 typealias가 배열 이름을 정합니다.
CodingKeys에 모든 프로퍼티가 들어가나요?
네. 키가 하나라도 바뀌면 그 구조체의 모든 프로퍼티가 열거형에 적힙니다. 자동 합성 Codable이 전부 필요로 하기 때문입니다. 바뀐 키가 없는 구조체에는 열거형이 없습니다.
선택 키는 어떻게 처리되나요?
배열의 일부 객체에 없거나 일부에서 null인 키는 String? 같은 옵셔널 프로퍼티가 되어 키가 없어도 null이어도 디코딩됩니다.
CodingKeys 없이 snake_case 키를 처리하려면?
디코더에 keyDecodingStrategy = .convertFromSnakeCase를 설정하고 프로퍼티를 camelCase로 쓰세요.
루트 이름은 어떻게 바꾸나요?
"루트 타입 이름"을 바꾸면 됩니다. 기본값은 Payload이고 PayloadUser 같은 중첩 구조체 이름도 거기서 만들어집니다.
JSON이 업로드되나요?
아니요. 브라우저에서만 처리됩니다.
마지막 업데이트:
