Skip to content

Commit b5af6a9

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

File tree

9 files changed

+301
-0
lines changed

9 files changed

+301
-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: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
use crate::framework::itest;
8+
use godot::builtin::{array, Array, GString, NodePath, StringName, Vector2i};
9+
use serde::{Deserialize, Serialize};
10+
11+
fn serde_roundtrip<T>(value: &T, expected_json: &str)
12+
where
13+
T: for<'a> Deserialize<'a> + Serialize + PartialEq + std::fmt::Debug,
14+
{
15+
let json: String = serde_json::to_string(value).unwrap();
16+
let back: T = serde_json::from_str(json.as_str()).unwrap();
17+
18+
assert_eq!(back, *value, "serde round-trip changes value");
19+
assert_eq!(
20+
json, expected_json,
21+
"value does not conform to expected JSON"
22+
);
23+
}
24+
25+
#[itest]
26+
fn serde_gstring() {
27+
let value = GString::from("hello world");
28+
29+
let expected_json = "\"hello world\"";
30+
31+
serde_roundtrip(&value, expected_json);
32+
}
33+
34+
#[itest]
35+
fn serde_node_path() {
36+
let value = NodePath::from("res://icon.png");
37+
let expected_json = "\"res://icon.png\"";
38+
39+
serde_roundtrip(&value, expected_json);
40+
}
41+
42+
#[itest]
43+
fn serde_string_name() {
44+
let value = StringName::from("hello world");
45+
let expected_json = "\"hello world\"";
46+
47+
serde_roundtrip(&value, expected_json);
48+
}
49+
50+
#[itest]
51+
fn serde_array_rust_native_type() {
52+
let value: Array<i32> = array![1, 2, 3, 4, 5, 6];
53+
54+
let expected_json = r#"[1,2,3,4,5,6]"#;
55+
56+
serde_roundtrip(&value, expected_json)
57+
}
58+
59+
#[itest]
60+
fn serde_array_godot_builtin_type() {
61+
let value: Array<GString> = array!["Godot".into(), "Rust".into(), "Rocks".into()];
62+
63+
let expected_json = r#"["Godot","Rust","Rocks"]"#;
64+
65+
serde_roundtrip(&value, expected_json)
66+
}
67+
68+
#[itest]
69+
fn serde_array_godot_type() {
70+
let value: Array<Vector2i> = array![
71+
Vector2i::new(1, 1),
72+
Vector2i::new(2, 2),
73+
Vector2i::new(3, 3)
74+
];
75+
76+
let expected_json = r#"[{"x":1,"y":1},{"x":2,"y":2},{"x":3,"y":3}]"#;
77+
78+
serde_roundtrip(&value, expected_json)
79+
}

0 commit comments

Comments
 (0)