owengage / fastnbt

Fast serde serializer and deserializer for Minecraft's NBT and Anvil formats
MIT License
186 stars 34 forks source link

Deserializing list into tuple gives error "expected to be in list, but was in compound Some(List)" #63

Closed dzil123 closed 2 years ago

dzil123 commented 2 years ago

Affects fastnbt v2.2.0 and eb36aa3.

Normally, an NBT List of ints, len=3 can be deserialized into a tuple of (i32, i32, i32), an array of [i32; 3], or a Vec<i32>. However, nesting this list in a list of compounds causes deserialization into a tuple or array to fail with the error message "expected to be in list, but was in compound Some(List)".

In this example code, I expect all test cases to pass:

use serde::{Serialize, Deserialize};

#[derive(Deserialize, Serialize, Debug, Clone)]
struct IntTuple {
    value: (i32, i32, i32),
}

#[derive(Deserialize, Serialize, Debug, Clone)]
struct IntVec {
    value: Vec<i32>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
struct IntTupleList {
    list: Vec<IntTuple>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
struct IntVecList {
    list: Vec<IntVec>,
}

fn main() {
    // should deserialize into IntTuple and IntVec
    let nbt_direct = fastnbt::nbt!(
        {
            "value": [1, 2, 3],
        }
    );

    // should deserialize into IntTupleList and IntVecList
    let nbt_list = fastnbt::nbt!(
        {
            "list": [
                {
                    "value": [1, 2, 3],
                },
            ],
        }
    );

    // serialize Value into NBT bytes, then deserialize into given struct type

    match fastnbt::from_bytes::<IntTuple>(&fastnbt::to_bytes(&nbt_direct).unwrap()) {
        Ok(v) => println!("pass - deserialized IntTuple - {:?}", v),
        Err(err) => println!("fail - deserialized IntTuple - {:#?}", err),
    }

    match fastnbt::from_bytes::<IntVec>(&fastnbt::to_bytes(&nbt_direct).unwrap()) {
        Ok(v) => println!("pass - deserialized IntVec - {:?}", v),
        Err(err) => println!("fail - deserialized IntVec - {:#?}", err),
    }

    match fastnbt::from_bytes::<IntTupleList>(&fastnbt::to_bytes(&nbt_list).unwrap()) {
        Ok(v) => println!("pass - deserialized IntTupleList - {:?}", v),
        Err(err) => println!("fail - deserialized IntTupleList - {:#?}", err),
    }

    match fastnbt::from_bytes::<IntVecList>(&fastnbt::to_bytes(&nbt_list).unwrap()) {
        Ok(v) => println!("pass - deserialized IntVecList - {:?}", v),
        Err(err) => println!("fail - deserialized IntVecList - {:#?}", err),
    }
}

Output:

pass - deserialized IntTuple - IntTuple { value: (1, 2, 3) }
pass - deserialized IntVec - IntVec { value: [1, 2, 3] }
fail - deserialized IntTupleList - Error(
    "expected to be in list, but was in compound Some(List)",
)
pass - deserialized IntVecList - IntVecList { list: [IntVec { value: [1, 2, 3] }] }
owengage commented 2 years ago

Hi, thats a pretty big issue. Thanks for taking the time to come up with test cases.

I'll add these to the unit tests (I'm surprised I didn't already have something along these lines) and work in a fix when I get a chance.

Thanks again!

owengage commented 2 years ago

Hi again, this turns out to be a reasonably deep issue. It is related to serde-rs#2198. For tuples and arrays the next_seed function isn't called a last time.

Fixing it will be a bit of effort that I don't have right now. A workaround is to use Vec for these like you have already shown, rather than a tuple. Sorry about that!

owengage commented 2 years ago

Fixed in fastnbt 2.3.0! Ended up rewriting the majority of the crate!