Rust

Rust 강좌 6 -JSON과 함께 작업해볼까요?

드리프트2 2024. 10. 8. 22:13

Rust 강좌 6 -JSON과 함께 작업해볼까요?

JSON은 현대 웹의 핵심 데이터 형식입니다.

 

자바스크립트 세계에서 비롯되어 많은 관심을 받았고, 현재는 데이터 교환 형식으로 웹 개발자의 첫 번째 선택인데요.

 

웹뿐만 아니라 한때는 자바스크립트 전용이었던 JSON 지원이 이제는 어디에서나 가능합니다.

 

많은 언어들이 표준 라이브러리에 JSON 파서를 포함하고 있고, 그렇지 않은 경우에도 누군가 이미 서드 파티 라이브러리를 만들어 놓았는데요.

 

Rust의 경우, JSON 지원은 rustc_serialize::json 모듈에서 제공됩니다.

 

참고로, 이번 글에서는 웹, API, 요청 등에 초점을 두지 않았습니다.

 

이전에 hyper에 대한 글에서 JSON을 언급했지만, 이번에는 JSON으로 인코딩된 데이터가 어디에서 오는지나 이후 어떻게 처리하는지는 중요하지 않은데요.

 

여기서는 몇 가지 실용적인 팁을 보여드리겠습니다.

 

자, 그럼 이제 JSON과 Rust로 돌아가볼까요?

 

기본 자료형

많은 Rust 타입은 예상대로 JSON으로 직렬화됩니다.

 

encode() 함수는 인자를 불변으로 빌린다는 점에 주의해야 합니다:

 

extern crate rustc_serialize;

use rustc_serialize::Encodable;
use rustc_serialize::json::{self, Encoder};

println!("{:?}", json::encode(&42));
println!("{:?}", json::encode(&vec!["to", "be", "or", "not", "to", "be"]));

 

Option<T>Some(value)이면 값 자체로 인코딩되고, None이면 null로 매핑됩니다.

 

자동 (역)직렬화

CSV 챕터에서 RustcEncodableRustcDecodable 트레이트를 언급했는데요.

 

여기 중첩된 구조체의 예시가 있습니다:

println!("{:?}", json::encode(&Some(true)));
let user = User {
    name: "Zbyszek".to_string(),
    post_count: 100u32,
    likes_burgers: true,
    avatar: Some(Photo {
        url: "http://lorempixel.com/160/160/".to_string(),
        dimensions: (160u32, 160u32),
    }),
};
$ cargo run
Ok("{\"name\":\"Zbyszek\",\"post_count\":100,\"likes_burgers\":true,\"avatar\":{\"url\":\"http://lorempixel.com/160/160/\",\"dimensions\":[160,160]}}")

출력 형식화

json::encode()는 출력의 가독성을 신경 쓰지 않는데요.

 

생성된 JSON은 정확하고 기계가 읽을 수 있지만, 줄 바꿈이나 들여쓰기가 없어서 사람이 디버그하기에는 어렵습니다.

 

출력 형식화는 단순한 함수 호출보다 조금 더 복잡하지만, 너무 어렵지는 않습니다:

println!("{:?}", json::encode(&user));
let mut encoded = String::new();
{
    let mut encoder = Encoder::new_pretty(&mut encoded);
    user.encode(&mut encoder).expect("JSON encode error");
}

디코딩

println!("{}", encoded);
let incoming_request = "{\"name\":\"John\",\"post_count\":2,\"likes_burgers\":false,\
                        \"avatar\":null}";
let decoded: User = json::decode(incoming_request).unwrap();
println!("나의 이름은 {}이고 나는 버거를 {}",
    decoded.name,
    if decoded.likes_burgers {
        "좋아합니다"
    } else {
        "좋아하지 않습니다"
    });

 

보시다시피, 디코딩도 꽤 쉬운데요.

 

하지만 사전에 모든 필드를 알지 못하면 어떻게 될까요?

 

json 모듈의 또 다른 함수인 from_str()를 사용할 수 있습니다.

 

from_str()decode()의 차이점은 후자는 RustcDecodable을 구현하는 구조체를 반환할 수 있는 반면, 전자는 Json 값을 반환한다는 점인데요.

 

이 타입은 find()를 포함한 몇 가지 메소드를 가지고 있습니다.

 

아래 예시를 보세요:

assert!(decoded.avatar.is_none());
let new_request = "{\"id\":64,\"title\":\"24days\",\"stats\":{\"pageviews\":1500}}";

if let Ok(request_json) = json::Json::from_str(new_request) {
    if let Some(stats) = request_json.find("stats") {
        if let Some(pageviews) = stats.find("pageviews") {
            println!("페이지뷰 수: {}", pageviews);
        }
    }
}

 

if let 언어 구조를 사용하고 있는데요, 이는 우리가 한 가지 분기에만 관심이 있고 표현식이 일치하지 않을 경우 아무것도 하지 않을 때 패턴 매치를 간소화해줍니다.

 

json! 매크로

참고: 문법 확장은 nightly에서만 작동합니다.

 

오늘 보여드리고 싶은 것이 하나 더 있는데요.

 

json_macros 크레이트를 사용하면 JSON과 유사한 리터럴을 Rust 코드에 직접 포함시킬 수 있습니다.

 

이는 아래와 같이 편리한 문법을 가능하게 하는 컴파일러 확장입니다:

#![feature(plugin)]
#![plugin(json_macros)]

let config = json!({
    "hostname": "localhost",
    "port": 6543,
    "allowed_methods": ["get", "post"],
});

 

이렇게 하면 JSON 문자열을 작성하는 것보다 훨씬 간편하게 설정을 정의할 수 있습니다.