json icon indicating copy to clipboard operation
json copied to clipboard

Untagged Enum Bug (?)

Open aramrw opened this issue 1 year ago • 2 comments

pub type Entries = Vec<Vec<EntryItem>>;
// Untagged Enum is the issue
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum EntryItem {
    Str(String),
    Int(u128),
    ContentVec(Vec<serde_json::Value>),
}
[
    [
        "手信号",
        "てしんごう",
        "",
        "",
        0,
        [
            {
                "type": "structured-content",
                "content": [{/* long asf */}]
            }
        ]
     ]
]
Error("data did not match any variant of untagged enum EntryItem", line: 9, column: 8),
)
[src/main.rs:21:5] serde_json::from_str::<Entries2>(mystr) = Ok(
    [
        [
            Str(
                "手信号",
            ),
            Str(
                "てしんごう",
            ),
            Str(
                "",
            ),
            Str(
                "",
            ),
            ContentVec(
                Number(0),
            ),
            ContentVec(
                Array [
                    Object {
                        "content": String("[{/* long asf */}]"),
                        "type": String("structured-content"),
                    },
                ],
            ),
        ],
    ],
)

When the type is ContentVec(Vec<Value>), it throws the error above. However, when I set ContentVec(Value) it deserializes fine?

Is that not a vector right after the 0 in the json?

And when I print it out the value as ContentVec(Value) instead of ContentVec(Vec<Value>), it is clearly an array:

Array [Object {
  "content": Object {
    "content": String("連ね歌"), 
    "href": String("?query=連ね歌&wildcards=off"), 
    "tag": String("a")}, 
    "type": String("structured-content")
  }
] 

I even added this if statement (when set as ContentVec(Value) ), to make sure it is an array. Again, it prints out with no errors! (the same thing as above).

if let EntryItem::ContentVec(value) = &entry[5] {
    // This works
    if value.is_array() {
      println!("{x:?}")
    }
}

After asking a handful of people, everybody agreed this is not the intended behavior. If it is, can I can get an explanation as to what is going on here...?

Please try the code out for yourself. https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6811facc5a4e83d7a46847f37da9d569

aramrw avatar Jul 08 '24 02:07 aramrw

I think this is caused by https://github.com/serde-rs/serde/issues/2230. If you use u64 instead of u128 it works. You see that the Int(u128), variant of the enum is not being deserialized.

ContentVec(
    Number(0),
),

jonasbb avatar Jul 08 '24 12:07 jonasbb

The issue indeed seems to be for enum wrapped u128. Here is a minimal example:

pub enum Value { U128(u128) }

impl<'de> serde::Deserialize<'de> for Value {
  fn deserialize<D>(d: D) -> Result<Self, D::Error>
  where D: serde::Deserializer<'de> {
    struct GeneratedVisitor;
    impl<'de> serde::de::Visitor<'de> for GeneratedVisitor {
      type Value = Value;
      fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "only supported values")
      }
      fn visit_u128<E>(self, v: u128) -> Result<Self::Value, E>
      where E: serde::de::Error {
        Ok(Value::U128(v))
      }
    }

    d.deserialize_any(GeneratedVisitor)
  }
}


#[test]
fn can_parse_wrapped_u128() {
  let value: Value = serde_json::from_str("107982289906077432706421448461854884600").unwrap();
}

This fails with the following error and seems to incorrectly guess the type while parsing:

called `Result::unwrap()` on an `Err` value: Error("invalid type: floating point `1.0798228990607744e38`, expected only supported values", line: 1, column: 39)

In addition to jonasbb workaround but you want to preserve the full range of a u128, you could sum 2x u64, store it as text and to parse it outside of serde, or to serialize the u128 into Uuid text via as_u128 and from_u128.

litvinav avatar Oct 13 '24 09:10 litvinav