Skip to content

Commit d3f9699

Browse files
add reflection serialization example (#20940)
# Objective It is a very common[0] misconception that serde's Serialize/Deserialize derives are required for reflection (de)serialization. This is not true, and can lead to unexpectedly changing the serialization behavior in a way that doesn't get exposed via reflection information. Having an example to point to would be a useful resource when talking about this. [0]: This is why Avian has Serialize/Deserialize on all types, including marker types; This also cropped up in PRs to new BRP functionality. ## Solution Implement an example showing deserialization and serialization for a type that only implements `Reflect`, and not serde's `Serialize` or `Deserialize`. ## Testing ``` cargo run --example serialization 2025-09-09T19:08:28.258523Z INFO serialization: reflect_value=DynamicStruct(serialization::Player { health: 50, name: "BevyPlayerOne" }) 2025-09-09T19:08:28.258770Z INFO serialization: player=serialization::Player { name: "BevyPlayerOne", health: 50 } 2025-09-09T19:08:28.259382Z INFO serialization: json="{\"serialization::Player\":{\"name\":\"BevyPlayerSerialize\",\"health\":80}}" ```
1 parent d87e5c3 commit d3f9699

File tree

3 files changed

+87
-0
lines changed

3 files changed

+87
-0
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2719,6 +2719,17 @@ description = "Demonstrates how reflection in Bevy provides a way to dynamically
27192719
category = "Reflection"
27202720
wasm = false
27212721

2722+
[[example]]
2723+
name = "serialization"
2724+
path = "examples/reflection/serialization.rs"
2725+
doc-scrape-examples = true
2726+
2727+
[package.metadata.example.serialization]
2728+
name = "Serialization"
2729+
description = "Demonstrates serialization and deserialization using reflection without serde's Serialize/Deserialize traits"
2730+
category = "Reflection"
2731+
wasm = false
2732+
27222733
[[example]]
27232734
name = "custom_attributes"
27242735
path = "examples/reflection/custom_attributes.rs"

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ Example | Description
433433
[Generic Reflection](../examples/reflection/generic_reflection.rs) | Registers concrete instances of generic types that may be used with reflection
434434
[Reflection](../examples/reflection/reflection.rs) | Demonstrates how reflection in Bevy provides a way to dynamically interact with Rust types
435435
[Reflection Types](../examples/reflection/reflection_types.rs) | Illustrates the various reflection types available
436+
[Serialization](../examples/reflection/serialization.rs) | Demonstrates serialization and deserialization using reflection without serde's Serialize/Deserialize traits
436437
[Type Data](../examples/reflection/type_data.rs) | Demonstrates how to create and use type data
437438

438439
### Remote Protocol
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//! Illustrates how "reflection" serialization works in Bevy.
2+
//!
3+
//! Deriving `Reflect` will also register `SerializationData`,
4+
//! which powers reflect (de)serialization.
5+
//! Serializing reflected data *does not* require deriving serde's
6+
//! Serialize and Deserialize implementations.
7+
8+
use bevy::{
9+
prelude::*,
10+
reflect::serde::{ReflectDeserializer, ReflectSerializer},
11+
};
12+
use serde::de::DeserializeSeed;
13+
14+
fn main() {
15+
App::new()
16+
.add_plugins(DefaultPlugins)
17+
.add_systems(Startup, (deserialize, serialize).chain())
18+
.run();
19+
}
20+
21+
/// Deriving `Reflect` includes reflecting `SerializationData`
22+
#[derive(Reflect)]
23+
pub struct Player {
24+
name: String,
25+
health: u32,
26+
}
27+
28+
const PLAYER_JSON: &str = r#"{
29+
"serialization::Player": {
30+
"name": "BevyPlayerOne",
31+
"health": 50
32+
}
33+
}"#;
34+
35+
fn deserialize(type_registry: Res<AppTypeRegistry>) {
36+
let type_registry = type_registry.read();
37+
38+
// a serde_json::Value that might have come from an API
39+
let value: serde_json::Value = serde_json::from_str(PLAYER_JSON).unwrap();
40+
41+
// alternatively, `TypedReflectDeserializer` can be used if the type
42+
// is known.
43+
let deserializer = ReflectDeserializer::new(&type_registry);
44+
// deserialize
45+
let reflect_value = deserializer.deserialize(value).unwrap();
46+
// If Player implemented additional functionality, like Component,
47+
// this reflect_value could be used with commands.insert_reflect
48+
info!(?reflect_value);
49+
50+
// `FromReflect` and `ReflectFromReflect` can yield a concrete value.
51+
let type_id = reflect_value.get_represented_type_info().unwrap().type_id();
52+
let reflect_from_reflect = type_registry
53+
.get_type_data::<ReflectFromReflect>(type_id)
54+
.unwrap();
55+
let player: Box<dyn Reflect> = reflect_from_reflect
56+
.from_reflect(reflect_value.as_partial_reflect())
57+
.unwrap();
58+
info!(?player);
59+
}
60+
61+
fn serialize(type_registry: Res<AppTypeRegistry>) {
62+
let type_registry = type_registry.read();
63+
64+
// a concrete value
65+
let value = Player {
66+
name: "BevyPlayerSerialize".to_string(),
67+
health: 80,
68+
};
69+
70+
// By default, all derived `Reflect` types can be serialized using serde. No need to derive
71+
// Serialize!
72+
let serializer = ReflectSerializer::new(&value, &type_registry);
73+
let json = serde_json::to_string(&serializer).unwrap();
74+
info!(?json);
75+
}

0 commit comments

Comments
 (0)