[Solved]How to decode different value types using rmp-serialize


#1

Hi,

I’m trying to encode/decode multiple value types to the same field, using rmp-serialize to encode an Enum of the types that I want to support.

Encoding seems fine, if a little ugly. When decoding I can’t figure out how to extract the value from the stream with the correct type.

Example code is below. I’m new to Rust so I’d welcome any general comments as well as help on the specific issue. For instance, is using an Enum in this way even the right thing to be doing?

Thanks,

Matt

extern crate rmp as msgpack;
extern crate rmp_serialize;
extern crate rustc_serialize;

use rustc_serialize::Encodable;
use std::collections::HashMap;

#[derive(Debug)]
enum AttributeValue {
    S(String),
    I(i32),
    F(f64),
}

#[derive(Debug)]
struct Attribute {
    value: AttributeValue,
    description: String
}

impl rustc_serialize::Encodable for Attribute {
    fn encode<S: rustc_serialize::Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
        s.emit_map(2, |s| {
            //description
            try!(s.emit_map_elt_key(0, |s| { "description".encode(s) }));
            try!(s.emit_map_elt_val(0, |s| { self.description.encode(s) }));
            //value
            try!(s.emit_map_elt_key(1, |s| { "value".encode(s) }));
            try!(s.emit_map_elt_val(1, |s| { 
                match self.value {
                    AttributeValue::S(ref x) => s.emit_str(&x),
                    AttributeValue::I(x) => s.emit_i32(x),
                    AttributeValue::F(x) => s.emit_f64(x),
                }
            }));
            Ok(())
        })
    }    
}

impl rustc_serialize::Decodable for Attribute
{
    fn decode<D: rustc_serialize::Decoder>(d: &mut D) -> Result<Attribute, D::Error> {
        d.read_map(|d, len| {
            println!("Attr with {} members", len);
            let mut description = "".to_string();
            let mut value = AttributeValue::I(0);
            for i in 0..len {
                let key: String = try!(d.read_map_elt_key(i, |d| rustc_serialize::Decodable::decode(d)));
                let keystr = key.as_str();
                match keystr {
                    // XXX: Value fails 
                    "value" => {
                        let raw: AttributeValue = try!(d.read_map_elt_val(i, |d| rustc_serialize::Decodable::decode(d)));
                        value = raw
                    }, 
                    "description" => description = try!(d.read_map_elt_val(i, |d| rustc_serialize::Decodable::decode(d))),
                    _ => println!("Unexpected key {} in attr", keystr)
                }
            }
            let attr = Attribute {description: description, value: value}; 
            println!("Created attr {:?}", attr); 
            Ok(attr)
        })
    }
}

impl rustc_serialize::Decodable for AttributeValue
{
    fn decode<D: rustc_serialize::Decoder>(d: &mut D) -> Result<AttributeValue, D::Error> {
        // let reader = d.get_ref();
        match d.read_i32() {
            Ok(val) => return Ok(AttributeValue::I(val)),
            Err(_) => {}
        }
        match d.read_f64() {
            Ok(val) => return Ok(AttributeValue::F(val)),
            Err(_) => {}
        }
        match d.read_str() {
            Ok(val) => return Ok(AttributeValue::S(val)),
            Err(_) => {}
        }
        panic!("Fail");
    }
}

fn main() {
    let age = Attribute {
        value: AttributeValue::I(19),
        description: "A persons age.".to_string()
    };
    let height = Attribute {
        value: AttributeValue::F(1.85),
        description: "Height in metres.".to_string()
    };
    let name = Attribute {
        value: AttributeValue::S("Matt".to_string()),
        description: "Name".to_string()
    };
    let mut attrs = HashMap::new();
    attrs.insert("age".to_string(), age);
    attrs.insert("height".to_string(), height);
    attrs.insert("name".to_string(), name);
	
    let mut buf: Vec<u8> = Vec::new();
    attrs.encode(&mut rmp_serialize::encode::Encoder::new(&mut buf));
    println!("{:#?}", attrs);
	println!("Encoded to MessagePack: {:?}", buf);

    let mut decoder = rmp_serialize::decode::Decoder::new(&buf[..]);
    let decoded: HashMap<String, Attribute> = 
            match rustc_serialize::Decodable::decode(&mut decoder) {
        Ok(val) => {
            println!("{:?}", val);
            val  
        },
        Err(e) => {
            println!("{:?}", e);
            HashMap::new()
        }
    };
}

Resulting output:

                        ^~~~~~~
src/main.rs:107:5: 107:70 warning: unused result which must be used, #[warn(unused_must_use)] on by default
src/main.rs:107     attrs.encode(&mut rmp_serialize::encode::Encoder::new(&mut buf));
                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     Running `target/debug/encode-decode`
{
    "age": Attribute {
        value: I(
            19
        ),
        description: "A persons age."
    },
    "name": Attribute {
        value: S(
            "Matt"
        ),
        description: "Name"
    },
    "height": Attribute {
        value: F(
            1.85
        ),
        description: "Height in metres."
    }
}
Encoded to MessagePack: [131, 163, 97, 103, 101, 130, 171, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 174, 65, 32, 112, 101, 114, 115, 111, 110, 115, 32, 97, 103, 101, 46, 165, 118, 97, 108, 117, 101, 19, 164, 110, 97, 109, 101, 130, 171, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 164, 78, 97, 109, 101, 165, 118, 97, 108, 117, 101, 164, 77, 97, 116, 116, 166, 104, 101, 105, 103, 104, 116, 130, 171, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 177, 72, 101, 105, 103, 104, 116, 32, 105, 110, 32, 109, 101, 116, 114, 101, 115, 46, 165, 118, 97, 108, 117, 101, 203, 63, 253, 153, 153, 153, 153, 153, 154]
Attr with 2 members
Created attr Attribute { value: I(19), description: "A persons age." }
Attr with 2 members
thread '<main>' panicked at 'Fail', src/main.rs:84
note: Run with `RUST_BACKTRACE=1` for a backtrace.
Process didn't exit successfully: `target/debug/encode-decode` (exit code: 101)


#2

I don’t know exactly what your constraints are, but it might make more sense to use the low-level interface to rmp, which will give you a MessagePack enum so you won’t have to roll your own enum type.


#3

Thanks for the answer :slightly_smiling:

I’ve rewritten my decode function to use the rmp:: raw functionality rather than trying to use serialize. It’s a lot of unpacking of Enums but it works :slightly_smiling:

I’ll try and make it a bit less ugly!

Cheers