ㅇㅖ? C로 HTTP API 코드를 작성한다고요? 🤷🏻♀️
그동안 Java/Spring으로 편하게 HTTP API 코드를 작성하던 나..
C/C++ base의 JSON 객체를 주고 받는 HTTP API 코드를 처음 접하고 든 충격을 전하고 싶어 노트북을 펼치게 되었다.
결론부터 말하자면, Java로 이러한 통신 코드를 작성하는 게 훨씬 단순하다는 점이고, 이 단순함은 JSON 객체를 다루는 편리함에서 와서 C/C++와 Java로 JSON 객체를 다룰 때 차이를 중점적으로 다루고자 한다.
[목표] JSON 객체 다룰 때 C/C++ vs. Java 주요 차이점 훑어보기
크게 두가지 측면에서 차이점을 비교해보자
✒️ Java의 Jackson 라이브러리, C의 Jansson 라이브러리 기준으로, 구체적인 사용법은 다루지 않는다
C와 Java의 차이점을 하나 뽑자면, 동적 메모리를 다루는 방식에서의 차이가 가장 크다.
C/C++은 개발자가 직접 메모리를 관리하는 반면, Java는 JVM이 알아서 메모리 관리를 하므로 개발자가 신경쓰지 않아도 된다.
int* ptr = new int; // Allocates memory on the heap
*ptr = 10;
// Must manually deallocate to prevent memory leaks
delete ptr;
C에서 JSON 데이터를 다룰 때 역시 memory leak이 나지 않도록, 메모리 할당/해제를 직접적으로 해준다는 귀찮음이 따라온다.
Jansson 라이브러리의 기본 Json 구조는 json_t로, 데이터 타입과 reference count를 들고 있는 구조체다. 포인터를 써서 접근 및 변경하고, 사용 후에는 더 이상 사용하지 않는 json 객체를 반드시 명시적으로 free 하는 과정이 필요하다.(json_decref(): reference count--, 0이 될 시 memory free)
json_t *json = json_object();
// Use json...
json_decref(json);
Jansson 라이브러리 문서에는 서로가 서로의 레퍼런스를 잡아 memory free 할 수 없는 "Circular reference"를 주의하라고 명시했다. 예를 들어, 아래와 같은 상황이다:
json_t *obj1 = json_object();
json_t *obj2 = json_object();
json_object_set(obj1, "ref", obj2);
json_object_set(obj2, "ref", obj1);
이런 경우 결과적으로 서로가 서로의 레퍼런스를 잡고 있는 구조를 가지게 되어, decref 하더라도 free 될 수 없는 상황이 발생한다.
obj1 = {"ref": {"ref": obj1}}
obj2 = {"ref": {"ref": obj2}}
이렇듯 C/C++은 JSON 객체를 다룰 때, 메모리를 직접 다루기 때문에 오는 번거로움을 그대로 가져간다.
객체지향에서 오는 장점 => 직렬화/역직렬화의 용이함
C는 절차지향적인 언어인 반면, Java는 객체지향적인 언어다. C++은 절차지향 base인 C에, 클래스를 도입함으로써 객체지향적인 특성을 가져왔다.
Java의 객체지향적인 성격이 JSON의 구조와 잘 어울린다. 클래스의 구조와 JSON의 구조가 상응이 잘되기 때문에, 클래스↔JSON 객체의 변환이 용이하다.
Java의 Jackson 라이브러리가 제공하는 ObjectMapper를 통해 JSON ↔ Java 객체를 편리하게 변환할 수 있다.
import com.fasterxml.jackson.databind.ObjectMapper;
public class Person {
private String name;
private int age;
// Getters and setters
// ...
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
// Serialization
Person person = new Person();
person.setName("Yooyoung");
person.setAge(28);
String json = mapper.writeValueAsString(person);
System.out.println("Serialized JSON: " + json);
// Deserialization
Person deserializedPerson = mapper.readValue(json, Person.class);
System.out.println("Deserialized name: " + deserializedPerson.getName());
System.out.println("Deserialized age: " + deserializedPerson.getAge());
}
}
같은 동작을 하는 코드를 C로 작성해보자.
명시적으로 struct와 포인터를 활용하여 JSON을 다뤄야 하는 불편함이 있다.
#include <stdio.h>
#include <string.h>
#include <jansson.h>
typedef struct {
char name[50];
int age;
} Person;
// Serialization function
char* serialize_person(Person* person) {
json_t *root = json_object();
json_object_set_new(root, "name", json_string(person->name));
json_object_set_new(root, "age", json_integer(person->age));
char *json_string = json_dumps(root, JSON_COMPACT);
json_decref(root);
return json_string;
}
// Deserialization function
void deserialize_person(const char* json_string, Person* person) {
json_error_t error;
json_t *root = json_loads(json_string, 0, &error);
if (!root) {
fprintf(stderr, "JSON parsing error: %s\n", error.text);
return;
}
json_t *name = json_object_get(root, "name");
json_t *age = json_object_get(root, "age");
if (json_is_string(name)) {
strncpy(person->name, json_string_value(name), sizeof(person->name) - 1);
person->name[sizeof(person->name) - 1] = '\0';
}
if (json_is_integer(age)) {
person->age = json_integer_value(age);
}
json_decref(root);
}
int main() {
// Serialization
Person person = {"Yooyoung Lee", 28};
char *json = serialize_person(&person);
printf("Serialized JSON: %s\n", json);
// Deserialization
Person deserialized_person;
deserialize_person(json, &deserialized_person);
printf("Deserialized name: %s\n", deserialized_person.name);
printf("Deserialized age: %d\n", deserialized_person.age);
// Clean up
free(json);
return 0;
}
<주요 차이점>
Java의 객체지향적인 성격으로 직관적으로 JSON와 Java Object 간 매핑이 용이하기 때문에 사용성 측면에서 편리하다.
반면 C/C++은 일일이 key, value를 명시하는 과정이 필요하며, 이로 인해 보일러플레이트 코드가 더 요구된다.
타입 체킹 및 에러 핸들링
C는 강 타입(strong typing)인 반면, JSON은 약 타입(loose typing)
✔️ 강 타입(strong typing)과 약 타입(loose typing)
C++: 구체적으로 명시된 타입
int age = 30;
std::string name = "John";
Json: 타입 명시 X
{
"age": 30,
"name": "John",
"isStudent": true,
"grades": [95, 87, 92]
}
이러면 C++에서 deserialize할 때(JSON -> C++), 해당 값에 적절한 타입을 결정해야 한다.
이로 인해 C++ JSON 라이브러리는 통상 타입 체킹 및 변환 함수, 타입 불일치에 대한 에러 핸들링 로직 등을 포함한다.
Java 역시 strong-typed 언어인데.. Java는 이 문제를 어떻게 해결한 것일까?
핵심 원리는 Java에 내장된 리플렉션이다.
리플렉션은 런타임에 동적으로 클래스의 정보를 알아내고 실행할 수 있는 것을 말한다.
덕분에 구조체를 정의하여 일일이 매핑해야 하는 C/C++에 비해, Java는 그 수고로움이 덜어진다.
https://jansson.readthedocs.io/en/latest/apiref.html
https://blogs.oracle.com/javamagazine/post/java-json-serialization-jackson
https://stackoverflow.com/questions/17549906/c-json-serialization/34165367