dtolnay / serde-yaml

Strongly typed YAML library for Rust
Apache License 2.0
965 stars 164 forks source link

Support for serializing flow sequences #303

Open cadubentzen opened 2 years ago

cadubentzen commented 2 years ago

It would be amazing to be able to serialize flow sequences, especially for Vec, as I'm trying to use serde-yaml to serialize some 64-byte sequences in a compact format.

Would a PR for this be welcome, and any ideas on how to signal that via the API?

anthrotype commented 1 year ago

yes, that would be nice to have in serde-yaml! In Python's PyYAML there's a default_flow_style option which controls whether to output dicts/sequences always in block (False) or flow (True) style, or, most usefully, if set to None value, it outputs flow style only for the collections that consists only of scalars, e.g.:

    contours:
    - - x: 222.0
        y: -227.0
        typ: Line
      - x: 329.0
        y: -227.0
        typ: Line
      - x: 329.0
        y: 757.0
        typ: Line
      - x: 222.0
        y: 757.0
        typ: Line

... would be serialized like this with default_flow_style=None in PyYAML:

    contours:
    - - {typ: Line, x: 222.0, y: -227.0}
      - {typ: Line, x: 329.0, y: -227.0}
      - {typ: Line, x: 329.0, y: 757.0}
      - {typ: Line, x: 222.0, y: 757.0}

Is there interest in adding support for something like this in serde_yaml?

goertzenator commented 1 year ago

Yes please! Here's some output I generated today... not ideal.

- - - - -150
      - -150
    - - -150
      - -150
    - - -150
      - -150
    - - -150
      - -150
    - - -150
      - -150
    - - -150
      - -150
    - - -150
      - -150
    - - -150
      - -150
NiceneNerd commented 1 year ago

Similar case here, where this:

      Vec2: !vec2
      - 1.2
      - 5.1
      Vec3: !vec3
      - 1.2
      - 5.1
      - 1.9
      Vec4: !vec4
      - 4.9
      - 1.1
      - 2.3
      - 3.5
      Color: !color
      - 0.7
      - 0.8
      - 0.9
      - 1.0

Has been more nicely represented by other tools as this:

      Vec2: !vec2 [1.2. 5.1]
      Vec3: !vec3 [1.2, 5.1, 1.9]
      Vec4: !vec4 [4.9, 1.1, 2.3, 3.5]
      Color: !color [0.7, 0.8, 0.9, 1.0]
NiceneNerd commented 1 year ago

So I've been investigating making a PR for this, but TBH I'm having a pretty hard time figuring out a way to implement. Presumably we don't want flow-only: that's basically JSON. Nor would we want the flow style to automatically be used under a certain number of elements, as depending on the content of the elements this may or may not produce desirable output (e.g. an array of three gigantic maps should probably not use the flow style).

The only way that would seems reasonable would be to factor both length and content (e.g. I've seen conditions used in other projects like, "If there are less than 10 children and none of them are an array or map, use flow style"). But I am not sure this is at all possible the way Serde and serde-yaml are set up, as it would require you to look ahead to whether the children of a sequence or map will themselves have children while initiating the sequence or map.

goertzenator commented 1 year ago

Take the following idea with a gain of salt.. I haven't done Rust and serde work for a few months:

  1. Add a FlowWrapper type that can wrap another Serialize type.
  2. Mark fields that should be flow as #[serde(into = "FlowWrapper<MyField>")]
  3. The YAML serlializer specializes on FlowWrapper to serialize in flow style (not sure if this is even possible).

Side note: For my use case listed above I ended up writing my own little function to emit YAML (ie, not using serde at all). It was simple and provided exactly what I wanted.

hasezoey commented 1 year ago

i would also like to see this being implemented and being able to manually specify which type should be used

example:

#[derive(Debug, Serialize, Deserialize)]
struct Test {
    pub list: Vec<Testy>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum Testy {
    String(String),
        #[serde(serialize_with = "serde_yaml::IntoFlow")] // maybe even something more convenient?
    Tuple(String, bool),
}

and produce the following:

list:
  - Test
  - [Test, true]

instead of

list:
  - Test
  - - Test
    - true