본문 바로가기
비개발자를 위한 개발 상식

개발자와 대화할 때 필수 개념, JSON 데이터 다루는 방법 간단 이해!

by keynotion 2023. 10. 22.
반응형

본 글을 포함하여, JSON, YAML, XML과 관련한 네 개의 글은 "학교에서 알려주지 않는 17가지 실무 개발 기술 - 이기곤, 한빛미디어" 를 이해한 후 살을 덧붙여 재구성한 내용임을 밝힌다. 비개발자 관점에서 꼭 필요한 최소 기본 개념을 요약하여 전달하고자 한다.

지난 글에서 프로그래밍의 가장 첫 수업인 "자료형" 에 대해서 아주아주 간단하게 소개했다. 또한 이 자료들을 담는 그릇인 JSON에 대해서 기본 개념을 소개했는데, 기초적이지만 매우 중요한 내용이므로 궁금한 사람은 이전 포스팅을 참고하자.

본 글에서는 JSON을 실제로 다룰 때 어떤 점에 유의하면 좋을지를 소개하고자 한다.

 

JSON 메시지 읽고 쓰기

실무 개발 환경에서는 HTTP 요청 메시지의 문자열을 JSON으로 사용하는 경우가 많지만, 내용 이해를 위해 JSON 메시지 파일 예시를 살펴본다. 먼저, 위 json 파일을 파이썬의 json 라이브러리로 읽어 온 후 print하면 아래와 같이 출력된다.

{'number': 12345, 'pi': 3.14, 'str': 'text value', nulL_key': None, object': {'str2' : 'text value 2', 'object2': {' number2' : 12345}}, 'num_array': [1, 2, 3, 4, 5], 'str_array': ['one', 'two', 'three', 'four', 'five']}

 

이 JSON 객체에서 키와 값을 읽는 방법 (sample.json 파일을 읽는 예제 코드)
import json

def open_json_file(filename):
	with open(filename) as file:
	return json.load(file)

json_data = open_json_file('sample.json')

# 정수
num_value = json_data['number']

# 실수
float_value = json_data['pi']

# 문자열
str_value = json_data['str']

# 존재하지 않는 키
empty_value = json_data['abcdef']

print('A={0}'.format(num_value))
print('B={0}'.format(float_value))
print('C={0}'.format(str_value))
print('D={0}'.format(empty_value))
결과
A=12345
B=3.14
C=text value
D=None

키 값이 없는 경우, None, null, nil로 표기되는 것이 원칙이며, 라이브러리에 따라 false, 0, “” 등 기본 값을 표기할 수도 있음

 

객체 안 객체를 읽을 때
json_data = open_json_file('sample.json')
json_data2 = json_data['object']

print('json_data[\\'object\\'][\\'str2\\']={0}.format(json_data2['str2']))
결과
jsont_data['object']['str2']=text value 2

 

배열을 읽을 때
json_array = json_data['num_array']

for n in json_array:
	print('n={0}'.format(n))
결과
n=1
n=2
n=3
n=4
n=5
  • 역직렬화: 파이썬, 자바, C#과 같은 고수준 언어에서 제공한 JSON 라이브러리에서, 읽어온 JSON 데이터를 클래스, 맵, 리스트 등과 같은 객체로 변환해 주는 기능
  • 직렬화: 역직렬화와 반대로, 클래스, 맵, 리스트 데이터를 JSON 문자열로 바꿔주는 기능

 

키 읽을 때 주의 사항

위 예제 코드에서는 키가 항상 존재하고, 키에 대응하는 값도 모두 존재하며, 전체적으로 올바른 형태의 데이터라고 가정했다. 그러나 실무 개발 환경에서는 버그, 잘못된 요청, 의도적인 데이터 변조 등 키 자체가 존재하지 않거나, 키가 있더라도 값이 없거나 예상치 않은 값이 나오는 경우가 많다. 실무에서는 이에 대한 예외 처리를 잘 해둬야 한다.

 

파이썬에서 존재하지 않는 키에 접근했을 때
unknown_value = json_data['unknown_key']

print('unknown_value={0}'.format(unknown_value))
결과
...
unknown_value = json_data['unknown_key']
KeyError: 'unknown_key'

파이썬, 자바, C#처럼 가상 환경에서 동작하는 언어들은 모두 위와 같은 예외가 발생한다. 또한 C, C++ 등과 같은 CPU에서 직접 실행되는 언어도 알 수 없는 예외, 메모리 오류를 발생한 다음 종료한다.

 

예외 제어 방법
  1. try-catch 사용하는 방법: 서버 내부 통신, 내부에서 사용하는 파일 등, 사전 협의된 JSON 키와 값일 때 유용
  2. 사용하는 모든 키가 존재하는지 검사하는 방법: 외부 HTTP 요청 또는 외부 업로드 파일 등, 예측 불가, 제어 불가한 외부 문자열에서 키를 읽을 때 유용함

키를 검사할 때는 키 존재 여부 외에도, 키에 대응하는 값이 올바른 형태인지도 함께 검사해야 한다. 키가 반드시 필요한지, 생략 가능한지, 기본 값은 어떻게 둘지, 설정이 잘 되는지도 검사해야 한다. 디버깅 환경에서만 동작하는 assert를 사용하여 올바른 JSON 형태인지, 필요 키가 모두 있는지 검사하면 좋다. 또한 JSON 객체와 배열을 읽을 때에는 객체 내부 또는 배열 요소가 알파벳 순서나 오름차순과 같은 순서로 정렬됐다는 가정을 하지 않는 것이 좋다. 왜냐하면 데이터를 전달하는 소프트웨어 로직이나 라이브러리에 의해 바뀔 수 있기 때문이다.

 

파이썬에서 JSON 파일 다루는 방법

프로그래밍 언어는 JSON 객체를 .ToString()과 같은 메서드를 사용해 문자열로 바꿔서 그 문자열을 파일에 쓰는 것도 가능하다. 일반적으로 프로그램에서 읽기만 하는 용도일 때는 JSON을 한 줄로 읽어오는 것이 상관 없으나, 디버깅 또는 보여주기 목적으로는 적합하지 않다.

파이썬은 Indent 인수를 사용하면 각 키마다 개행 문자(\n)를 자동으로 추가함

with open('message.json', 'w', encoding='UTF8') as file:

	# 기본 한 줄 출력
	json.dump(message, file, ensure_ascii=False)

	# 들여쓰기 추가
	json.dump(message, file, ensure_ascii=False, indent=2)

	# 들여쓰기 및 키 정렬 추가
	json.dump(message, file, ensure_ascii=False, indent=2, sort_keys=True)

 

JSON 파일 주의사항

실무 환경에서는 null을 사용하지 않는 것이 좋다. 실제로 그 키가 어떤 형태의 데이터를 담고 있는지 알 수 없기 때이다. 정수, 실수, 문자열, 객체, 배열 등 null 만으로는 알 수 없기 때문이다. 따라서 실제로 빈 값은 아래와 같이 명확히 메시지를 쓰는 것이 좋다.

  • 숫자(정수, 실수): “null_key”: 0
  • 문자열: “null_key”: “”
  • 객체: “null_key”: {}
  • 배열: “null_key”: []

 

JSON 키 이름 형식

키 이름에 대한 표준이나 관례는 없다. 대문자 시작, 언더바 사용, 소문자만 사용 등 여러 방법이 있으나 프로그래밍 언어나 소프트웨어 프레임워크, API마다 이름 짓는 방법이 다르다. 실무에서는 하나의 규칙을 정하고 통일하는 것이 유지보수 측면에서 좋다. 대표적인 언어, 프레임워크, 서비스에 대한 형식은 아래와 같다.

PHP snake_case
자바 camelCase
자바스크립트 camelCase
파이썬 snake_case
GSON(자바 패키지) snake_case
AWS 혼용
Google JSON 가이드 camelCase
MVC(ASP.net) CamelCase(1.0.0 전)
camelCase(1.0.0 이후)

 

 

JSON의 한계

사람이 읽기 편한 만큼, 컴퓨터 입장에서는 비효율이 있다는 점이 단점이다.

 

첫째, 불필요한 트래픽 오버헤드

읽기 쉽지만, 텍스트 기반이기 때문에 실질적인 데이터를 표현하는 데 드는 비용이 크다는 단점이 있다. 바이너리 기반 규격인 프로토콜 버퍼와 비교하면, JSON 225바이트 사용할 때, 프로토콜 버퍼는 86바이트만 사용한다. 가공하려는 데이터가 크면 클 수록 더 큰 차이가 날 것이다. 압축하면 바이너리 데이터와 비슷한 효과가 있지만, CPU 자원을 많이 사용해야 한다. 클라이언트 입장에서는 괜찮을 수 있어도, 서버 입장에서는 도입 전 충분한 부하 테스트를 해야 한다.

둘째, 메시지 호환성 유지의 어려움

유지보수의 어려움은 모든 텍스트 기반 데이터의 단점이다. 서버에서 JSON 파일 규격을 업데이트하면, 모든 클라이언트 프로그램들의 규격을 동일하게 반영해야 한다. (규격을 업데이트 한다는 것의 의미: 데이터 구조를 변경한다는 것, 즉 키와 값의 형태가 바뀔 수 있다는 뜻) 소프트웨어나 팀 규모가 커질 수록 문제 발생 소지가 커진다. 메시지 규격이 바뀔 때마다 메시지 사용하는 모든 곳을 찾아서 직접 수정해야 하기 때문에 누락되기 십상이다.

문자를 정수로 취급하는 등 타입 에러 발생시키기도 한다. 서버와 클라이언트가 같은 소스 코드 저장소를 사용한다 하더라도 오류는 발생할 수 있다. 따라서, 규격을 바꿔야 한다면 새 규격을 사용하는 것이 좋다.

  • “version”: 1, “data”: { … }
  • “version”: 2, “data”: { … }

RESTful API 사용시, 버전을 주소에 넣어 해결하는 경우가 많다.

인터페이스 코드를 활용할 수도 있다. 인터페이스 코드란? 프로토콜 버퍼처럼 규격을 정의하는 ‘스키마’ 파일을 먼저 만들고, 이 스키마 파일로부터 자동 생성되는 코드를 의미한다. 만약 스키마가 바뀌면, 인터페이스 코드도 바뀌게 된다. 인터페이스 코드를 사용하는 소프트웨어는 컴파일이 실패하거나 동작하지 않으므로 소프트웨어 배포 전 규격을 검사할 수 있다는 장점이 있다. 새 키를 추가했지만 사용하지 않는 경우 메시지 처리할 때 런타임 에러를 출력하도록 할 수 있다. 즉 조기에 문제 발견이 용이하다.

비슷한 접근으로, JSON을 직접 쓰지 않고 항상 클래스나 구조체로부터 자동 생성되게 처리하는 방법이 있다. 그러나 클라이언트와 서버가 사용하는 언어가 다르면 적용이 어려우며, 만들기도 쉽지 않다는 단점이 있다.

 

마치며

JSON은 단순하고 강력한 텍스트 기반 데이터 규격이다. 주석 사용 불가가 단점이지만 그럼에도 불구하고 JSON은 매우 많이 사용한다. 특별한 데이터 규격을 선택하기 어렵다면 JSON을 사용하면 된다. 그러나 서버와 클라이언트 간 연동 데이터 규격이 변경되는 상황을 인지할 수 없는 것이 단점이다. 1초 이내 짧은 시간에 많은 데이터를 주고 받아야 하는 환경에서는 비효율적이다.

 

참고

설정 파일과 같이, 주석이나 설명이 필요한 데이터를 저장할 때에는 주석이 공식 지원되는 YAML을 사용하면 좋다. YAML도 JSON만큼 많이 사용되는 인기있는 텍스트 기반 데이터 규격이다. 몽고DB에 관심이 있다면 바이너리 지원되는 JSON 규격인 BSON에 대해 공부하면 좋으나, 표준이 아니므로 몽고DB 아닌 다른 환경에 바이너리 데이터를 JSON에 포함할 때에는 Base64를 사용하는 것이 좋다.

 

다음 글에서는 YAML과 XML에 대한 보다 자세한 양식을 소개하겠다.


참고 문헌

  • 학교에서 알려주지 않는 17가지 실무 개발 기술 - 이기곤, 한빛미디어

댓글