diff --git a/fastnbt/src/lib.rs b/fastnbt/src/lib.rs index 7f001b2..e422e28 100644 --- a/fastnbt/src/lib.rs +++ b/fastnbt/src/lib.rs @@ -253,9 +253,20 @@ pub fn to_writer(writer: W, v: &T) -> Result<()> { } /// Options for customizing serialization. -#[derive(Default, Clone)] +#[derive(Clone)] pub struct SerOpts { root_name: String, + /// Whether to include the root compound name. + serialize_root_name: bool, +} + +impl Default for SerOpts { + fn default() -> Self { + Self { + root_name: Default::default(), + serialize_root_name: true, + } + } } impl SerOpts { @@ -264,11 +275,21 @@ impl SerOpts { Default::default() } + pub fn network_nbt() -> Self { + Self::new().serialize_root_compound_name(false) + } + + pub fn serialize_root_compound_name(mut self, serialize_root_name: bool) -> Self { + self.serialize_root_name = serialize_root_name; + self + } + /// Set the root name (top level) of the compound. In most Minecraft data /// structures this is the empty string. The [`ser`][`crate::ser`] module /// contains an example. pub fn root_name(mut self, root_name: impl Into) -> Self { self.root_name = root_name.into(); + self.serialize_root_name = true; self } } @@ -281,6 +302,7 @@ pub fn to_bytes_with_opts(v: &T, opts: SerOpts) -> Result> let mut serializer = Serializer { writer: &mut result, root_name: opts.root_name, + serialize_root_name: opts.serialize_root_name, }; v.serialize(&mut serializer)?; Ok(result) @@ -293,6 +315,7 @@ pub fn to_writer_with_opts(writer: W, v: &T, opts: SerOp let mut serializer = Serializer { writer, root_name: opts.root_name, + serialize_root_name: opts.serialize_root_name, }; v.serialize(&mut serializer)?; Ok(()) diff --git a/fastnbt/src/ser/serializer.rs b/fastnbt/src/ser/serializer.rs index 9057df9..8c82914 100644 --- a/fastnbt/src/ser/serializer.rs +++ b/fastnbt/src/ser/serializer.rs @@ -18,7 +18,7 @@ use super::{ enum DelayedHeader { List { len: usize }, // header for a list, so element tag and list size. MapEntry { outer_name: Vec }, // header for a compound, so tag, name of compound. - Root { root_name: String }, // root compound, special because it isn't allowed to be an array type. Must be compound. + Root { root_name: Option }, // root compound, special because it isn't allowed to be an array type. Must be compound. } pub struct Serializer { @@ -27,6 +27,7 @@ pub struct Serializer { // Desired name of the root compound, typically an empty string. // NOTE: This is `mem:take`en, so is only valid at the start of serialization! pub(crate) root_name: String, + pub(crate) serialize_root_name: bool, } macro_rules! no_root { @@ -69,10 +70,13 @@ impl<'a, W: 'a + Write> serde::ser::Serializer for &'a mut Serializer { // Take the root name to avoid a clone. Need to be careful not to use // self.root_name elsewhere. let root_name = mem::take(&mut self.root_name); + let serialize_root_name = mem::take(&mut self.serialize_root_name); Ok(SerializerMap { ser: self, key: None, - header: Some(DelayedHeader::Root { root_name }), + header: Some(DelayedHeader::Root { + root_name: serialize_root_name.then_some(root_name), + }), trailer: Some(Tag::End), }) } @@ -143,7 +147,9 @@ fn write_header(writer: &mut impl Write, header: DelayedHeader, actual_tag: Tag) return Err(Error::no_root_compound()); } writer.write_tag(Tag::Compound)?; - writer.write_size_prefixed_str(&outer_name)?; + if let Some(outer_name) = &outer_name { + writer.write_size_prefixed_str(&outer_name)?; + } } DelayedHeader::MapEntry { ref outer_name } => { writer.write_tag(actual_tag)?; diff --git a/fastnbt/src/test/ser.rs b/fastnbt/src/test/ser.rs index 75f4b54..40a4bad 100644 --- a/fastnbt/src/test/ser.rs +++ b/fastnbt/src/test/ser.rs @@ -1,10 +1,10 @@ use std::{collections::HashMap, io::Cursor, iter::FromIterator}; use crate::{ - borrow, from_bytes, + borrow, from_bytes, from_bytes_with_opts, test::{resources::CHUNK_RAW_WITH_ENTITIES, Single, Wrap}, - to_bytes, to_bytes_with_opts, to_writer_with_opts, ByteArray, IntArray, LongArray, SerOpts, - Tag, Value, + to_bytes, to_bytes_with_opts, to_writer_with_opts, ByteArray, DeOpts, IntArray, LongArray, + SerOpts, Tag, Value, }; use serde::{ser::SerializeMap, Deserialize, Serialize}; use serde_bytes::{ByteBuf, Bytes}; @@ -936,3 +936,26 @@ fn serialize_root_with_name() { assert_eq!(actual_via_writer.into_inner(), expected); assert_eq!(actual_value, expected); } + +#[test] +fn serialize_networked_compound() { + #[derive(Serialize)] + struct Example { + networked: String, + } + + let data = Example { + networked: "compound".to_string(), + }; + let bytes: Vec = to_bytes_with_opts(&data, SerOpts::network_nbt()).unwrap(); + + assert_eq!( + bytes, + b"\ + \x0a\ + \x08\ + \x00\x09networked\ + \x00\x08compound\ + \x00" + ); +}