ebarnard / rust-plist

A rusty plist parser.
MIT License
71 stars 42 forks source link

Using nested plist::Dictionary with serde? #54

Closed madig closed 2 years ago

madig commented 4 years ago

The following code does not compile. Is there a way to use plist::Dictionary in a nested way?

use plist;
#[macro_use]
extern crate serde_derive;

fn main() {
    #[derive(Deserialize)]
    struct LayerinfoData {
        color: Option<String>,
        lib: Option<plist::Dictionary>,
    }

    let lib_dict: LayerinfoData = plist::from_bytes(r#"
    <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>color</key>
    <string>1,0.75,0,0.7</string>
    <key>lib</key>
    <dict>
      <key>com.typemytype.robofont.segmentType</key>
      <string>curve</string>
    </dict>
  </dict>
</plist>
"#.as_bytes()).unwrap();

    println!("{:?}, {:?}.", lib_dict.color, lib_dict.lib);
}

Compiler output:

error[E0277]: the trait bound `plist::dictionary::Dictionary: fontinfo::_IMPL_DESERIALIZE_FOR_FontInfo::_serde::Deserialize<'_>` is not satisfied
  --> src/layer.rs:75:17
   |
75 |                 lib: Option<plist::Dictionary>,
   |                 ^^^ the trait `fontinfo::_IMPL_DESERIALIZE_FOR_FontInfo::_serde::Deserialize<'_>` is not implemented for `plist::dictionary::Dictionary`
   |
   = note: required because of the requirements on the impl of `fontinfo::_IMPL_DESERIALIZE_FOR_FontInfo::_serde::Deserialize<'_>` for `std::option::Option<plist::dictionary::Dictionary>`
   = note: required by `fontinfo::_IMPL_DESERIALIZE_FOR_FontInfo::_serde::de::SeqAccess::next_element`

error[E0277]: the trait bound `plist::dictionary::Dictionary: fontinfo::_IMPL_DESERIALIZE_FOR_FontInfo::_serde::Deserialize<'_>` is not satisfied
  --> src/layer.rs:75:17
   |
75 |                 lib: Option<plist::Dictionary>,
   |                 ^^^ the trait `fontinfo::_IMPL_DESERIALIZE_FOR_FontInfo::_serde::Deserialize<'_>` is not implemented for `plist::dictionary::Dictionary`
   |
   = note: required because of the requirements on the impl of `fontinfo::_IMPL_DESERIALIZE_FOR_FontInfo::_serde::Deserialize<'_>` for `std::option::Option<plist::dictionary::Dictionary>`
   = note: required by `fontinfo::_IMPL_DESERIALIZE_FOR_FontInfo::_serde::de::MapAccess::next_value`
ebarnard commented 4 years ago

Unfortunately plist::Dictionary doesn't, at the moment, implement serde::Deserialize.

If you really have a plist file containing a heterogeneous collection of objects you can deserialize it into a HashMap<String, serde_json::Value>, although it is a bit of a hack.

cmyr commented 3 years ago

I'm bumping into something similar, and looking through the code i see that there was at least an attempt to implement serialize and deserialize for Dictionary. I'm curious if the decision to not follow through with this was because of some technical issue, or something else? If you would be open to having it implemented I'd be happy to dig into it...

ebarnard commented 3 years ago

Most of the Dictionary code is copy-pasted from serde_json. Implementing (de)serialize for Dictionary would mean implementing it for Value, which I’ve never got around to doing.

cmyr commented 3 years ago

okay, good to know. I'll look into getting that implemented next time I have the proper combination of energy and exasperation. 😁

madig commented 3 years ago

I think I'm running into this again. I'm trying to convert a serde_json::Value to a plist::Value. Is there any workaround? A cross-deserialization like

let data =
    serde_json::from_str::<plist::Value>(&serde_json::to_string(&value).unwrap())
        .unwrap();

needs Deserialize for plist::Value.

cmyr commented 3 years ago

I think the best approach would be to write your own conversion method that converts the various serde_json types to the appropriate plist types? Like,


fn convert(from: serde_json::Value) -> plist::Value {
    match from {
        serde_json::Value::Bool(val) => plist::Value::Boolean(val),
        serde_json::Value::Number(num) => {
            if let Some(float) = num.as_f64() {
                plist::Real(float)
            } else if let Some(uint) = num.as_u64() {
                plist::Number(uint.into())
            } else if let Some(int) = num.as_u64() {
                plist::Number(int.into())
            } else {
                unreachable!()
            }
        }
        // etc
    }
}
madig commented 3 years ago

Hm, yes, an be recursive for containers I guess...

ebarnard commented 3 years ago

Not the most elegant solution, but you could try it the other way round - serialising the serde_json::Value to a binary plist and then reading it into a Value.

let mut cursor = std::io::Cursor::new(Vec::new());
plist::to_writer_binary(&mut cursor, &value).unwrap()
cursor.set_position(0);
let data = plist::Value::from_reader(&mut cursor).unwrap();
madig commented 3 years ago

That worked, thanks! Still better than writing my own converter :)

MLKrisJohnson commented 3 years ago

PR #70 provides a fix for this issue.

ctrlcctrlv commented 2 years ago

Thank you very much for that recently pushed work @MLKrisJohnson, this was a blocker on proper data type of glifparser::Glif::lib, which is now a plist::Dictionary as it always should've been: https://github.com/MFEK/glifparser.rlib/commit/69389e67f536d7f9685313d89b7510cd88263a1b

I imagine that your work will broadly help anyone dealing with formats that have inline plist's like UFO .glif

DiegoMagdaleno commented 2 years ago

Hi, has this issue being fixed?.

If so, I suggest closing it, as it is confusing if it is supported.

Thank you :)

ctrlcctrlv commented 2 years ago

I suggest leaving it open until it's actually in a release. I'm tracking HEAD until then.

ebarnard commented 2 years ago

Released in v1.3.0.