diff --git a/src/world/block_entity.rs b/src/world/block_entity.rs
index 6ad58a1..589a53c 100644
--- a/src/world/block_entity.rs
+++ b/src/world/block_entity.rs
@@ -41,10 +41,15 @@ pub struct Sign {
 
 impl Sign {
 	/// Processes a [de::BlockEntitySign] into a [Sign]
-	fn new(sign: &de::BlockEntitySign, kind: SignKind, material: Option<String>) -> Sign {
+	fn new(
+		sign: &de::BlockEntitySign,
+		kind: SignKind,
+		material: Option<String>,
+		data_version: u32,
+	) -> Sign {
 		let (front_text, back_text) = sign.text();
-		let front_text = front_text.decode();
-		let back_text = back_text.decode();
+		let front_text = front_text.decode(data_version);
+		let back_text = back_text.decode(data_version);
 		Sign {
 			kind,
 			material,
@@ -78,7 +83,11 @@ pub struct BlockEntity {
 
 impl BlockEntity {
 	/// Processes a [de::BlockEntity] into a [BlockEntity]
-	pub fn new(entity: &de::BlockEntity, block_type: Option<&BlockType>) -> Option<Self> {
+	pub fn new(
+		entity: &de::BlockEntity,
+		block_type: Option<&BlockType>,
+		data_version: u32,
+	) -> Option<Self> {
 		let wall_sign = block_type
 			.map(|block_type| block_type.block_color.is(BlockFlag::WallSign))
 			.unwrap_or_default();
@@ -92,7 +101,7 @@ impl BlockEntity {
 		let material = block_type
 			.as_ref()
 			.and_then(|block_type| block_type.sign_material.as_ref());
-		let data = BlockEntityData::Sign(Sign::new(sign, kind, material.cloned()));
+		let data = BlockEntityData::Sign(Sign::new(sign, kind, material.cloned(), data_version));
 
 		Some(BlockEntity {
 			x: entity.x,
diff --git a/src/world/chunk.rs b/src/world/chunk.rs
index c4744d0..311fca8 100644
--- a/src/world/chunk.rs
+++ b/src/world/chunk.rs
@@ -58,6 +58,8 @@ pub struct Chunk<'a> {
 	inner: ChunkInner<'a>,
 	/// Unprocessed block entities
 	block_entities: &'a Vec<de::BlockEntity>,
+	/// Chunk data version
+	data_version: u32,
 }
 
 impl<'a> Chunk<'a> {
@@ -87,6 +89,7 @@ impl<'a> Chunk<'a> {
 			Chunk {
 				inner,
 				block_entities,
+				data_version,
 			},
 			has_unknown,
 		))
@@ -292,7 +295,11 @@ impl<'a> Chunk<'a> {
 			.iter()
 			.map(|block_entity| {
 				let block_type = self.block_type_at_block_entity(block_entity)?;
-				Ok(BlockEntity::new(block_entity, block_type))
+				Ok(BlockEntity::new(
+					block_entity,
+					block_type,
+					self.data_version,
+				))
 			})
 			.collect::<Result<_>>()?;
 		Ok(entities.into_iter().flatten().collect())
diff --git a/src/world/sign.rs b/src/world/sign.rs
index c913b6f..8e4e670 100644
--- a/src/world/sign.rs
+++ b/src/world/sign.rs
@@ -49,7 +49,7 @@ static DYE_COLORS: phf::Map<&'static str, Color> = phf::phf_map! {
 
 impl RawSignText<'_> {
 	/// Decodes the [RawSignText] into a [SignText]
-	pub fn decode(&self) -> SignText {
+	pub fn decode(&self, data_version: u32) -> SignText {
 		let color = self
 			.color
 			.map(|c| DYE_COLORS.get(c).copied().unwrap_or(DEFAULT_COLOR));
@@ -60,7 +60,7 @@ impl RawSignText<'_> {
 		SignText(
 			self.messages
 				.iter()
-				.map(|message| message.deserialize().linearize(&parent))
+				.map(|message| message.deserialize(data_version).linearize(&parent))
 				.collect(),
 		)
 	}
diff --git a/src/world/text_value.rs b/src/world/text_value.rs
index 336b1b1..75defc4 100644
--- a/src/world/text_value.rs
+++ b/src/world/text_value.rs
@@ -171,12 +171,20 @@ impl Default for DeserializedText {
 
 /// Minecraft raw text value
 #[derive(Debug, Deserialize)]
-pub struct TextValue(pub String);
+pub struct TextValue(pub fastnbt::Value);
 
 impl TextValue {
 	/// Deserializes a [TextValue] into a [DeserializedText]
-	pub fn deserialize(&self) -> DeserializedText {
-		serde_json::from_str(&self.0).unwrap_or_default()
+	pub fn deserialize(&self, data_version: u32) -> DeserializedText {
+		if data_version < 4290 {
+			if let fastnbt::Value::String(json) = &self.0 {
+				if let Ok(content) = serde_json::from_str(json) {
+					return content;
+				}
+			}
+		}
+
+		fastnbt::from_value(&self.0).unwrap_or_default()
 	}
 }