Module fastnbt::de[][src]

This module contains a serde deserializer. It can do most of the things you would expect of a typical serde deserializer, such as deserializing into:

  • Rust structs.
  • containers like HashMap and Vec.
  • an arbitrary Value.
  • enums. For NBT typically you want either internally or untagged enums.

This deserializer only supports from_bytes. This is usually fine as most structures stored in this format are reasonably small, the largest likely being an individual Chunk which maxes out at 1 MiB compressed. This allows us to avoid excessive memory allocations.

Avoiding allocations

Due to having all the input in memory, we can avoid allocations for things like strings and vectors, instead deserializing into a reference to the input data.

This deserializer will allow you to deserialize any list of integral value to a &[u8] slice to avoid allocations. This includes the ByteArray, IntArray, LongArray types as well as a List that happens to be only one of these types. This does however mean some more effort on the implementors side to extract the real values. The fastanvil crate can potentially do some of this for you.

Other quirks

Some other quirks which may not be obvious:

  • When deserializing to unsigned types such as u32, it will be an error if a value is negative to avoid unexpected behaviour with wrap-around. This does not apply to deserializing lists of integrals to u8 slice or vectors.
  • You can deserialize a field to the unit type (). This ignores the value but ensures that it existed.
  • You cannot deserialize bytes directly into anything other than a struct or similar object eg HashMap. This is due to a misalignment between the NBT format and Rust's types. Attempting to will give a NoRootCompound error.

Example Minecraft types

This section demonstrates writing types for a few real Minecraft structures.

Extracting entities as an enum

This demonstrates the type that you would need to write in order to extract some subset of entities. This uses a tagged enum in serde, meaning that it will look for a certain field in the structure to tell it what enum variant to deserialize into. We use serde's other attribute to not error when an unknown entity type is found.

use serde::Deserialize;

#[derive(Deserialize, Debug)]
#[serde(tag = "id")]
enum Entity {
   #[serde(rename = "minecraft:bat")]
   Bat {
       #[serde(rename = "BatFlags")]
       bat_flags: i8,
   },

   #[serde(rename = "minecraft:creeper")]
   Creeper { ignited: i8 },

   // Entities we haven't coded end up as just 'unknown'.
   #[serde(other)]
   Unknown,
}

Capture unknown entities

If you need to capture all entity types, but do not wish to manually type all of them, you can wrap the above entity type in an untagged enum.

use serde::Deserialize;
use fastnbt::Value;

#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum Entity {
    Known(KnownEntity),
    Unknown(Value),
}
#[derive(Deserialize, Debug)]
#[serde(tag = "id")]
enum KnownEntity {
    #[serde(rename = "minecraft:bat")]
    Bat {
        #[serde(rename = "BatFlags")]
        bat_flags: i8,
    },
    #[serde(rename = "minecraft:creeper")]
    Creeper { ignited: i8 },
}

Avoiding allocations in a Chunk

This example shows how to avoid some allocations. The Section type below contains the block states which stores the state of part of the Minecraft world. In NBT this is a complicated backed bits type stored as an array of longs (i64). We avoid allocating a vector for this by storing it as a &[u8] instead. We can't safely store it as &[i64] due to memory alignment constraints. The fastanvil crate has a PackedBits type that can handle the unpacking of these block states.

#[derive(Deserialize)]
struct Chunk<'a> {
    #[serde(rename = "Level")]
    #[serde(borrow)]
    level: Level<'a>,
}

#[derive(Deserialize)]
struct Level<'a> {
    #[serde(rename = "Sections")]
    #[serde(borrow)]
    pub sections: Option<Vec<Section<'a>>>,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct Section<'a> {
    #[serde(borrow)]
    pub block_states: Option<&'a [u8]>,
}

Unit variant enum from status of chunk

use serde::Deserialize;

#[derive(Deserialize)]
struct Chunk {
    #[serde(rename = "Level")]
    level: Level,
}

#[derive(Deserialize)]
struct Level {
    #[serde(rename = "Status")]
    status: Status,
}

#[derive(Deserialize, PartialEq, Debug)]
#[serde(rename_all = "snake_case")]
enum Status {
    Empty,
    StructureStarts,
    StructureReferences,
    Biomes,
    Noise,
    Surface,
    Carvers,
    LiquidCarvers,
    Features,
    Light,
    Spawn,
    Heightmaps,
    Full,
}

Structs

Deserializer

Deserializer for NBT data. See the de module for more information.

Functions

from_bytes

Deserialize into a T from some NBT data. See the de module for more information.