

[“슈퍼 마리오 갤럭시"
,"악마는 프라다를 입는다 2"
,”살목지"
,”프로젝트 헤일메리"
,"짱구"
,”왕과 사는 남자"
,"란 12.3"
,"내 이름은"
,"사랑의 하츄핑 특별판"
,"극장판 반짝반짝 달님이: 싱어롱 파티”]

https://codebeautify.org/jsonviewer
Best JSON Viewer and JSON Beautifier Online
Online JSON Viewer, JSON Beautifier and Formatter to beautify and tree view of JSON data - It works as JSON Pretty Print to pretty print JSON data.
codebeautify.org
open api를 해석하는 방법
import Foundation
// MARK: - Root
struct BoxOfficeResponse: Codable {
let boxOfficeResult: BoxOfficeResult
}
// MARK: - BoxOfficeResult
struct BoxOfficeResult: Codable {
let boxofficeType: String
let showRange: String
let dailyBoxOfficeList: [DailyBoxOffice]
}
// MARK: - DailyBoxOffice
struct DailyBoxOffice: Codable {
let rnum: String // 순번
let rank: String // 해당일자 순위
let rankInten: String // 전일 대비 순위 변동분
let rankOldAndNew: String // 신규진입여부 (NEW / OLD)
let movieCd: String // 영화코드
let movieNm: String // 영화명
let openDt: String // 개봉일
let salesAmt: String // 해당일 매출액
let salesShare: String // 해당일 점유율
let salesInten: String // 전일 대비 매출액 증감분
let salesChange: String // 전일 대비 매출액 증감 비율
let salesAcc: String // 누적 매출액
let audiCnt: String // 해당일 관객수
let audiInten: String // 전일 대비 관객수 증감분
let audiChange: String // 전일 대비 관객수 증감 비율
let audiAcc: String // 누적 관객수
let scrnCnt: String // 해당일 상영 스크린수
let showCnt: String // 해당일 상영횟수
}
숫자 필드를 적절한 타입으로 변환하고 싶다면 아래처럼 Int / Double로 선언하고 CodingKeys로 매핑할 수 있습니다.
// MARK: - DailyBoxOffice (타입 변환 버전)
struct DailyBoxOffice: Codable {
let rnum: String
let rank: Int
let rankInten: Int // 음수 가능
let rankOldAndNew: RankStatus
let movieCd: String
let movieNm: String
let openDt: String
let salesAmt: Int
let salesShare: Double
let salesInten: Int // 음수 가능
let salesChange: Double // 음수 가능
let salesAcc: Int
let audiCnt: Int
let audiInten: Int // 음수 가능
let audiChange: Double // 음수 가능
let audiAcc: Int
let scrnCnt: Int
let showCnt: Int
}
// MARK: - RankStatus
enum RankStatus: String, Codable {
case new = "NEW"
case old = "OLD"
}
사용 예시
let jsonData = json.data(using: .utf8)!
let decoder = JSONDecoder()
do {
let result = try decoder.decode(BoxOfficeResponse.self, from: jsonData)
let list = result.boxOfficeResult.dailyBoxOfficeList
list.forEach { print("\($0.rank)위: \($0.movieNm)") }
} catch {
print("Decoding error: \(error)")
}
JSON의 모든 숫자값이 String 형태로 전달되고 있어서, 서버 스펙이 확실하다면 타입 변환 버전을 쓰되 String으로 디코딩 후 직접 변환하는 방법도 안전합니다.
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let boxOfficeResult: BoxOfficeResult
}
// MARK: - BoxOfficeResult
struct BoxOfficeResult: Codable {
let boxofficeType, showRange: String
let dailyBoxOfficeList: [DailyBoxOfficeList]
}
// MARK: - DailyBoxOfficeList
struct DailyBoxOfficeList: Codable {
let rnum, rank, rankInten: String
let rankOldAndNew: RankOldAndNew
let movieCD, movieNm, openDt, salesAmt: String
let salesShare, salesInten, salesChange, salesAcc: String
let audiCnt, audiInten, audiChange, audiAcc: String
let scrnCnt, showCnt: String
enum CodingKeys: String, CodingKey {
case rnum, rank, rankInten, rankOldAndNew
case movieCD = "movieCd"
case movieNm, openDt, salesAmt, salesShare, salesInten, salesChange, salesAcc, audiCnt, audiInten, audiChange, audiAcc, scrnCnt, showCnt
}
}
enum RankOldAndNew: String, Codable {
case old = "OLD"
}
Kotlin (Data Class)
import com.google.gson.annotations.SerializedName
// Root
data class BoxOfficeResponse(
@SerializedName("boxOfficeResult")
val boxOfficeResult: BoxOfficeResult
)
// BoxOfficeResult
data class BoxOfficeResult(
@SerializedName("boxofficeType")
val boxofficeType: String,
@SerializedName("showRange")
val showRange: String,
@SerializedName("dailyBoxOfficeList")
val dailyBoxOfficeList: List<DailyBoxOffice>
)
// DailyBoxOffice
data class DailyBoxOffice(
@SerializedName("rnum")
val rnum: String, // 순번
@SerializedName("rank")
val rank: String, // 순위
@SerializedName("rankInten")
val rankInten: String, // 순위 변동분
@SerializedName("rankOldAndNew")
val rankOldAndNew: String, // 신규진입여부 (NEW / OLD)
@SerializedName("movieCd")
val movieCd: String, // 영화코드
@SerializedName("movieNm")
val movieNm: String, // 영화명
@SerializedName("openDt")
val openDt: String, // 개봉일
@SerializedName("salesAmt")
val salesAmt: String, // 매출액
@SerializedName("salesShare")
val salesShare: String, // 매출 점유율
@SerializedName("salesInten")
val salesInten: String, // 매출액 증감분
@SerializedName("salesChange")
val salesChange: String, // 매출액 증감 비율
@SerializedName("salesAcc")
val salesAcc: String, // 누적 매출액
@SerializedName("audiCnt")
val audiCnt: String, // 관객수
@SerializedName("audiInten")
val audiInten: String, // 관객수 증감분
@SerializedName("audiChange")
val audiChange: String, // 관객수 증감 비율
@SerializedName("audiAcc")
val audiAcc: String, // 누적 관객수
@SerializedName("scrnCnt")
val scrnCnt: String, // 스크린수
@SerializedName("showCnt")
val showCnt: String // 상영횟수
)
// 사용 예시 (Gson)
// val response = Gson().fromJson(jsonString, BoxOfficeResponse::class.java)
// val list = response.boxOfficeResult.dailyBoxOfficeList
// list.forEach { println("${it.rank}위: ${it.movieNm}") }
Java (POJO + Gson)
import com.google.gson.annotations.SerializedName;
import java.util.List;
// Root
public class BoxOfficeResponse {
@SerializedName("boxOfficeResult")
private BoxOfficeResult boxOfficeResult;
public BoxOfficeResult getBoxOfficeResult() { return boxOfficeResult; }
public void setBoxOfficeResult(BoxOfficeResult boxOfficeResult) {
this.boxOfficeResult = boxOfficeResult;
}
}
// BoxOfficeResult
public class BoxOfficeResult {
@SerializedName("boxofficeType")
private String boxofficeType;
@SerializedName("showRange")
private String showRange;
@SerializedName("dailyBoxOfficeList")
private List<DailyBoxOffice> dailyBoxOfficeList;
public String getBoxofficeType() { return boxofficeType; }
public void setBoxofficeType(String boxofficeType) { this.boxofficeType = boxofficeType; }
public String getShowRange() { return showRange; }
public void setShowRange(String showRange) { this.showRange = showRange; }
public List<DailyBoxOffice> getDailyBoxOfficeList() { return dailyBoxOfficeList; }
public void setDailyBoxOfficeList(List<DailyBoxOffice> list) { this.dailyBoxOfficeList = list; }
}
// DailyBoxOffice
public class DailyBoxOffice {
@SerializedName("rnum")
private String rnum; // 순번
@SerializedName("rank")
private String rank; // 순위
@SerializedName("rankInten")
private String rankInten; // 순위 변동분
@SerializedName("rankOldAndNew")
private String rankOldAndNew; // 신규진입여부 (NEW / OLD)
@SerializedName("movieCd")
private String movieCd; // 영화코드
@SerializedName("movieNm")
private String movieNm; // 영화명
@SerializedName("openDt")
private String openDt; // 개봉일
@SerializedName("salesAmt")
private String salesAmt; // 매출액
@SerializedName("salesShare")
private String salesShare; // 매출 점유율
@SerializedName("salesInten")
private String salesInten; // 매출액 증감분
@SerializedName("salesChange")
private String salesChange; // 매출액 증감 비율
@SerializedName("salesAcc")
private String salesAcc; // 누적 매출액
@SerializedName("audiCnt")
private String audiCnt; // 관객수
@SerializedName("audiInten")
private String audiInten; // 관객수 증감분
@SerializedName("audiChange")
private String audiChange; // 관객수 증감 비율
@SerializedName("audiAcc")
private String audiAcc; // 누적 관객수
@SerializedName("scrnCnt")
private String scrnCnt; // 스크린수
@SerializedName("showCnt")
private String showCnt; // 상영횟수
// Getters & Setters
public String getRnum() { return rnum; }
public void setRnum(String rnum) { this.rnum = rnum; }
public String getRank() { return rank; }
public void setRank(String rank) { this.rank = rank; }
public String getRankInten() { return rankInten; }
public void setRankInten(String rankInten) { this.rankInten = rankInten; }
public String getRankOldAndNew() { return rankOldAndNew; }
public void setRankOldAndNew(String rankOldAndNew) { this.rankOldAndNew = rankOldAndNew; }
public String getMovieCd() { return movieCd; }
public void setMovieCd(String movieCd) { this.movieCd = movieCd; }
public String getMovieNm() { return movieNm; }
public void setMovieNm(String movieNm) { this.movieNm = movieNm; }
public String getOpenDt() { return openDt; }
public void setOpenDt(String openDt) { this.openDt = openDt; }
public String getSalesAmt() { return salesAmt; }
public void setSalesAmt(String salesAmt) { this.salesAmt = salesAmt; }
public String getSalesShare() { return salesShare; }
public void setSalesShare(String salesShare) { this.salesShare = salesShare; }
public String getSalesInten() { return salesInten; }
public void setSalesInten(String salesInten) { this.salesInten = salesInten; }
public String getSalesChange() { return salesChange; }
public void setSalesChange(String salesChange) { this.salesChange = salesChange; }
public String getSalesAcc() { return salesAcc; }
public void setSalesAcc(String salesAcc) { this.salesAcc = salesAcc; }
public String getAudiCnt() { return audiCnt; }
public void setAudiCnt(String audiCnt) { this.audiCnt = audiCnt; }
public String getAudiInten() { return audiInten; }
public void setAudiInten(String audiInten) { this.audiInten = audiInten; }
public String getAudiChange() { return audiChange; }
public void setAudiChange(String audiChange) { this.audiChange = audiChange; }
public String getAudiAcc() { return audiAcc; }
public void setAudiAcc(String audiAcc) { this.audiAcc = audiAcc; }
public String getScrnCnt() { return scrnCnt; }
public void setScrnCnt(String scrnCnt) { this.scrnCnt = scrnCnt; }
public String getShowCnt() { return showCnt; }
public void setShowCnt(String showCnt) { this.showCnt = showCnt; }
}
// 사용 예시
// BoxOfficeResponse response = new Gson().fromJson(jsonString, BoxOfficeResponse.class);
// List<DailyBoxOffice> list = response.getBoxOfficeResult().getDailyBoxOfficeList();
// for (DailyBoxOffice movie : list) {
// System.out.println(movie.getRank() + "위: " + movie.getMovieNm());
// }
Python (dataclass + dacite)
from dataclasses import dataclass
from typing import List
import json
@dataclass
class DailyBoxOffice:
rnum: str # 순번
rank: str # 순위
rankInten: str # 순위 변동분
rankOldAndNew: str # 신규진입여부 (NEW / OLD)
movieCd: str # 영화코드
movieNm: str # 영화명
openDt: str # 개봉일
salesAmt: str # 매출액
salesShare: str # 매출 점유율
salesInten: str # 매출액 증감분
salesChange: str # 매출액 증감 비율
salesAcc: str # 누적 매출액
audiCnt: str # 관객수
audiInten: str # 관객수 증감분
audiChange: str # 관객수 증감 비율
audiAcc: str # 누적 관객수
scrnCnt: str # 스크린수
showCnt: str # 상영횟수
@dataclass
class BoxOfficeResult:
boxofficeType: str
showRange: str
dailyBoxOfficeList: List[DailyBoxOffice]
@dataclass
class BoxOfficeResponse:
boxOfficeResult: BoxOfficeResult
# 사용 예시 (dacite 사용)
# pip install dacite
from dacite import from_dict
with open("boxoffice.json", encoding="utf-8") as f:
raw = json.load(f)
response = from_dict(data_class=BoxOfficeResponse, data=raw)
for movie in response.boxOfficeResult.dailyBoxOfficeList:
print(f"{movie.rank}위: {movie.movieNm}")
# dacite 없이 순수하게 파싱하려면:
def parse_response(data: dict) -> BoxOfficeResponse:
result = data["boxOfficeResult"]
movies = [DailyBoxOffice(**item) for item in result["dailyBoxOfficeList"]]
box_result = BoxOfficeResult(
boxofficeType=result["boxofficeType"],
showRange=result["showRange"],
dailyBoxOfficeList=movies
)
return BoxOfficeResponse(boxOfficeResult=box_result)
언어별 핵심 비교
| 모델 형태 | data class | POJO | @dataclass |
| JSON 직렬화 | Gson / Moshi | Gson / Jackson | json + dacite |
| 보일러플레이트 | 최소 | 많음 | 적음 |
| Null 안전성 | 컴파일 타임 | 런타임 | 런타임 |








//
// ViewController.swift
// MovieLJY
//
// Created by comsoft on 2026/05/04.
//
import UIKit
let name = ["1:슈퍼 마리오 갤럭시"
,"2:악마는 프라다를 입는다 2"
,"3:살목지"
,"4:프로젝트 헤일메리"
,"5:짱구"
,"6:왕과 사는 남자"
,"7:란 12.3"
,"8:내 이름은"
,"9:사랑의 하츄핑 특별판"
,"10:극장판 반짝반짝 달님이: 싱어롱 파티"]
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
@IBOutlet weak var table: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
cell.moiveName.text = name[indexPath.row]
print(indexPath.description)
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 5
}
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self
table.dataSource = self
}
}
ViewController.swift 코드 설명
전체 구조 한눈에 보기
ViewController
├── UITableViewDelegate (테이블 동작/이벤트 처리)
└── UITableViewDataSource (테이블 데이터 제공)
1. 전역 데이터
let name = ["1:슈퍼 마리오 갤럭시", "2:악마는 프라다를 입는다 2", ...]
- 영화 이름 10개를 담은 문자열 배열
- class 밖에 선언된 전역 상수라 앱 어디서든 접근 가능
2. Protocol 채택
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
| UIViewController | 화면(View) 기본 기능 |
| UITableViewDelegate | 셀 선택, 높이 등 동작 처리 |
| UITableViewDataSource | 셀 개수, 셀 내용 등 데이터 제공 |
3. IBOutlet
@IBOutlet weak var table: UITableView!
- 스토리보드의 TableView와 코드를 연결한 변수
- weak → 메모리 순환 참조 방지
4. DataSource 메서드
섹션 수
func numberOfSections(in tableView: UITableView) -> Int {
return 5 // 섹션 5개
}
행(Row) 수
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10 // 각 섹션마다 10행
}
⚠️ 주의: 섹션 5개 × 행 10개 = 총 50개 셀이 생성되는데,
name 배열은 10개뿐이라 indexPath.row가 0~9를 반복하므로
의도한 동작인지 확인이 필요합니다.
셀 구성
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
cell.moiveName.text = name[indexPath.row]
print(indexPath.description)
return cell
}
| dequeueReusableCell | 셀을 재사용해서 메모리 절약 |
| "myCell" | 스토리보드에서 지정한 셀 Identifier |
| as! MyTableViewCell | 커스텀 셀 클래스로 강제 캐스팅 |
| cell.moiveName.text | 셀 안의 Label에 영화 이름 표시 |
| print(indexPath.description) | 몇 번째 섹션, 몇 번째 행인지 콘솔 출력 |
5. viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self
table.dataSource = self
}
- 화면이 메모리에 로드될 때 최초 1회 실행
- 테이블뷰의 delegate와 dataSource를 자기 자신(ViewController) 으로 지정
⚠️ 개선이 필요한 부분
// 1. name 배열 크기와 행 수를 하드코딩 대신 연동 권장
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return name.count // 10 대신 name.count 사용
}
// 2. 오타 수정
cell.moiveName.text → cell.movieName.text // "moive" → "movie"
// 3. 섹션이 5개면 각 섹션별로 다른 데이터를 보여줄 의도인지 확인 필요