JSON 데이터는 현대 애플리케이션에서 널리 사용되는 데이터 교환 형식으로, 경량이며 사람이 읽기 쉬운 구조를 가지고 있습니다. 웹 API, 설정 파일, 데이터 저장 및 전송 등 다양한 용도로 활용됩니다.
C언어에서는 JSON 데이터를 직접 다루기 어려운 경우가 많으며, 이를 보다 쉽게 처리하기 위해 여러 라이브러리가 존재합니다. 본 기사에서는 C언어에서 JSON 데이터를 효율적으로 입출력하고 파싱하는 방법을 소개합니다. 특히 cJSON과 Jansson 같은 라이브러리를 활용하여 JSON 데이터를 읽고, 수정하고, 저장하는 기법을 설명하며, 성능 최적화를 위한 방법도 함께 다룰 것입니다.
JSON이란 무엇인가?
JSON(JavaScript Object Notation)은 데이터 교환을 위한 경량 포맷으로, 사람이 읽기 쉬우면서도 기계가 쉽게 분석할 수 있도록 설계되었습니다.
JSON의 특징
- 가독성: 단순한 텍스트 형식으로 되어 있어 사람이 쉽게 읽고 이해할 수 있습니다.
- 경량성: XML보다 크기가 작아 데이터 전송 속도가 빠릅니다.
- 호환성: 다양한 프로그래밍 언어에서 지원되며, 특히 웹 API에서 표준적으로 사용됩니다.
JSON 데이터 예시
JSON은 키-값 쌍으로 구성되며, 중괄호 {}
를 사용하여 객체를 정의합니다.
{
"name": "홍길동",
"age": 30,
"email": "hong@example.com"
}
또한, 배열을 지원하여 여러 데이터를 리스트 형태로 저장할 수도 있습니다.
{
"employees": [
{"name": "김철수", "age": 25},
{"name": "이영희", "age": 28}
]
}
XML과의 차이점
비교 항목 | JSON | XML |
---|---|---|
데이터 표현 | 키-값 쌍 | 태그 기반 |
크기 | 작음 | 비교적 큼 |
가독성 | 높음 | 낮음 |
지원 | 대부분의 언어에서 지원 | 대부분의 언어에서 지원 |
JSON은 XML보다 간결하며, 파싱 속도가 빠르다는 장점이 있어 웹 API 및 다양한 데이터 저장 방식에서 선호됩니다. C언어에서는 JSON을 직접 처리하기 어려울 수 있으므로, 별도의 JSON 라이브러리를 활용하는 것이 일반적입니다.
C언어에서 JSON을 다루는 라이브러리
C언어는 기본적으로 JSON을 직접 처리하는 기능을 제공하지 않으므로, JSON 데이터를 다루기 위해서는 별도의 라이브러리를 활용해야 합니다. 대표적인 JSON 라이브러리로는 cJSON, Jansson, json-c 등이 있으며, 각각의 특징과 장점을 살펴보겠습니다.
1. cJSON
cJSON은 경량의 JSON 라이브러리로, 단순한 구조와 쉬운 사용법이 장점입니다.
- 특징: 작고 가벼우며, JSON 데이터를 쉽게 파싱하고 생성할 수 있음
- 장점: C언어 스타일의 간결한 API 제공, 메모리 관리가 용이
- 단점: 고급 기능이 부족하여 복잡한 JSON 데이터를 처리하기 어려움
설치 방법 (Linux 환경)
git clone https://github.com/DaveGamble/cJSON.git
cd cJSON
mkdir build && cd build
cmake ..
make && sudo make install
2. Jansson
Jansson은 JSON을 처리하는 보다 강력한 라이브러리로, 다양한 기능을 제공합니다.
- 특징: JSON 데이터의 직렬화, 역직렬화, 동적 데이터 처리가 가능
- 장점: 높은 성능과 유연한 데이터 처리, JSON 객체를 C 구조체처럼 다룰 수 있음
- 단점: 메모리 관리가 다소 복잡할 수 있음
설치 방법 (Linux 환경)
sudo apt install libjansson-dev
3. json-c
json-c는 C언어에서 JSON을 다루기 위한 또 다른 라이브러리로, 속도와 유연성을 고려하여 설계되었습니다.
- 특징: JSON 문서 파싱 및 생성 기능 제공, 동적 할당을 효율적으로 관리
- 장점: 다양한 JSON 데이터 구조를 지원, 비교적 빠른 처리 속도
- 단점: 사용법이 다소 복잡하여 학습 곡선이 있음
설치 방법 (Linux 환경)
sudo apt install libjson-c-dev
라이브러리 비교
라이브러리 | 특징 | 장점 | 단점 |
---|---|---|---|
cJSON | 경량 JSON 처리 | 사용법이 간단함 | 기능이 제한적 |
Jansson | 강력한 기능 제공 | 높은 성능과 유연한 데이터 처리 | 메모리 관리가 필요함 |
json-c | 고성능 JSON 처리 | 다양한 JSON 데이터 구조 지원 | 학습 곡선이 있음 |
C언어에서 JSON을 다루기 위해서는 프로젝트의 요구사항에 따라 적절한 라이브러리를 선택해야 합니다. 간단한 JSON 처리가 필요하다면 cJSON, 고급 기능이 필요하다면 Jansson이나 json-c를 사용하는 것이 좋습니다.
cJSON 라이브러리를 활용한 기본 JSON 파싱
C언어에서 JSON 데이터를 다루기 위해 가장 많이 사용되는 라이브러리 중 하나는 cJSON입니다. cJSON은 경량이며 사용법이 간단하여 JSON 데이터를 쉽게 파싱하고 생성할 수 있습니다.
cJSON 라이브러리 설치
cJSON을 사용하려면 먼저 라이브러리를 설치해야 합니다.
Linux에서 설치하는 방법:
git clone https://github.com/DaveGamble/cJSON.git
cd cJSON
mkdir build && cd build
cmake ..
make && sudo make install
헤더 파일 포함 및 컴파일 방법:
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
컴파일 시에는 -lcjson
옵션을 추가해야 합니다.
gcc main.c -o main -lcjson
JSON 파싱 기본 예제
아래는 cJSON을 이용하여 JSON 문자열을 파싱하는 기본적인 예제입니다.
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
int main() {
// JSON 문자열 예제
const char *json_string = "{\"name\":\"홍길동\", \"age\":30, \"email\":\"hong@example.com\"}";
// JSON 파싱
cJSON *json = cJSON_Parse(json_string);
if (json == NULL) {
printf("JSON 파싱 실패\n");
return 1;
}
// JSON 객체에서 값 추출
cJSON *name = cJSON_GetObjectItemCaseSensitive(json, "name");
cJSON *age = cJSON_GetObjectItemCaseSensitive(json, "age");
cJSON *email = cJSON_GetObjectItemCaseSensitive(json, "email");
// 값 출력
if (cJSON_IsString(name) && name->valuestring) {
printf("이름: %s\n", name->valuestring);
}
if (cJSON_IsNumber(age)) {
printf("나이: %d\n", age->valueint);
}
if (cJSON_IsString(email) && email->valuestring) {
printf("이메일: %s\n", email->valuestring);
}
// JSON 객체 해제
cJSON_Delete(json);
return 0;
}
설명
cJSON_Parse(json_string)
: JSON 문자열을 파싱하여 cJSON 객체로 변환합니다.cJSON_GetObjectItemCaseSensitive(json, "키")
: 해당 키의 값을 가져옵니다.cJSON_IsString()
및cJSON_IsNumber()
: 값의 타입을 확인합니다.cJSON_Delete(json)
: JSON 객체 사용이 끝난 후 메모리를 해제합니다.
실행 결과
이름: 홍길동
나이: 30
이메일: hong@example.com
이제 cJSON을 이용하여 JSON 데이터를 쉽게 파싱할 수 있습니다. 다음 단계에서는 JSON 파일을 읽고 객체로 변환하는 방법을 설명하겠습니다.
JSON 파일을 읽고 객체로 변환하는 방법
C언어에서 JSON 데이터를 다룰 때, 일반적으로 JSON 파일을 읽어 데이터를 파싱하여 객체로 변환하는 과정이 필요합니다. 이를 위해 cJSON 라이브러리를 활용할 수 있습니다.
JSON 파일 예제
먼저, data.json
이라는 파일을 생성하고 다음과 같이 내용을 작성합니다.
{
"name": "홍길동",
"age": 30,
"email": "hong@example.com",
"skills": ["C", "Python", "Java"]
}
파일에서 JSON 데이터 읽기
아래 코드는 JSON 파일을 읽고, cJSON을 이용하여 데이터를 객체로 변환하는 방법을 보여줍니다.
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
// JSON 파일을 읽어 문자열로 변환하는 함수
char *read_json_file(const char *filename) {
FILE *file = fopen(filename, "r");
if (!file) {
printf("파일을 열 수 없습니다: %s\n", filename);
return NULL;
}
// 파일 크기 확인
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
rewind(file);
// 메모리 할당 및 파일 읽기
char *json_data = (char *)malloc(file_size + 1);
if (!json_data) {
printf("메모리 할당 실패\n");
fclose(file);
return NULL;
}
fread(json_data, 1, file_size, file);
json_data[file_size] = '\0'; // 문자열 종료 문자 추가
fclose(file);
return json_data;
}
int main() {
// JSON 파일 읽기
char *json_string = read_json_file("data.json");
if (!json_string) {
return 1;
}
// JSON 문자열을 cJSON 객체로 변환
cJSON *json = cJSON_Parse(json_string);
if (!json) {
printf("JSON 파싱 실패\n");
free(json_string);
return 1;
}
// 개별 데이터 추출
cJSON *name = cJSON_GetObjectItemCaseSensitive(json, "name");
cJSON *age = cJSON_GetObjectItemCaseSensitive(json, "age");
cJSON *email = cJSON_GetObjectItemCaseSensitive(json, "email");
cJSON *skills = cJSON_GetObjectItemCaseSensitive(json, "skills");
// JSON 데이터 출력
if (cJSON_IsString(name) && name->valuestring) {
printf("이름: %s\n", name->valuestring);
}
if (cJSON_IsNumber(age)) {
printf("나이: %d\n", age->valueint);
}
if (cJSON_IsString(email) && email->valuestring) {
printf("이메일: %s\n", email->valuestring);
}
// JSON 배열 (skills) 출력
if (cJSON_IsArray(skills)) {
int skill_count = cJSON_GetArraySize(skills);
printf("기술 스택: ");
for (int i = 0; i < skill_count; i++) {
cJSON *skill = cJSON_GetArrayItem(skills, i);
if (cJSON_IsString(skill) && skill->valuestring) {
printf("%s ", skill->valuestring);
}
}
printf("\n");
}
// 메모리 해제
cJSON_Delete(json);
free(json_string);
return 0;
}
설명
- 파일 읽기
read_json_file()
함수는 JSON 파일을 열어 전체 내용을 읽고 문자열로 변환합니다.fseek()
과ftell()
을 사용하여 파일 크기를 확인하고,malloc()
을 이용해 메모리를 동적으로 할당합니다.
- JSON 파싱 및 데이터 추출
cJSON_Parse(json_string)
를 이용하여 JSON 문자열을 cJSON 객체로 변환합니다.cJSON_GetObjectItemCaseSensitive(json, "키")
를 이용해 개별 데이터를 가져옵니다.cJSON_IsArray()
를 사용해 배열인지 확인한 후,cJSON_GetArraySize()
로 요소 개수를 확인합니다.
- 메모리 관리
cJSON_Delete(json)
을 사용하여 JSON 객체를 해제합니다.- 동적으로 할당된
json_string
메모리도free()
를 통해 해제합니다.
실행 결과
이름: 홍길동
나이: 30
이메일: hong@example.com
기술 스택: C Python Java
이제 JSON 파일을 읽어 데이터를 객체로 변환하고, 필요한 정보를 추출하는 방법을 알게 되었습니다. 다음으로 JSON 데이터를 파일로 저장하는 방법을 살펴보겠습니다.
JSON 데이터를 파일로 저장하는 방법
C언어에서 JSON 데이터를 생성하고 파일로 저장하는 것은 JSON 기반 설정 파일을 만들거나 데이터를 저장하는 데 유용합니다. 이를 위해 cJSON 라이브러리를 사용하여 JSON 객체를 생성하고, 이를 문자열로 변환한 후 파일에 저장하는 방법을 설명하겠습니다.
JSON 파일 저장 기본 예제
아래 코드는 JSON 데이터를 생성하여 output.json
파일로 저장하는 예제입니다.
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
// JSON 데이터를 파일로 저장하는 함수
void save_json_to_file(const char *filename, const char *json_string) {
FILE *file = fopen(filename, "w");
if (!file) {
printf("파일을 열 수 없습니다: %s\n", filename);
return;
}
fprintf(file, "%s", json_string);
fclose(file);
}
int main() {
// JSON 객체 생성
cJSON *json = cJSON_CreateObject();
// 키-값 추가
cJSON_AddStringToObject(json, "name", "홍길동");
cJSON_AddNumberToObject(json, "age", 30);
cJSON_AddStringToObject(json, "email", "hong@example.com");
// JSON 배열 생성 (기술 스택)
cJSON *skills = cJSON_CreateArray();
cJSON_AddItemToArray(skills, cJSON_CreateString("C"));
cJSON_AddItemToArray(skills, cJSON_CreateString("Python"));
cJSON_AddItemToArray(skills, cJSON_CreateString("Java"));
// 배열을 JSON 객체에 추가
cJSON_AddItemToObject(json, "skills", skills);
// JSON 객체를 문자열로 변환
char *json_string = cJSON_Print(json);
if (json_string) {
// JSON 파일 저장
save_json_to_file("output.json", json_string);
printf("JSON 데이터가 output.json 파일에 저장되었습니다.\n");
// 문자열 메모리 해제
free(json_string);
}
// JSON 객체 메모리 해제
cJSON_Delete(json);
return 0;
}
설명
- JSON 객체 생성 및 데이터 추가
cJSON_CreateObject()
로 JSON 객체를 생성합니다.cJSON_AddStringToObject()
와cJSON_AddNumberToObject()
를 이용하여 데이터를 추가합니다.cJSON_CreateArray()
를 사용해 JSON 배열을 생성하고,cJSON_AddItemToArray()
를 이용해 배열 요소를 추가합니다.
- JSON 데이터를 문자열로 변환
cJSON_Print(json)
을 사용하여 JSON 객체를 문자열로 변환합니다.
- JSON 파일 저장
save_json_to_file()
함수를 이용해 JSON 문자열을 파일로 저장합니다.fprintf()
를 사용하여 JSON 데이터를 파일에 기록합니다.
- 메모리 해제
cJSON_Delete(json)
을 사용하여 JSON 객체를 해제합니다.free(json_string)
을 사용하여 JSON 문자열 메모리를 해제합니다.
실행 결과
프로그램을 실행하면 output.json
파일이 생성되며, 다음과 같은 내용이 저장됩니다.
{
"name": "홍길동",
"age": 30,
"email": "hong@example.com",
"skills": [
"C",
"Python",
"Java"
]
}
JSON 데이터를 파일에 저장할 때 고려해야 할 사항
- 파일 쓰기 오류 처리: 파일이 정상적으로 열리지 않았을 경우 오류 메시지를 출력해야 합니다.
- JSON 데이터의 포맷:
cJSON_Print()
를 사용하면 사람이 읽기 쉬운 JSON 형식으로 저장되지만,cJSON_PrintUnformatted()
를 사용하면 공백 없이 작은 크기의 JSON 데이터를 저장할 수 있습니다. - 메모리 관리: JSON 객체와 변환된 문자열을 적절히 해제해야 메모리 누수를 방지할 수 있습니다.
이제 JSON 데이터를 파일로 저장하는 방법을 익혔습니다. 다음 단계에서는 JSON 데이터를 수정 및 업데이트하는 방법을 살펴보겠습니다.
JSON 데이터의 수정 및 업데이트 방법
C언어에서 JSON 데이터를 수정하고 업데이트하는 것은 설정 파일을 변경하거나 프로그램에서 데이터를 동적으로 조작하는 데 유용합니다. cJSON 라이브러리를 활용하여 기존 JSON 데이터를 로드한 후 특정 값을 변경하고, 새로운 데이터를 추가하는 방법을 살펴보겠습니다.
JSON 데이터 수정 기본 예제
아래 예제에서는 JSON 파일을 읽고, 특정 값을 수정한 후 다시 저장하는 과정을 다룹니다.
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
// JSON 파일을 읽어 문자열로 변환하는 함수
char *read_json_file(const char *filename) {
FILE *file = fopen(filename, "r");
if (!file) {
printf("파일을 열 수 없습니다: %s\n", filename);
return NULL;
}
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
rewind(file);
char *json_data = (char *)malloc(file_size + 1);
if (!json_data) {
printf("메모리 할당 실패\n");
fclose(file);
return NULL;
}
fread(json_data, 1, file_size, file);
json_data[file_size] = '\0';
fclose(file);
return json_data;
}
// JSON 데이터를 파일로 저장하는 함수
void save_json_to_file(const char *filename, const char *json_string) {
FILE *file = fopen(filename, "w");
if (!file) {
printf("파일을 저장할 수 없습니다: %s\n", filename);
return;
}
fprintf(file, "%s", json_string);
fclose(file);
}
int main() {
// JSON 파일 읽기
char *json_string = read_json_file("output.json");
if (!json_string) {
return 1;
}
// JSON 문자열을 cJSON 객체로 변환
cJSON *json = cJSON_Parse(json_string);
if (!json) {
printf("JSON 파싱 실패\n");
free(json_string);
return 1;
}
// 기존 값 수정
cJSON *name = cJSON_GetObjectItemCaseSensitive(json, "name");
if (name) {
cJSON_SetValuestring(name, "이순신"); // "홍길동" → "이순신" 변경
}
cJSON *age = cJSON_GetObjectItemCaseSensitive(json, "age");
if (age) {
age->valueint = 40; // 나이를 40으로 변경
}
// 새로운 데이터 추가
cJSON_AddStringToObject(json, "address", "서울특별시");
// JSON 배열 업데이트 (기술 스택 추가)
cJSON *skills = cJSON_GetObjectItemCaseSensitive(json, "skills");
if (cJSON_IsArray(skills)) {
cJSON_AddItemToArray(skills, cJSON_CreateString("Go"));
}
// JSON 객체를 문자열로 변환
char *updated_json_string = cJSON_Print(json);
if (updated_json_string) {
// 변경된 JSON을 파일에 저장
save_json_to_file("output.json", updated_json_string);
printf("JSON 데이터가 수정되어 저장되었습니다.\n");
// 문자열 메모리 해제
free(updated_json_string);
}
// 메모리 해제
cJSON_Delete(json);
free(json_string);
return 0;
}
설명
- JSON 파일 읽기 및 파싱
read_json_file("output.json")
을 사용해 파일 내용을 문자열로 변환합니다.cJSON_Parse(json_string)
을 이용하여 JSON 객체를 생성합니다.
- 기존 값 수정
cJSON_GetObjectItemCaseSensitive(json, "name")
을 이용하여 값을 가져온 후,cJSON_SetValuestring(name, "이순신")
으로 값을 변경합니다.- 숫자 값(
age
)의 경우age->valueint = 40;
으로 직접 변경합니다.
- 새로운 데이터 추가
cJSON_AddStringToObject(json, "address", "서울특별시")
를 사용하여 새 필드를 추가합니다.
- JSON 배열 수정
cJSON_GetObjectItemCaseSensitive(json, "skills")
을 사용하여 배열을 가져옵니다.cJSON_AddItemToArray(skills, cJSON_CreateString("Go"))
를 이용하여"Go"
라는 새로운 요소를 추가합니다.
- 파일로 저장
cJSON_Print(json)
을 사용해 JSON 객체를 문자열로 변환한 후,save_json_to_file("output.json", updated_json_string)
을 이용해 저장합니다.
실행 결과
기존 output.json
데이터
{
"name": "홍길동",
"age": 30,
"email": "hong@example.com",
"skills": ["C", "Python", "Java"]
}
업데이트된 output.json
데이터
{
"name": "이순신",
"age": 40,
"email": "hong@example.com",
"skills": ["C", "Python", "Java", "Go"],
"address": "서울특별시"
}
JSON 데이터 수정 시 고려해야 할 사항
- 기존 값을 변경할 때 데이터 타입 유지:
cJSON_SetValuestring()
은 문자열 값만 변경할 수 있으며, 숫자 값은 직접 수정해야 합니다. - 배열 수정 시 크기 관리: 새로운 요소를 추가할 경우
cJSON_AddItemToArray()
를 사용해야 합니다. - 메모리 해제 필수:
cJSON_Delete(json)
과free(json_string)
을 사용해 메모리를 적절히 해제해야 합니다.
이제 JSON 데이터를 읽고, 수정하고, 저장하는 방법을 익혔습니다. 다음으로 JSON 데이터를 C 구조체로 변환하는 방법을 살펴보겠습니다.
JSON 데이터를 C 구조체로 변환하는 방법
C언어에서 JSON 데이터를 다룰 때, 구조체를 활용하면 데이터를 보다 직관적이고 효율적으로 관리할 수 있습니다. JSON 데이터를 C 구조체로 변환하면 코드의 가독성이 높아지고, 유지보수가 쉬워집니다.
본 예제에서는 cJSON 라이브러리를 활용하여 JSON 데이터를 C 구조체로 변환하는 방법을 살펴보겠습니다.
JSON 데이터 예제
먼저, data.json
파일을 생성하고 다음과 같은 JSON 데이터를 저장합니다.
{
"name": "홍길동",
"age": 30,
"email": "hong@example.com",
"skills": ["C", "Python", "Java"]
}
JSON 데이터를 구조체로 변환
아래 C 프로그램은 JSON 데이터를 읽어 구조체로 변환한 후, 해당 데이터를 출력하는 예제입니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"
// 사용자 정보를 저장할 구조체 정의
typedef struct {
char name[50];
int age;
char email[100];
char skills[5][20]; // 최대 5개의 기술 스택 저장 가능
int skill_count;
} UserInfo;
// JSON 파일을 읽어 문자열로 변환하는 함수
char *read_json_file(const char *filename) {
FILE *file = fopen(filename, "r");
if (!file) {
printf("파일을 열 수 없습니다: %s\n", filename);
return NULL;
}
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
rewind(file);
char *json_data = (char *)malloc(file_size + 1);
if (!json_data) {
printf("메모리 할당 실패\n");
fclose(file);
return NULL;
}
fread(json_data, 1, file_size, file);
json_data[file_size] = '\0';
fclose(file);
return json_data;
}
// JSON 데이터를 구조체로 변환하는 함수
int parse_json_to_struct(const char *json_string, UserInfo *user) {
cJSON *json = cJSON_Parse(json_string);
if (!json) {
printf("JSON 파싱 실패\n");
return 0;
}
// JSON 데이터에서 값 추출
cJSON *name = cJSON_GetObjectItemCaseSensitive(json, "name");
cJSON *age = cJSON_GetObjectItemCaseSensitive(json, "age");
cJSON *email = cJSON_GetObjectItemCaseSensitive(json, "email");
cJSON *skills = cJSON_GetObjectItemCaseSensitive(json, "skills");
if (cJSON_IsString(name) && name->valuestring) {
strncpy(user->name, name->valuestring, sizeof(user->name) - 1);
}
if (cJSON_IsNumber(age)) {
user->age = age->valueint;
}
if (cJSON_IsString(email) && email->valuestring) {
strncpy(user->email, email->valuestring, sizeof(user->email) - 1);
}
// JSON 배열 (skills) 처리
user->skill_count = 0;
if (cJSON_IsArray(skills)) {
int skill_count = cJSON_GetArraySize(skills);
for (int i = 0; i < skill_count && i < 5; i++) { // 최대 5개까지만 저장
cJSON *skill = cJSON_GetArrayItem(skills, i);
if (cJSON_IsString(skill) && skill->valuestring) {
strncpy(user->skills[i], skill->valuestring, sizeof(user->skills[i]) - 1);
user->skill_count++;
}
}
}
// JSON 객체 메모리 해제
cJSON_Delete(json);
return 1;
}
int main() {
// JSON 파일 읽기
char *json_string = read_json_file("data.json");
if (!json_string) {
return 1;
}
// 구조체 선언
UserInfo user;
// JSON 데이터를 구조체로 변환
if (parse_json_to_struct(json_string, &user)) {
printf("이름: %s\n", user.name);
printf("나이: %d\n", user.age);
printf("이메일: %s\n", user.email);
printf("기술 스택: ");
for (int i = 0; i < user.skill_count; i++) {
printf("%s ", user.skills[i]);
}
printf("\n");
} else {
printf("JSON 파싱 중 오류 발생\n");
}
// 메모리 해제
free(json_string);
return 0;
}
설명
- UserInfo 구조체 정의
name
: 사용자 이름 (문자열)age
: 나이 (정수)email
: 이메일 주소 (문자열)skills
: 사용자의 기술 스택 (최대 5개까지 저장 가능)skill_count
: 기술 개수
- JSON 파일 읽기 (
read_json_file()
)
fseek()
,ftell()
을 이용하여 파일 크기를 확인한 후,- 동적으로 메모리를 할당하여 JSON 데이터를 문자열로 변환합니다.
- JSON 데이터를 구조체로 변환 (
parse_json_to_struct()
)
cJSON_Parse()
를 이용하여 JSON을 파싱한 후,cJSON_GetObjectItemCaseSensitive()
를 이용하여 데이터를 추출합니다.strncpy()
를 사용하여 문자열 값을 안전하게 복사합니다.cJSON_IsArray()
를 이용하여 JSON 배열을 확인하고,cJSON_GetArrayItem()
을 이용해 데이터를 가져옵니다.
- 출력 예시
이름: 홍길동
나이: 30
이메일: hong@example.com
기술 스택: C Python Java
JSON 데이터를 C 구조체로 변환할 때 고려해야 할 사항
- 문자열 복사 시
strncpy()
사용 strncpy()
를 사용하여 문자열을 복사할 때, 버퍼 오버플로우를 방지하기 위해sizeof(배열) - 1
을 지정해야 합니다.- 배열 크기 제한
skills
배열의 크기를 제한하여 메모리 초과를 방지해야 합니다.- 동적 메모리 관리
malloc()
로 할당된 메모리는free()
를 사용하여 해제해야 합니다.- 에러 처리
- JSON 파일이 없거나 JSON 형식이 잘못되었을 경우를 대비해 에러 처리를 추가해야 합니다.
이제 JSON 데이터를 C 구조체로 변환하는 방법을 익혔습니다. 다음으로는 JSON 입출력 성능을 최적화하는 기법을 살펴보겠습니다.
JSON 입출력 최적화 기법
C언어에서 JSON 데이터를 처리할 때, 대용량 데이터의 입출력 성능을 최적화하는 것은 매우 중요합니다. 특히 JSON 파일을 다룰 때 메모리 관리, 스트림 처리, 버퍼링 등을 효율적으로 적용하면 속도를 크게 향상할 수 있습니다.
본 기사에서는 cJSON 라이브러리를 활용하여 JSON 입출력 성능을 개선하는 여러 가지 기법을 소개합니다.
1. JSON 데이터를 스트림 방식으로 읽기
기본적으로 JSON 데이터를 파일에서 한 번에 읽어 파싱하는 방식은 작은 파일에서는 문제가 없지만, 대용량 JSON 파일에서는 메모리 부족이 발생할 수 있습니다. 이를 해결하기 위해 JSON 데이터를 한 줄씩 스트리밍 방식으로 읽는 방법을 사용할 수 있습니다.
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
#define BUFFER_SIZE 4096 // 4KB 버퍼 크기
// JSON 파일을 스트리밍 방식으로 읽어 파싱하는 함수
cJSON *parse_json_from_stream(const char *filename) {
FILE *file = fopen(filename, "r");
if (!file) {
printf("파일을 열 수 없습니다: %s\n", filename);
return NULL;
}
char buffer[BUFFER_SIZE];
size_t bytesRead;
cJSON *json = NULL;
// 파일을 버퍼 단위로 읽기
while ((bytesRead = fread(buffer, 1, BUFFER_SIZE - 1, file)) > 0) {
buffer[bytesRead] = '\0'; // 문자열 종료
json = cJSON_Parse(buffer);
if (json) {
break; // JSON 파싱 성공 시 종료
}
}
fclose(file);
return json;
}
int main() {
cJSON *json = parse_json_from_stream("large_data.json");
if (json) {
printf("JSON 파싱 성공!\n");
cJSON_Delete(json);
} else {
printf("JSON 파싱 실패\n");
}
return 0;
}
🔹 최적화 포인트:
- 버퍼 크기 최적화: 4KB 버퍼를 사용하여 불필요한 메모리 사용을 방지함
- 스트리밍 처리: 파일을 한꺼번에 메모리에 올리지 않고 버퍼 단위로 읽어 처리
- 조기 종료: JSON 데이터가 파싱되면 즉시 종료하여 성능을 향상
2. JSON 파일을 압축하여 저장
JSON 파일은 일반적으로 많은 공백과 개행 문자를 포함하여 크기가 커질 수 있습니다. 이를 해결하기 위해 최소한의 공간을 사용하여 저장하는 방법을 적용할 수 있습니다.
🔹 최적화 방법:
- cJSON_PrintUnformatted(): JSON 데이터를 공백 없이 압축된 형식으로 변환하여 파일 크기 절감
- gzip/zlib 압축: JSON 데이터를 파일로 저장할 때 gzip이나 zlib을 활용하면 크기를 더욱 줄일 수 있음
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
// JSON 데이터를 압축된 형식으로 저장하는 함수
void save_json_compressed(const char *filename, cJSON *json) {
char *json_string = cJSON_PrintUnformatted(json); // 압축된 JSON 문자열 생성
if (!json_string) {
printf("JSON 변환 실패\n");
return;
}
FILE *file = fopen(filename, "w");
if (!file) {
printf("파일을 저장할 수 없습니다: %s\n", filename);
free(json_string);
return;
}
fprintf(file, "%s", json_string);
fclose(file);
free(json_string);
}
int main() {
// JSON 객체 생성
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "name", "홍길동");
cJSON_AddNumberToObject(json, "age", 30);
// JSON 압축 저장
save_json_compressed("compressed.json", json);
printf("JSON이 압축된 형식으로 저장되었습니다.\n");
cJSON_Delete(json);
return 0;
}
🔹 결과 비교
일반 JSON (cJSON_Print 사용)
{
"name": "홍길동",
"age": 30
}
압축 JSON (cJSON_PrintUnformatted 사용)
{"name":"홍길동","age":30}
💡 효과:
- 공백 및 개행 문자 제거 → 파일 크기 최대 30% 절감
- 불필요한 데이터 제거 → 메모리 사용량 감소
3. JSON 파싱 속도 최적화
🔹 JSON 파싱 시 성능을 높이는 방법
- 필요한 데이터만 파싱: JSON 데이터의 일부만 필요한 경우 전체 파싱을 하지 않고 부분적으로 처리
- JSON 구조 최적화: 중첩된 JSON 구조가 복잡하면 단순화하여 처리 속도 개선
- 해시 테이블 기반 검색: JSON 데이터를 해시 테이블에 저장하여 검색 속도를 높임
4. JSON 데이터를 바이너리 포맷으로 변환
JSON 데이터는 텍스트 형식이므로, 파일 크기 및 입출력 속도가 저하될 수 있습니다. 대안으로 바이너리 포맷(e.g., MessagePack, CBOR)을 사용하면 성능을 더욱 최적화할 수 있습니다.
🔹 바이너리 포맷 활용
- MessagePack: JSON과 유사한 구조지만 더 작은 크기와 빠른 속도 제공
- CBOR (Concise Binary Object Representation): IoT 및 대용량 데이터 처리에서 많이 사용됨
🔹 CBOR 예제 (TinyCBOR 라이브러리 활용)
sudo apt install libcbor-dev
#include <stdio.h>
#include <tinycbor/cbor.h>
int main() {
uint8_t buffer[128];
CborEncoder encoder, mapEncoder;
cbor_encoder_init(&encoder, buffer, sizeof(buffer), 0);
cbor_encoder_create_map(&encoder, &mapEncoder, 2);
cbor_encode_text_stringz(&mapEncoder, "name");
cbor_encode_text_stringz(&mapEncoder, "홍길동");
cbor_encode_text_stringz(&mapEncoder, "age");
cbor_encode_uint(&mapEncoder, 30);
cbor_encoder_close_container(&encoder, &mapEncoder);
printf("CBOR 데이터 저장 완료\n");
return 0;
}
💡 효과:
- 파일 크기 최대 50% 감소
- JSON보다 빠른 입출력 속도
- IoT 및 네트워크 전송에 최적
최적화 기법 요약
최적화 기법 | 설명 | 효과 |
---|---|---|
스트리밍 JSON 읽기 | 대용량 JSON 파일을 한 줄씩 처리 | 메모리 절약, 속도 향상 |
압축된 JSON 저장 | 공백 없는 JSON 저장 (cJSON_PrintUnformatted) | 파일 크기 절감 |
파싱 속도 개선 | 필요 데이터만 선택적으로 파싱 | 불필요한 연산 감소 |
바이너리 포맷 사용 | CBOR, MessagePack 적용 | 더 빠른 데이터 처리 |
결론
C언어에서 JSON 데이터를 처리할 때, 파일 크기와 메모리 사용량을 줄이고 입출력 속도를 높이기 위한 다양한 최적화 기법을 적용할 수 있습니다.
- 대용량 JSON을 효율적으로 읽기 위해 스트리밍 기법 활용
- JSON을 압축하여 파일 크기 절감
- 바이너리 포맷(JSON 대체)으로 변환하여 성능 향상
이제 최적화된 JSON 입출력 방식을 활용하여 더욱 빠르고 효율적인 C언어 애플리케이션을 개발할 수 있습니다.
요약
본 기사에서는 C언어에서 JSON 데이터를 입출력하고 파싱하는 방법에 대해 자세히 살펴보았습니다. 주요 내용을 정리하면 다음과 같습니다.
- JSON 개념 이해
- JSON은 가볍고, 가독성이 높은 데이터 교환 포맷이며, 다양한 프로그래밍 언어에서 사용됩니다.
- XML과 비교하여 간결하며 빠른 속도를 제공합니다.
- C언어에서 JSON을 다루는 라이브러리
- 대표적인 라이브러리로 cJSON, Jansson, json-c 등이 있으며, 프로젝트의 요구사항에 따라 선택할 수 있습니다.
- JSON 파싱 및 파일 입출력
cJSON_Parse()
를 이용하여 JSON 문자열을 C에서 객체로 변환하고,cJSON_Print()
를 사용해 JSON 데이터를 문자열로 변환할 수 있습니다.- JSON 데이터를 파일에서 읽고 객체로 변환하는 방법을 설명하였으며, 구조체와 연계하여 쉽게 활용할 수 있습니다.
- JSON 데이터 수정 및 업데이트
- 기존 JSON 데이터의 값을 변경하고, 새로운 데이터를 추가하는 방법을 살펴보았습니다.
cJSON_SetValuestring()
과cJSON_AddItemToObject()
등을 사용하여 JSON을 동적으로 수정할 수 있습니다.
- JSON 데이터를 C 구조체로 변환
- JSON 데이터를 구조체로 변환하면 코드의 가독성이 향상되고 데이터 처리 속도가 빨라집니다.
strncpy()
와cJSON_GetObjectItemCaseSensitive()
를 활용하여 JSON 값을 안전하게 구조체에 저장하는 방법을 소개했습니다.
- JSON 입출력 최적화
- 대용량 JSON 데이터를 효율적으로 다루기 위해 스트리밍 방식의 파일 읽기를 적용할 수 있습니다.
cJSON_PrintUnformatted()
를 이용한 압축 저장 기법을 활용하여 파일 크기를 줄일 수 있습니다.- JSON의 바이너리 대안으로 CBOR, MessagePack을 활용하면 속도를 더욱 최적화할 수 있습니다.
🚀 결론
- C언어에서 JSON을 다루려면 적절한 라이브러리를 선택하고, 효율적인 입출력 방식을 적용해야 합니다.
- cJSON을 사용하면 경량 JSON 처리가 가능하며, 대용량 데이터를 처리할 경우 스트리밍 방식과 압축 저장 기법을 활용하면 성능을 크게 개선할 수 있습니다.
- JSON 데이터를 C 구조체와 연계하여 처리하면 프로그램의 유지보수성과 성능을 더욱 향상할 수 있습니다.
이제 최적화된 JSON 데이터 입출력 방식을 적용하여 보다 효율적인 C언어 애플리케이션을 개발할 수 있습니다. 🚀