JSON은 구조화된 데이터를 텍스트로 표현하는 데이터 교환 형식이다.
프론트엔드는 JavaScript 객체를 사용하고, 백엔드는 Java 객체를 사용한다. 서로 다른 언어로 작성된 두 프로그램이 데이터를 주고받으려면 양쪽에서 이해할 수 있는 공통 형식이 필요하다.
이때 API에서 가장 흔하게 사용하는 형식이 JSON이다.

RFC 8259에서는 JSON을 가볍고, 텍스트 기반이며, 언어에 독립적인 데이터 교환 형식으로 설명한다. 특정 프로그래밍 언어에서만 사용하는 형식이 아니라, 서로 다른 시스템이 데이터를 주고받을 때 사용할 수 있는 공통 형식이라는 의미다.
MDN에서도 JSON을 데이터 교환 형식으로 설명한다. 이름과 문법은 JavaScript에서 시작됐지만, JavaScript에서만 사용할 수 있는 것은 아니다. Java, Python, Kotlin, Go 같은 여러 언어에서도 JSON을 읽고 쓸 수 있다.
JSON은 다음과 같은 값을 표현할 수 있다.
문자열
숫자
boolean
null
배열
객체
Spring MVC는 HTTP 요청과 응답의 body를 읽고 쓰기 위해 HttpMessageConverter를 사용한다.
그중 MappingJackson2HttpMessageConverter는 Jackson의 ObjectMapper를 사용해 JSON과 Java 객체 사이의 변환을 처리한다.
Spring Boot에서 Jackson이 기본으로 구성된 환경이라면 @RequestBody가 붙은 매개변수로 JSON 요청을 Java 객체로 받을 수 있다. 반대로 @RestController가 Java 객체를 반환하면 해당 객체를 JSON 응답으로 변환할 수 있다.
JSON이라는 이름에는 JavaScript가 들어간다. 문법도 JavaScript 객체와 비슷하게 생겼다.
{
"id": 1,
"name": "junior"
}
이 때문에 처음에는 JSON과 JavaScript 객체를 같은 것으로 생각하기 쉽다.
하지만 JSON은 JavaScript 객체 그 자체가 아니다. JSON은 구조화된 데이터를 일정한 규칙에 따라 텍스트로 표현한 형식이다.
서버에서 다음과 같은 JSON 응답을 보냈다고 생각해보자.
{
"id": 1,
"name": "junior"
}
프론트엔드는 이 JSON을 파싱해 JavaScript에서 사용할 수 있는 값으로 변환한다.
const user = await response.json();
console.log(user.name);
이 예제에서는 JSON의 최상위 구조가 객체이므로 user도 JavaScript 객체가 된다.
다만 JSON의 최상위 값이 항상 객체인 것은 아니다. 배열도 JSON 값이 될 수 있다.
["USER", "ADMIN"]
숫자, 문자열, boolean, null도 JSON 값으로 표현할 수 있다.
true
따라서 JSON을 무조건 JavaScript 객체로 변환한다고 이해하기보다는, JSON을 JavaScript에서 사용할 수 있는 값으로 파싱한다고 이해하는 편이 정확하다.

백엔드도 마찬가지다. JSON 요청이 들어오면 Spring MVC와 Jackson이 JSON 데이터를 읽어 Java 객체로 변환한다.
JSON은 특정 언어의 객체가 아니라, 서로 다른 프로그램이 데이터를 주고받기 위해 사용하는 공통 데이터 형식이다.
JSON은 택배 송장 양식과 비슷하다.
택배를 보내려면 보내는 사람, 택배 회사, 받는 사람이 모두 알아볼 수 있는 일정한 형식이 필요하다.
받는 사람: 이준
주소: 서울시 ...
전화번호: 010-...
정해진 양식이 있으면 서로 다른 사람들이 같은 정보를 이해할 수 있다.
JSON도 비슷하다.
{
"receiver": "이준",
"address": "서울시 ...",
"phone": "010-..."
}
프론트엔드가 JavaScript로 작성되어 있고 백엔드가 Java로 작성되어 있어도, 양쪽 모두 같은 JSON 구조를 읽을 수 있다.
API에서는 보통 다음과 같은 흐름으로 데이터를 주고받는다.
프론트엔드
-> JSON 요청
-> 백엔드
-> JSON 응답
-> 프론트엔드
프론트엔드는 JavaScript 객체를 JSON 형식으로 변환해 전송한다.
백엔드는 전달받은 JSON을 Java DTO로 변환해 사용한다.
반대로 백엔드가 Java 객체를 반환하면 이를 JSON 응답으로 변환해 프론트엔드로 전달한다.
회원 생성 API를 예로 들어보자.
프론트엔드는 다음과 같은 요청을 보낸다.
POST /users HTTP/1.1
Content-Type: application/json
Accept: application/json
{
"name": "junior",
"email": "junior@example.com"
}
Content-Type: application/json은 현재 보내는 요청 body가 JSON 형식이라는 뜻이다.
Accept: application/json은 클라이언트가 JSON 형식의 응답을 받을 수 있다는 뜻이다.
Spring Boot에서는 다음과 같이 요청을 받을 수 있다.
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserResponse createUser(
@RequestBody UserCreateRequest request
) {
return new UserResponse(
1L,
request.name(),
request.email()
);
}
}
요청 DTO는 다음과 같이 작성할 수 있다.
public record UserCreateRequest(
String name,
String email
) {
}
응답 DTO도 비슷한 방식으로 만들 수 있다.
public record UserResponse(
Long id,
String name,
String email
) {
}
요청이 들어오면 Spring MVC는 HTTP body를 읽고, Jackson을 통해 JSON을 UserCreateRequest 객체로 변환한다.
JSON 요청 body
-> HttpMessageConverter
-> Jackson
-> UserCreateRequest 객체

이 변환이 성공하려면 JSON 필드와 DTO의 구조 및 타입이 호환되어야 한다.
Controller가 UserResponse 객체를 반환하면 반대 방향의 변환이 일어난다.
UserResponse 객체
-> Jackson
-> JSON 응답 body
응답은 대략 다음과 같이 전달된다.
HTTP/1.1 201 Created
Content-Type: application/json
{
"id": 1,
"name": "junior",
"email": "junior@example.com"
}
이 과정 덕분에 백엔드 개발자는 HTTP body에 들어 있는 JSON을 직접 읽고 파싱하지 않고, Java 객체를 중심으로 코드를 작성할 수 있다.
Java 객체를 JSON으로 변환하는 과정을 직렬화라고 한다.
Java 객체
-> JSON
반대로 JSON을 Java 객체로 변환하는 과정은 역직렬화라고 한다.
JSON
-> Java 객체
Spring Boot에서는 일반적으로 Jackson이 이 작업을 처리한다.
요청을 받을 때는 JSON이 Java DTO로 역직렬화된다.
JSON
-> 역직렬화
-> Java DTO
응답을 보낼 때는 Java DTO가 JSON으로 직렬화된다.
Java DTO
-> 직렬화
-> JSON
용어는 조금 어렵게 느껴질 수 있지만, 결국 Java 객체와 JSON 사이를 서로 변환하는 과정이다.
{
"id": 1,
"name": "junior",
"active": true
}
필드 이름과 값을 직접 확인할 수 있어 데이터 구조를 이해하기 쉽다.
일반적인 API 데이터에서는 XML보다 반복되는 표기가 적어 비교적 간결하게 표현할 수 있다.
프론트엔드가 JavaScript를 사용하고, 백엔드가 Java를 사용하며, 배치 시스템이 Python을 사용해도 모두 JSON을 처리할 수 있다.
각 언어에서는 JSON을 자신이 사용하는 값이나 객체로 변환한다.
JavaScript
JSON -> JavaScript 객체 또는 값
Java
JSON -> DTO 또는 객체
Python
JSON -> dict 또는 list
JSON은 HTTP 요청과 응답 body에 구조화된 데이터를 담기에 적합하다.
Content-Type: application/json
헤더를 통해 body가 JSON 형식이라는 점도 명확하게 전달할 수 있다.
JSON은 객체와 배열을 조합할 수 있다.
{
"id": 1,
"name": "junior",
"roles": ["USER", "ADMIN"],
"profile": {
"age": 27,
"city": "Seoul"
}
}
배열과 중첩 객체를 사용할 수 있어 실제 API 요청과 응답에서 필요한 데이터를 표현하기 좋다.
다음과 같이 JSON 안에 주석을 넣으면 일반적인 JSON 문법에 맞지 않는다.
{
// 사용자 이름
"name": "junior"
}
API 예시에 설명을 추가해야 한다면 JSON 내부가 아니라 본문이나 별도의 설명 영역에 작성하는 것이 좋다.
올바른 JSON은 다음과 같다.
{
"name": "junior"
}
다음처럼 작은따옴표를 사용하면 올바른 JSON이 아니다.
{
'name': 'junior'
}
JavaScript 객체에서는 작은따옴표를 사용할 수 있지만, JSON의 필드 이름과 문자열에는 큰따옴표를 사용해야 한다.
다음 JSON은 올바르지 않다.
{
"id": 1,
"name": "junior",
}
마지막 필드 뒤의 쉼표를 제거해야 한다.
{
"id": 1,
"name": "junior"
}
JSON에는 날짜 전용 타입이 없다. 날짜와 시간도 문자열로 표현한다.
{
"createdAt": "2026-07-22T09:00:00+09:00"
}
UTC 시간을 사용한다면 다음과 같이 표현할 수 있다.
{
"createdAt": "2026-07-22T00:00:00Z"
}
생년월일처럼 시간대가 필요 없는 날짜는 날짜만 전달할 수도 있다.
{
"birthDate": "1996-01-07"
}
서버와 클라이언트가 같은 값을 해석할 수 있도록 날짜 형식과 시간대 기준을 미리 정해두는 것이 좋다.
JSON에는 숫자를 표현하는 문법이 있지만, 이를 읽는 언어마다 사용하는 숫자 타입과 표현 가능한 범위가 다르다.
예를 들어 JavaScript의 Number는 매우 큰 정수나 일부 소수를 정확하게 표현하지 못할 수 있다.
금액은 최소 화폐 단위를 정수로 전달할 수 있다.
{
"priceInWon": 10000
}
소수점 자릿수를 그대로 유지해야 한다면 문자열로 전달하는 규칙을 정할 수도 있다.
{
"price": "10000.00"
}
다만 문자열로 전달하면 수신 측에서 별도의 형식 검증과 숫자 변환이 필요하다.
null과 필드 누락은 의미가 다를 수 있다다음 JSON에는 nickname 필드가 있고 값이 null이다.
{
"nickname": null
}
다음 JSON에는 nickname 필드 자체가 없다.
{
}
API 설계에 따라 두 경우를 다르게 처리할 수 있다.
nickname 필드 없음
-> 기존 값을 변경하지 않음
nickname: null
-> 기존 값을 삭제함
특히 수정 API를 만들 때 null과 필드 누락을 어떻게 처리할지 명확하게 정해야 한다.
다음과 같이 규칙이 섞이면 API를 사용하는 쪽에서 혼란이 생긴다.
{
"user_id": 1,
"userName": "junior"
}
팀에서 camelCase를 사용할지 snake_case를 사용할지 정하고 일관되게 유지하는 것이 좋다.
{
"userId": 1,
"userName": "junior"
}
다음 요청은 JSON 문법에는 문제가 없다.
{
"name": "",
"email": "not-email"
}
Jackson이 이 JSON을 Java 객체로 변환할 수 있더라도, 이름이 비어 있거나 이메일 형식이 잘못된 요청을 그대로 처리해서는 안 될 수 있다.
Spring Boot에서는 Bean Validation을 사용해 값을 검증할 수 있다.
public record UserCreateRequest(
@NotBlank
String name,
@Email
@NotBlank
String email
) {
}
Controller에서는 @Valid를 붙여 검증을 실행할 수 있다.
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserResponse createUser(
@Valid @RequestBody UserCreateRequest request
) {
return new UserResponse(
1L,
request.name(),
request.email()
);
}
JSON을 Java 객체로 변환하는 것과 요청 값이 올바른지 확인하는 것은 서로 다른 과정이다.
JSON 파싱
-> Java 객체 변환
-> 요청 값 검증
-> 비즈니스 로직 실행
프론트엔드가 회원 생성 요청을 보내는 과정은 다음과 같다.
JavaScript 객체 생성
-> JSON으로 변환
-> HTTP 요청 body로 전송
-> Spring MVC가 요청 수신
-> Jackson이 Java DTO로 변환
-> Controller에서 DTO 사용
응답은 반대 방향으로 진행된다.
Controller가 Java DTO 반환
-> Jackson이 JSON으로 변환
-> HTTP 응답 body로 전송
-> 프론트엔드가 JSON 수신
-> JavaScript 값으로 파싱
JSON은 프론트엔드와 백엔드가 같은 데이터 구조를 이해할 수 있도록 중간에서 연결해주는 역할을 한다.
JSON은 백엔드 API에서 가장 자주 접하는 데이터 형식 중 하나다.
사람이 읽기 비교적 쉽고, 여러 프로그래밍 언어에서 처리할 수 있으며, HTTP 요청과 응답에 구조화된 데이터를 담기 좋다.
다만 JSON은 JavaScript 객체 자체가 아니다. 서로 다른 프로그램이 데이터를 주고받기 위해 사용하는 텍스트 기반 데이터 형식이다.
프론트엔드는 JSON을 JavaScript에서 사용할 수 있는 값으로 파싱하고, 백엔드는 JSON을 Java DTO나 객체로 변환해 사용한다.
Spring Boot에서는 Spring MVC의 HttpMessageConverter와 Jackson이 이 변환 과정을 처리한다.
나는 JSON을 다음과 같이 이해하고 있다.
JSON은 서로 다른 프로그램이 데이터를 오해 없이 주고받기 위해 사용하는 공통 문서 양식이다.
JSON 문법을 아는 것도 중요하지만, 실제 API를 설계할 때는 다음 내용을 함께 생각해야 한다.
누가 이 JSON을 보내는가?
누가 이 JSON을 읽는가?
필수 필드는 무엇인가?
필드가 없으면 어떻게 처리할 것인가?
null은 어떤 의미인가?
날짜와 시간대는 어떤 형식으로 전달할 것인가?
숫자의 범위와 정밀도는 안전한가?
필드 이름 규칙은 일관적인가?
잘못된 값은 어디에서 검증할 것인가?
이런 기준을 하나씩 정리하다 보면 JSON 문법을 아는 수준을 넘어, 사용하기 편하고 예측 가능한 API를 설계할 수 있게 된다.