Skip to content

Commit 2db0576

Browse files
committed
Add serde support for GString, StringName, NodePath and Array
1 parent 05a1b09 commit 2db0576

File tree

9 files changed

+295
-0
lines changed

9 files changed

+295
-0
lines changed

check.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ for arg in "$@"; do
171171
echo "$HELP_TEXT"
172172
exit 0
173173
;;
174+
--use-serde)
175+
extraCargoArgs+=("--features" "serde")
176+
;;
174177
--double)
175178
extraCargoArgs+=("--features" "godot/double-precision")
176179
;;

godot-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ serde = { version = "1", features = ["derive"], optional = true }
3131
# Reverse dev dependencies so doctests can use `godot::` prefix
3232
[dev-dependencies]
3333
godot = { path = "../godot" }
34+
serde_json = { version = "1.0" }
3435

3536
[build-dependencies]
3637
godot-bindings = { path = "../godot-bindings" }

godot-core/src/builtin/array.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,3 +1063,62 @@ impl fmt::Debug for TypeInfo {
10631063
write!(f, "{:?}{}", self.variant_type, class_str)
10641064
}
10651065
}
1066+
1067+
#[cfg(feature = "serde")]
1068+
mod serialize {
1069+
use super::*;
1070+
use serde::de::{SeqAccess, Visitor};
1071+
use serde::ser::SerializeSeq;
1072+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
1073+
use std::marker::PhantomData;
1074+
1075+
impl<T: Serialize + GodotType> Serialize for Array<T> {
1076+
#[inline]
1077+
fn serialize<S>(
1078+
&self,
1079+
serializer: S,
1080+
) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
1081+
where
1082+
S: Serializer,
1083+
{
1084+
let mut sequence = serializer.serialize_seq(Some(self.len()))?;
1085+
for e in self.iter_shared() {
1086+
sequence.serialize_element(&e)?
1087+
}
1088+
sequence.end()
1089+
}
1090+
}
1091+
1092+
impl<'de, T: Deserialize<'de> + GodotType> Deserialize<'de> for Array<T> {
1093+
#[inline]
1094+
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
1095+
where
1096+
D: Deserializer<'de>,
1097+
{
1098+
struct ArrayVisitor<T>(PhantomData<T>);
1099+
impl<'de, T: Deserialize<'de> + GodotType> Visitor<'de> for ArrayVisitor<T> {
1100+
type Value = Array<T>;
1101+
1102+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> fmt::Result {
1103+
formatter.write_str(std::any::type_name::<Self::Value>())
1104+
}
1105+
1106+
fn visit_seq<A>(
1107+
self,
1108+
mut seq: A,
1109+
) -> Result<Self::Value, <A as SeqAccess<'de>>::Error>
1110+
where
1111+
A: SeqAccess<'de>,
1112+
{
1113+
let mut vec = seq.size_hint().map_or_else(Vec::new, Vec::with_capacity);
1114+
while let Some(val) = seq.next_element::<T>()? {
1115+
vec.push(val);
1116+
}
1117+
Ok(Self::Value::from(vec.as_slice()))
1118+
}
1119+
}
1120+
1121+
deserializer.deserialize_seq(ArrayVisitor::<T>(PhantomData))
1122+
}
1123+
}
1124+
}

godot-core/src/builtin/string/gstring.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,51 @@ impl From<NodePath> for GString {
313313
Self::from(&path)
314314
}
315315
}
316+
317+
#[cfg(feature = "serde")]
318+
mod serialize {
319+
use super::*;
320+
use serde::de::{Error, Visitor};
321+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
322+
use std::fmt::Formatter;
323+
324+
impl Serialize for GString {
325+
#[inline]
326+
fn serialize<S>(
327+
&self,
328+
serializer: S,
329+
) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
330+
where
331+
S: Serializer,
332+
{
333+
serializer.serialize_str(&self.to_string())
334+
}
335+
}
336+
337+
#[cfg(feature = "serde")]
338+
impl<'de> serialize::Deserialize<'de> for GString {
339+
#[inline]
340+
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
341+
where
342+
D: Deserializer<'de>,
343+
{
344+
struct GStringVisitor;
345+
impl<'de> Visitor<'de> for GStringVisitor {
346+
type Value = GString;
347+
348+
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
349+
formatter.write_str("a GString")
350+
}
351+
352+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
353+
where
354+
E: Error,
355+
{
356+
Ok(GString::from(s))
357+
}
358+
}
359+
360+
deserializer.deserialize_str(GStringVisitor)
361+
}
362+
}
363+
}

godot-core/src/builtin/string/node_path.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,61 @@ impl From<StringName> for NodePath {
150150
Self::from(GString::from(string_name))
151151
}
152152
}
153+
154+
#[cfg(feature = "serde")]
155+
mod serialize {
156+
use super::*;
157+
use serde::de::{Error, Visitor};
158+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
159+
use std::fmt::Formatter;
160+
161+
impl Serialize for NodePath {
162+
#[inline]
163+
fn serialize<S>(
164+
&self,
165+
serializer: S,
166+
) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
167+
where
168+
S: Serializer,
169+
{
170+
serializer.serialize_newtype_struct("NodePath", &*self.to_string())
171+
}
172+
}
173+
174+
impl<'de> Deserialize<'de> for NodePath {
175+
#[inline]
176+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
177+
where
178+
D: Deserializer<'de>,
179+
{
180+
struct NodePathVisitor;
181+
182+
impl<'de> Visitor<'de> for NodePathVisitor {
183+
type Value = NodePath;
184+
185+
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
186+
formatter.write_str("a NodePath")
187+
}
188+
189+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
190+
where
191+
E: Error,
192+
{
193+
Ok(NodePath::from(s))
194+
}
195+
196+
fn visit_newtype_struct<D>(
197+
self,
198+
deserializer: D,
199+
) -> Result<Self::Value, <D as Deserializer<'de>>::Error>
200+
where
201+
D: Deserializer<'de>,
202+
{
203+
deserializer.deserialize_str(self)
204+
}
205+
}
206+
207+
deserializer.deserialize_newtype_struct("NodePath", NodePathVisitor)
208+
}
209+
}
210+
}

godot-core/src/builtin/string/string_name.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,50 @@ impl From<NodePath> for StringName {
245245
Self::from(GString::from(path))
246246
}
247247
}
248+
249+
#[cfg(feature = "serde")]
250+
mod serialize {
251+
use super::*;
252+
use serde::de::{Error, Visitor};
253+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
254+
use std::fmt::Formatter;
255+
256+
impl Serialize for StringName {
257+
#[inline]
258+
fn serialize<S>(
259+
&self,
260+
serializer: S,
261+
) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
262+
where
263+
S: Serializer,
264+
{
265+
serializer.serialize_str(&self.to_string())
266+
}
267+
}
268+
269+
impl<'de> serialize::Deserialize<'de> for StringName {
270+
#[inline]
271+
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
272+
where
273+
D: Deserializer<'de>,
274+
{
275+
struct StringNameVisitor;
276+
impl<'de> Visitor<'de> for StringNameVisitor {
277+
type Value = StringName;
278+
279+
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
280+
formatter.write_str("a StringName")
281+
}
282+
283+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
284+
where
285+
E: Error,
286+
{
287+
Ok(StringName::from(s))
288+
}
289+
}
290+
291+
deserializer.deserialize_str(StringNameVisitor)
292+
}
293+
}
294+
}

itest/rust/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ crate-type = ["cdylib"]
1010

1111
[features]
1212
default = []
13+
serde = ["dep:serde", "dep:serde_json", "godot/serde"]
1314
# Do not add features here that are 1:1 forwarded to the `godot` crate.
1415
# Instead, compile itest with `--features godot/my-feature`.
1516

1617
[dependencies]
1718
godot = { path = "../../godot", default-features = false }
19+
serde = { version = "1", features = ["derive"], optional = true }
20+
serde_json = { version = "1.0", optional = true }
1821

1922
[build-dependencies]
2023
godot-bindings = { path = "../../godot-bindings" } # emit_godot_version_cfg

itest/rust/src/builtin_tests/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,6 @@ mod string {
3535
mod color_test;
3636

3737
mod convert_test;
38+
39+
#[cfg(feature = "serde")]
40+
mod serde_test;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use crate::framework::itest;
2+
use godot::builtin::{array, Array, GString, NodePath, StringName, Vector2i};
3+
use serde::{Deserialize, Serialize};
4+
5+
fn serde_roundtrip<T>(value: &T, expected_json: &str)
6+
where
7+
T: for<'a> Deserialize<'a> + Serialize + PartialEq + std::fmt::Debug,
8+
{
9+
let json: String = serde_json::to_string(value).unwrap();
10+
let back: T = serde_json::from_str(json.as_str()).unwrap();
11+
12+
assert_eq!(back, *value, "serde round-trip changes value");
13+
assert_eq!(
14+
json, expected_json,
15+
"value does not conform to expected JSON"
16+
);
17+
}
18+
19+
#[itest]
20+
fn serde_gstring() {
21+
let value = GString::from("hello world");
22+
23+
let expected_json = "\"hello world\"";
24+
25+
serde_roundtrip(&value, expected_json);
26+
}
27+
28+
#[itest]
29+
fn serde_node_path() {
30+
let value = NodePath::from("res://icon.png");
31+
let expected_json = "\"res://icon.png\"";
32+
33+
serde_roundtrip(&value, expected_json);
34+
}
35+
36+
#[itest]
37+
fn serde_string_name() {
38+
let value = StringName::from("hello world");
39+
let expected_json = "\"hello world\"";
40+
41+
serde_roundtrip(&value, expected_json);
42+
}
43+
44+
#[itest]
45+
fn serde_array_rust_native_type() {
46+
let value: Array<i32> = array![1, 2, 3, 4, 5, 6];
47+
48+
let expected_json = r#"[1,2,3,4,5,6]"#;
49+
50+
serde_roundtrip(&value, expected_json)
51+
}
52+
53+
#[itest]
54+
fn serde_array_godot_builtin_type() {
55+
let value: Array<GString> = array!["Godot".into(), "Rust".into(), "Rocks".into()];
56+
57+
let expected_json = r#"["Godot","Rust","Rocks"]"#;
58+
59+
serde_roundtrip(&value, expected_json)
60+
}
61+
62+
#[itest]
63+
fn serde_array_godot_type() {
64+
let value: Array<Vector2i> = array![
65+
Vector2i::new(1, 1),
66+
Vector2i::new(2, 2),
67+
Vector2i::new(3, 3)
68+
];
69+
70+
let expected_json = r#"[{"x":1,"y":1},{"x":2,"y":2},{"x":3,"y":3}]"#;
71+
72+
serde_roundtrip(&value, expected_json)
73+
}

0 commit comments

Comments
 (0)