quartz_nbt icon indicating copy to clipboard operation
quartz_nbt copied to clipboard

Quartz_nbt Deserializer misses fields depending on the Deserialize implementation of another field.

Open Rossterd opened this issue 3 years ago • 1 comments

I'm using quartz_nbt together with serde to deserialize chunk information, and I am getting the following possibly erroneous behaviour.

#[derive(Serialize, Deserialize, Debug)]
pub struct ChunkV1_17 {
    #[serde(rename = "Level")]
    pub level: LevelV1_17,
    #[serde(rename = "DataVersion")]
    pub data_version: i32,
}

pub struct LevelV1_17 {
    #[serde(rename = "xPos")]
    x_pos: i32,

    #[serde(rename = "Biomes")]
    biomes: Option<BiomesV1_17>,
}

type BiomesV1_17 = Array3D<u8, 4, 64, 4>;

impl Serialize for BiomesV1_17 {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        todo!()
    }
}

impl<'de> Deserialize<'de> for BiomesV1_17 {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {   
        let data: Vec<i32> = Deserialize::deserialize(deserializer)?;

        Ok( BiomesV1_17::default() )
        
    }
}

Works, but if I don't deserialize any data in my deserialize function like so:

#[derive(Serialize, Deserialize, Debug)]
pub struct ChunkV1_17 {
    #[serde(rename = "Level")]
    pub level: LevelV1_17,
    #[serde(rename = "DataVersion")]
    pub data_version: i32,
}

pub struct LevelV1_17 {
    #[serde(rename = "xPos")]
    x_pos: i32,

    #[serde(rename = "Biomes")]
    biomes: Option<BiomesV1_17>,
}

type BiomesV1_17 = Array3D<u8, 4, 64, 4>;

impl Serialize for BiomesV1_17 {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        todo!()
    }
}

impl<'de> Deserialize<'de> for BiomesV1_17 {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {   
        // let data: Vec<i32> = Deserialize::deserialize(deserializer)?;

        Ok( BiomesV1_17::default() )
        
    }
}

I get the following error: thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom("missing field `xPos`")'

Rossterd avatar Feb 16 '22 13:02 Rossterd

The reason this happens is that the NBT deserializer reads bytes as requested, i.e. it does not parse the NBT before mapping it into Rust's data model. What's happening is that serde matches a NBT field named "biomes" to your BiomesV1_17 type, and then calls your type's Deserialize implementation, expecting it to parse all bytes associated with the value of that field so that the next bytes in the reader are part of the next tag name. In the second example, when it tries to parse the next tag name, it runs into biome data, so it gets confused. All of this is to say that if your Deserialize implementation gets invoked, you need to ensure that all the bytes constituting the value of your type are read.

Cassy343 avatar Feb 16 '22 19:02 Cassy343