-
Rust: serde json Enum 공부컴퓨터/Rust 2021. 7. 28. 19:20728x90반응형
serde
할 것
지난 글에서 trait를 이용해서 Serialize를 하였는데 (dyn erased_serde::Serialize)
여기서 Deserialize까지 하고 싶었더니 복잡하게 되었다.
polymorphism을 파이썬처럼 생각하고 짰더니 어려워진 것 같다.
Rust에서는 enum이 하스켈처럼 다양하게 사용될 수 있는 것 같다. 자바에서 enum과 정말 다르다.
간단 예제
// Create an `enum` to classify a web event. Note how both // names and type information together specify the variant: // `PageLoad != PageUnload` and `KeyPress(char) != Paste(String)`. // Each is different and independent. enum WebEvent { // An `enum` may either be `unit-like`, PageLoad, PageUnload, // like tuple structs, KeyPress(char), Paste(String), // or c-like structures. Click { x: i64, y: i64 }, } // A function which takes a `WebEvent` enum as an argument and // returns nothing. fn inspect(event: WebEvent) { match event { WebEvent::PageLoad => println!("page loaded"), WebEvent::PageUnload => println!("page unloaded"), // Destructure `c` from inside the `enum`. WebEvent::KeyPress(c) => println!("pressed '{}'.", c), WebEvent::Paste(s) => println!("pasted \"{}\".", s), // Destructure `Click` into `x` and `y`. WebEvent::Click { x, y } => { println!("clicked at x={}, y={}.", x, y); }, } } fn main() { let pressed = WebEvent::KeyPress('x'); // `to_owned()` creates an owned `String` from a string slice. let pasted = WebEvent::Paste("my text".to_owned()); let click = WebEvent::Click { x: 20, y: 80 }; let load = WebEvent::PageLoad; let unload = WebEvent::PageUnload; inspect(pressed); inspect(pasted); inspect(click); inspect(load); inspect(unload); }
Enum polymorphism
trait는 외부 코드로 타입을 더 추가할 수 있지만 enum은 정해진 타입만 사용 가능하다.
trait는 컴파일 타임에 Size를 알 수 없어서 Box<dyn trait> 형식으로 trait object로 쓰여야 한다.
사용은 때에 맞게 써야 할 것 같다.
읽기 좋은 글: Polymorphism in Rust: Enum vs Trait
enum Shape { Rectangle { width: f64, height: f64 }, Circle { radius: f64 }, RightAngleTriangle { base: f64, height: f64 }, } impl Shape { fn area(&self) -> f64 { match self { Shape::Rectangle { width, height } => width * height, Shape::Circle { radius } => std::f64::consts::PI * radius.powi(2), Shape::RightAngleTriangle { base, height } => 0.5 * base * height, } } }
Serde Enum variants
serde에서 어떻게 사용할까?
JSON 데이터가 아래와 같이 되어있을 때 Vec<Object> 하나로 표현하고 싶을 수 있다.
[{"label":"CALL LABEL","action":"phone","phoneNumber":"0","messageText":"MESSAGE"}, {"label":"SHARE LABEL","action":"share"},{"label":"MSG LABEL","action":"message"}, {"label":"LABEL","action":"webLink","webLinkUrl":"https://"}]
Internally Tagged 방식을 이용해서
action에 따라 알아서 알맞은 타입으로 매칭 되게 할 수도 있다.
아래 방식으로 하면 다른 field 들은 other에 넣던가 flatten 할 수 있지만 (Playground 링크)
#[derive(Serialize, Deserialize, Debug)] #[serde(tag = "action")] pub enum Button { #[serde(rename = "phone")] Call(CallButton), #[serde(rename = "link")] Link(LinkButton), #[serde(rename = "share")] Share(ShareButton), #[serde(rename = "message")] Msg(MsgButton), }
똑같이 label, action 필드 두 개를 갖지만 다른 버튼일 수도 있다.
{"label":"SHARE LABEL","action":"share"},{"label":"MSG LABEL","action":"message"}
ShareButton과 MsgButton으로 구분하고 싶어서
직접 Deserialize를 구현해보았다. Rust를 많이 사용하지 않아서 난잡해 보인다.
Button을 다 합쳐서 그냥 skip_if attribute를 사용해서 통합해도 될 것 같다.
untagged는 { "some" : { ... } } 처럼 외부에 이름이 있는 게 아니라 이름 없이
알아서 구분하라고 지정하고 싶을 때 쓸 수 있다.
코드
use serde::{de::Error, Deserialize, Deserializer, Serialize}; use serde_json::{Map, Value}; use std::collections::HashMap; #[derive(Serialize, Debug)] #[serde(untagged)] pub enum Button { Call(CallButton), Link(LinkButton), Share(ShareButton), Msg(MsgButton), } impl<'de> Deserialize<'de> for Button { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>, { let text: Map<String, Value> = Map::deserialize(deserializer)?; let mut keys = HashMap::new(); for (key, value) in &text { let _value = value.as_str().unwrap(); keys.insert(key.to_owned(), _value.to_string()); } let mut button: Button = match text.get("action").unwrap().as_str() { Some("webLink") => Self::Link(LinkButton::new("label".to_string())), Some("share") => Self::Share(ShareButton::new("label".to_string())), Some("message") => Self::Msg(MsgButton::new("label".to_string())), Some("phone") => Self::Call(CallButton::new("label".to_string())), _ => Self::Msg(MsgButton::new("label".to_string())), }; for (key, value) in &text { let _value = value.as_str().unwrap(); match &mut button { Self::Link(link) => match link { LinkButton { ref mut label, ref action, ref mut web_link_url, ref mut message_text, } => { if let Some(l) = keys.get("label") { *label = l.to_string(); } if let Some(l) = keys.get("webLinkUrl") { *web_link_url = l.to_string(); } if let Some(l) = keys.get("messageText") { *message_text = Some(l.to_string()); } } }, Self::Share(share) => match share { ShareButton { ref mut label, ref action, ref mut message_text, } => { if let Some(l) = keys.get("label") { *label = l.to_string(); } if let Some(l) = keys.get("messageText") { *message_text = Some(l.to_string()); } } }, Self::Msg(msg) => match msg { MsgButton { ref mut label, ref action, ref mut message_text, } => { if let Some(l) = keys.get("label") { *label = l.to_string(); } if let Some(l) = keys.get("messageText") { *message_text = Some(l.to_string()); } } }, Self::Call(call) => match call { CallButton { ref mut label, ref action, ref mut phone_number, ref mut message_text, } => { if let Some(l) = keys.get("label") { *label = l.to_string(); } if let Some(l) = keys.get("phoneNumber") { *phone_number = l.to_string(); } if let Some(l) = keys.get("messageText") { *message_text = Some(l.to_string()); } } }, } } Ok(button) } } // 버튼은 아래처럼 생긴 것들이다 #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] #[serde(deny_unknown_fields)] pub struct CallButton { label: String, action: String, phone_number: String, #[serde(skip_serializing_if = "Option::is_none")] message_text: Option<String>, } impl CallButton { pub fn set_number(mut self, number: String) -> Self { self.phone_number = number; self } pub fn new(label: String) -> Self { CallButton { label: label, action: "phone".to_string(), phone_number: "0".to_string(), message_text: None, } } pub fn set_label(mut self, label: String) -> Self { self.label = label; self } pub fn set_msg(mut self, msg: String) -> Self { self.message_text = Some(msg); self } } // ........................................ #[cfg(test)] mod test { use super::*; #[test] fn button_enum_test() { let data = r#"[{"label":"CALL LABEL","action":"phone","phoneNumber":"0","messageText":"MESSAGE"},{"label":"SHARE LABEL","action":"share"},{"label":"MSG LABEL","action":"message"},{"label":"LABEL","action":"webLink","webLinkUrl":"https://"}]"#; let buttons: Vec<Button> = serde_json::from_str(data).unwrap(); println!("{:?}", buttons); println!("{}", serde_json::to_string_pretty(&buttons).expect("Woah")); // println!("{}", serde_json::to_string(&buttons).expect("Woah")); } }
결과
[Call(CallButton { label: "CALL LABEL", action: "phone", phone_number: "0", message_text: Some("MESSAGE") }), Share(ShareButton { label: "SHARE LABEL", action: "share", message_text: None }), Msg(MsgButton { label: "MSG LABEL", action: "message", message_text: None }), Link(LinkButton { label: "LABEL", action: "webLink", web_link_url: "https://", message_text: None })] [ { "label": "CALL LABEL", "action": "phone", "phoneNumber": "0", "messageText": "MESSAGE" }, { "label": "SHARE LABEL", "action": "share" }, { "label": "MSG LABEL", "action": "message" }, { "label": "LABEL", "action": "webLink", "webLinkUrl": "https://" } ]
match Enum variants
아래처럼 SimpleText struct에 구현해 둔 메소드들도 사용할 수 있다.
#[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] pub enum Types { List(ListCard), Basic(BasicCard), Simple(SimpleText), Carousel(Carousel), } match output { Types::Simple(s) => { map.insert("simpleText".to_string(), s.html()); } Types::Carousel(_) => { map.insert("carousel".to_string(), "carousel".to_string()); } Types::Basic(_) => { map.insert("basicCard".to_string(), "carousel".to_string()); } Types::List(_) => { map.insert("listCard".to_string(), "carousel".to_string()); } }
참고
현재 공부 정리 프로젝트
728x90'컴퓨터 > Rust' 카테고리의 다른 글
Rust: scraper를 이용한 네이버 날씨 파싱 (0) 2021.07.28 Rust: Javascript POST json 데이터 consume (0) 2021.07.25 Rust: String 한글 len() (UTF-8) (0) 2021.07.20