polyxml 0.12.0__tar.gz → 0.13.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {polyxml-0.12.0 → polyxml-0.13.0}/Cargo.lock +6 -5
- {polyxml-0.12.0 → polyxml-0.13.0}/Cargo.toml +1 -1
- {polyxml-0.12.0 → polyxml-0.13.0}/PKG-INFO +1 -1
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/Cargo.toml +1 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/codegen/python/mod.rs +17 -1
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/error.rs +3 -0
- polyxml-0.13.0/crates/polyxml-core/src/json.rs +356 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/lib.rs +2 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/value.rs +23 -0
- polyxml-0.13.0/crates/polyxml-core/tests/test_json.rs +223 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/tests/test_python_codegen.rs +7 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-python/src/lib.rs +40 -0
- polyxml-0.13.0/crates/polyxml-python/tests/test_json.py +275 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/pyproject.toml +1 -1
- {polyxml-0.12.0 → polyxml-0.13.0}/python/polyxml/__init__.py +159 -1
- polyxml-0.13.0/python/polyxml/compat/__init__.py +1 -0
- polyxml-0.13.0/python/polyxml/compat/xsdata.py +36 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/README.md +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/README.md +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/codegen/csharp/mod.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/codegen/go/mod.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/codegen/mod.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/codegen/rust/mod.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/converters.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/ir/mod.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/parser.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/schema.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/schema_parser/mod.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/src/serializer.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/tests/test_conformance.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/tests/test_core.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/tests/test_rust_codegen.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/tests/test_security.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-python/Cargo.toml +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-python/README.md +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-python/tests/test_conformance.py +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-python/tests/test_generated_models.py +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/crates/polyxml-python/tests/test_polyxml.py +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/python/polyxml/AGENT_GUIDE.md +0 -0
- {polyxml-0.12.0 → polyxml-0.13.0}/python/polyxml/py.typed +0 -0
|
@@ -549,7 +549,7 @@ dependencies = [
|
|
|
549
549
|
|
|
550
550
|
[[package]]
|
|
551
551
|
name = "polyxml"
|
|
552
|
-
version = "0.
|
|
552
|
+
version = "0.13.0"
|
|
553
553
|
dependencies = [
|
|
554
554
|
"criterion",
|
|
555
555
|
"heck",
|
|
@@ -559,6 +559,7 @@ dependencies = [
|
|
|
559
559
|
"pretty_assertions",
|
|
560
560
|
"quick-xml",
|
|
561
561
|
"serde",
|
|
562
|
+
"serde_json",
|
|
562
563
|
"smallvec",
|
|
563
564
|
"tempfile",
|
|
564
565
|
"thiserror",
|
|
@@ -566,14 +567,14 @@ dependencies = [
|
|
|
566
567
|
|
|
567
568
|
[[package]]
|
|
568
569
|
name = "polyxml-c"
|
|
569
|
-
version = "0.
|
|
570
|
+
version = "0.13.0"
|
|
570
571
|
dependencies = [
|
|
571
572
|
"polyxml",
|
|
572
573
|
]
|
|
573
574
|
|
|
574
575
|
[[package]]
|
|
575
576
|
name = "polyxml-cli"
|
|
576
|
-
version = "0.
|
|
577
|
+
version = "0.13.0"
|
|
577
578
|
dependencies = [
|
|
578
579
|
"clap",
|
|
579
580
|
"glob",
|
|
@@ -587,7 +588,7 @@ dependencies = [
|
|
|
587
588
|
|
|
588
589
|
[[package]]
|
|
589
590
|
name = "polyxml-js"
|
|
590
|
-
version = "0.
|
|
591
|
+
version = "0.13.0"
|
|
591
592
|
dependencies = [
|
|
592
593
|
"napi",
|
|
593
594
|
"napi-build",
|
|
@@ -597,7 +598,7 @@ dependencies = [
|
|
|
597
598
|
|
|
598
599
|
[[package]]
|
|
599
600
|
name = "polyxml-python"
|
|
600
|
-
version = "0.
|
|
601
|
+
version = "0.13.0"
|
|
601
602
|
dependencies = [
|
|
602
603
|
"lexical-core",
|
|
603
604
|
"polyxml",
|
|
@@ -529,7 +529,23 @@ impl PythonCodegen {
|
|
|
529
529
|
out.push_str(" ) -> bytes:\n");
|
|
530
530
|
out.push_str(" \"\"\"Serialize this model instance into XML bytes.\"\"\"\n");
|
|
531
531
|
out.push_str(" import polyxml\n");
|
|
532
|
-
out.push_str(" return polyxml.serialize(self, indent=indent, namespaces=namespaces, ns_map=ns_map)\n");
|
|
532
|
+
out.push_str(" return polyxml.serialize(self, indent=indent, namespaces=namespaces, ns_map=ns_map)\n\n");
|
|
533
|
+
out.push_str(" @classmethod\n");
|
|
534
|
+
out.push_str(" def from_json(cls, data: bytes | str) -> Self:\n");
|
|
535
|
+
out.push_str(" \"\"\"Deserialize JSON bytes or string into this model.\"\"\"\n");
|
|
536
|
+
out.push_str(" import polyxml\n");
|
|
537
|
+
out.push_str(" return polyxml.deserialize_json(data, cls)\n\n");
|
|
538
|
+
out.push_str(" def to_json(\n");
|
|
539
|
+
out.push_str(" self,\n");
|
|
540
|
+
out.push_str(" *,\n");
|
|
541
|
+
out.push_str(" indent: int | None = None,\n");
|
|
542
|
+
out.push_str(" by_alias: bool = True,\n");
|
|
543
|
+
out.push_str(" ) -> bytes:\n");
|
|
544
|
+
out.push_str(" \"\"\"Serialize this model instance into JSON bytes.\"\"\"\n");
|
|
545
|
+
out.push_str(" import polyxml\n");
|
|
546
|
+
out.push_str(
|
|
547
|
+
" return polyxml.serialize_json(self, indent=indent, by_alias=by_alias)\n",
|
|
548
|
+
);
|
|
533
549
|
has_body = true;
|
|
534
550
|
}
|
|
535
551
|
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
use std::sync::Arc;
|
|
3
|
+
|
|
4
|
+
use serde::Serialize;
|
|
5
|
+
use serde_json::Value as JsonValue;
|
|
6
|
+
|
|
7
|
+
use crate::error::Result;
|
|
8
|
+
use crate::schema::{ModelSchema, ScalarType, ValueType};
|
|
9
|
+
use crate::value::PolyValue;
|
|
10
|
+
|
|
11
|
+
/// Convert a `PolyValue` into a `serde_json::Value` dynamically without a schema.
|
|
12
|
+
impl From<&PolyValue> for JsonValue {
|
|
13
|
+
fn from(val: &PolyValue) -> Self {
|
|
14
|
+
match val {
|
|
15
|
+
PolyValue::Null => JsonValue::Null,
|
|
16
|
+
PolyValue::Bool(b) => JsonValue::Bool(*b),
|
|
17
|
+
PolyValue::Int(i) => JsonValue::Number((*i).into()),
|
|
18
|
+
PolyValue::Float(f) => serde_json::Number::from_f64(*f)
|
|
19
|
+
.map(JsonValue::Number)
|
|
20
|
+
.unwrap_or(JsonValue::Null),
|
|
21
|
+
PolyValue::String(s) => JsonValue::String(s.clone()),
|
|
22
|
+
PolyValue::List(l) => JsonValue::Array(l.iter().map(JsonValue::from).collect()),
|
|
23
|
+
PolyValue::Object(m) => {
|
|
24
|
+
let mut map = serde_json::Map::with_capacity(m.len());
|
|
25
|
+
for (k, v) in m {
|
|
26
|
+
map.insert(k.clone(), JsonValue::from(v));
|
|
27
|
+
}
|
|
28
|
+
JsonValue::Object(map)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Convert a `serde_json::Value` into a `PolyValue` dynamically without a schema.
|
|
35
|
+
impl From<JsonValue> for PolyValue {
|
|
36
|
+
fn from(val: JsonValue) -> Self {
|
|
37
|
+
match val {
|
|
38
|
+
JsonValue::Null => PolyValue::Null,
|
|
39
|
+
JsonValue::Bool(b) => PolyValue::Bool(b),
|
|
40
|
+
JsonValue::Number(n) => {
|
|
41
|
+
if let Some(i) = n.as_i64() {
|
|
42
|
+
PolyValue::Int(i)
|
|
43
|
+
} else if let Some(u) = n.as_u64() {
|
|
44
|
+
PolyValue::Int(u as i64)
|
|
45
|
+
} else if let Some(f) = n.as_f64() {
|
|
46
|
+
PolyValue::Float(f)
|
|
47
|
+
} else {
|
|
48
|
+
PolyValue::String(n.to_string())
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
JsonValue::String(s) => PolyValue::String(s),
|
|
52
|
+
JsonValue::Array(a) => PolyValue::List(a.into_iter().map(PolyValue::from).collect()),
|
|
53
|
+
JsonValue::Object(o) => {
|
|
54
|
+
let mut map = HashMap::with_capacity(o.len());
|
|
55
|
+
for (k, v) in o {
|
|
56
|
+
map.insert(k, PolyValue::from(v));
|
|
57
|
+
}
|
|
58
|
+
PolyValue::Object(map)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Convert a `PolyValue` to `serde_json::Value` guided by a `ModelSchema`.
|
|
65
|
+
pub fn poly_value_to_json_value(
|
|
66
|
+
val: &PolyValue,
|
|
67
|
+
schema: &ModelSchema,
|
|
68
|
+
by_alias: bool,
|
|
69
|
+
) -> JsonValue {
|
|
70
|
+
match val {
|
|
71
|
+
PolyValue::Object(map) => {
|
|
72
|
+
let mut json_map = serde_json::Map::with_capacity(schema.fields.len());
|
|
73
|
+
for field in &schema.fields {
|
|
74
|
+
let key = if by_alias {
|
|
75
|
+
std::str::from_utf8(&field.xml_name).unwrap_or(field.name.as_str())
|
|
76
|
+
} else {
|
|
77
|
+
field.name.as_str()
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if let Some(field_val) = map.get(&field.name) {
|
|
81
|
+
let converted = match &field.val_type {
|
|
82
|
+
ValueType::Nested(nested_schema) => {
|
|
83
|
+
if field_val.is_null() {
|
|
84
|
+
JsonValue::Null
|
|
85
|
+
} else {
|
|
86
|
+
poly_value_to_json_value(field_val, nested_schema, by_alias)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
ValueType::List(inner) => {
|
|
90
|
+
if let PolyValue::List(items) = field_val {
|
|
91
|
+
if let ValueType::Nested(nested_schema) = inner.as_ref() {
|
|
92
|
+
JsonValue::Array(
|
|
93
|
+
items
|
|
94
|
+
.iter()
|
|
95
|
+
.map(|item| {
|
|
96
|
+
poly_value_to_json_value(
|
|
97
|
+
item,
|
|
98
|
+
nested_schema,
|
|
99
|
+
by_alias,
|
|
100
|
+
)
|
|
101
|
+
})
|
|
102
|
+
.collect(),
|
|
103
|
+
)
|
|
104
|
+
} else {
|
|
105
|
+
JsonValue::Array(items.iter().map(JsonValue::from).collect())
|
|
106
|
+
}
|
|
107
|
+
} else if field_val.is_null() {
|
|
108
|
+
JsonValue::Null
|
|
109
|
+
} else {
|
|
110
|
+
JsonValue::Array(vec![JsonValue::from(field_val)])
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
ValueType::Scalar(_) => JsonValue::from(field_val),
|
|
114
|
+
};
|
|
115
|
+
json_map.insert(key.to_string(), converted);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
JsonValue::Object(json_map)
|
|
119
|
+
}
|
|
120
|
+
other => JsonValue::from(other),
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Coerce a JSON value into a `PolyValue` scalar matching `ScalarType`.
|
|
125
|
+
fn coerce_scalar(val: &JsonValue, st: &ScalarType) -> PolyValue {
|
|
126
|
+
match val {
|
|
127
|
+
JsonValue::Null => PolyValue::Null,
|
|
128
|
+
JsonValue::Bool(b) => PolyValue::Bool(*b),
|
|
129
|
+
JsonValue::Number(n) => match st {
|
|
130
|
+
ScalarType::Int => {
|
|
131
|
+
if let Some(i) = n.as_i64() {
|
|
132
|
+
PolyValue::Int(i)
|
|
133
|
+
} else {
|
|
134
|
+
PolyValue::Int(n.as_f64().unwrap_or(0.0) as i64)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
ScalarType::Float => PolyValue::Float(n.as_f64().unwrap_or(0.0)),
|
|
138
|
+
ScalarType::Decimal | ScalarType::String => PolyValue::String(n.to_string()),
|
|
139
|
+
_ => {
|
|
140
|
+
if let Some(i) = n.as_i64() {
|
|
141
|
+
PolyValue::Int(i)
|
|
142
|
+
} else {
|
|
143
|
+
PolyValue::Float(n.as_f64().unwrap_or(0.0))
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
JsonValue::String(s) => match st {
|
|
148
|
+
ScalarType::Int => {
|
|
149
|
+
if let Ok(i) = s.trim().parse::<i64>() {
|
|
150
|
+
PolyValue::Int(i)
|
|
151
|
+
} else {
|
|
152
|
+
PolyValue::String(s.clone())
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
ScalarType::Float => {
|
|
156
|
+
if let Ok(f) = s.trim().parse::<f64>() {
|
|
157
|
+
PolyValue::Float(f)
|
|
158
|
+
} else {
|
|
159
|
+
PolyValue::String(s.clone())
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
ScalarType::Bool => {
|
|
163
|
+
let trimmed = s.trim().to_ascii_lowercase();
|
|
164
|
+
if trimmed == "true" || trimmed == "1" {
|
|
165
|
+
PolyValue::Bool(true)
|
|
166
|
+
} else if trimmed == "false" || trimmed == "0" {
|
|
167
|
+
PolyValue::Bool(false)
|
|
168
|
+
} else {
|
|
169
|
+
PolyValue::String(s.clone())
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
_ => PolyValue::String(s.clone()),
|
|
173
|
+
},
|
|
174
|
+
other => PolyValue::from(other.clone()),
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/// Convert a `serde_json::Value` into a `PolyValue` matching a `ModelSchema`.
|
|
179
|
+
pub fn json_value_to_poly_value(val: &JsonValue, schema: &ModelSchema) -> Result<PolyValue> {
|
|
180
|
+
match val {
|
|
181
|
+
JsonValue::Object(map) => {
|
|
182
|
+
let mut obj = HashMap::with_capacity(schema.fields.len());
|
|
183
|
+
for field in &schema.fields {
|
|
184
|
+
let xml_name_str = std::str::from_utf8(&field.xml_name).ok();
|
|
185
|
+
// Priority: match schema alias (xml_name) first, fallback to python field.name
|
|
186
|
+
let found_val = xml_name_str
|
|
187
|
+
.and_then(|alias| map.get(alias))
|
|
188
|
+
.or_else(|| map.get(&field.name));
|
|
189
|
+
|
|
190
|
+
if let Some(json_field_val) = found_val {
|
|
191
|
+
if json_field_val.is_null() {
|
|
192
|
+
obj.insert(field.name.clone(), PolyValue::Null);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
match &field.val_type {
|
|
197
|
+
ValueType::Scalar(st) => {
|
|
198
|
+
obj.insert(field.name.clone(), coerce_scalar(json_field_val, st));
|
|
199
|
+
}
|
|
200
|
+
ValueType::List(inner) => {
|
|
201
|
+
if let JsonValue::Array(items) = json_field_val {
|
|
202
|
+
let mut poly_list = Vec::with_capacity(items.len());
|
|
203
|
+
for item in items {
|
|
204
|
+
if let ValueType::Nested(nested_schema) = inner.as_ref() {
|
|
205
|
+
poly_list
|
|
206
|
+
.push(json_value_to_poly_value(item, nested_schema)?);
|
|
207
|
+
} else if let ValueType::Scalar(st) = inner.as_ref() {
|
|
208
|
+
poly_list.push(coerce_scalar(item, st));
|
|
209
|
+
} else {
|
|
210
|
+
poly_list.push(PolyValue::from(item.clone()));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
obj.insert(field.name.clone(), PolyValue::List(poly_list));
|
|
214
|
+
} else {
|
|
215
|
+
// Single item into list
|
|
216
|
+
let single = match inner.as_ref() {
|
|
217
|
+
ValueType::Nested(nested_schema) => {
|
|
218
|
+
json_value_to_poly_value(json_field_val, nested_schema)?
|
|
219
|
+
}
|
|
220
|
+
ValueType::Scalar(st) => coerce_scalar(json_field_val, st),
|
|
221
|
+
_ => PolyValue::from(json_field_val.clone()),
|
|
222
|
+
};
|
|
223
|
+
obj.insert(field.name.clone(), PolyValue::List(vec![single]));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
ValueType::Nested(nested_schema) => {
|
|
227
|
+
obj.insert(
|
|
228
|
+
field.name.clone(),
|
|
229
|
+
json_value_to_poly_value(json_field_val, nested_schema)?,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
Ok(PolyValue::Object(obj))
|
|
236
|
+
}
|
|
237
|
+
other => Ok(PolyValue::from(other.clone())),
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/// Serialize a `PolyValue` into JSON bytes using a `ModelSchema`.
|
|
242
|
+
pub fn serialize_json(
|
|
243
|
+
value: &PolyValue,
|
|
244
|
+
schema: &ModelSchema,
|
|
245
|
+
indent: Option<usize>,
|
|
246
|
+
by_alias: bool,
|
|
247
|
+
) -> Result<Vec<u8>> {
|
|
248
|
+
let json_val = poly_value_to_json_value(value, schema, by_alias);
|
|
249
|
+
match indent {
|
|
250
|
+
Some(spaces) if spaces > 0 => {
|
|
251
|
+
let indent_str = " ".repeat(spaces);
|
|
252
|
+
let formatter = serde_json::ser::PrettyFormatter::with_indent(indent_str.as_bytes());
|
|
253
|
+
let mut buf = Vec::new();
|
|
254
|
+
let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
|
|
255
|
+
json_val.serialize(&mut ser)?;
|
|
256
|
+
Ok(buf)
|
|
257
|
+
}
|
|
258
|
+
_ => Ok(serde_json::to_vec(&json_val)?),
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/// Deserialize JSON bytes into a `PolyValue` using a `ModelSchema`.
|
|
263
|
+
pub fn deserialize_json(json_bytes: &[u8], schema: Arc<ModelSchema>) -> Result<PolyValue> {
|
|
264
|
+
let json_val: JsonValue = serde_json::from_slice(json_bytes)?;
|
|
265
|
+
json_value_to_poly_value(&json_val, &schema)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
#[cfg(test)]
|
|
269
|
+
mod tests {
|
|
270
|
+
use super::*;
|
|
271
|
+
use crate::schema::{FieldKind, FieldSchema};
|
|
272
|
+
|
|
273
|
+
#[test]
|
|
274
|
+
fn test_json_roundtrip_primitives() {
|
|
275
|
+
let schema = ModelSchema::builder("User")
|
|
276
|
+
.field(FieldSchema::new(
|
|
277
|
+
"user_id",
|
|
278
|
+
b"userId",
|
|
279
|
+
FieldKind::Attribute,
|
|
280
|
+
ValueType::Scalar(ScalarType::Int),
|
|
281
|
+
))
|
|
282
|
+
.field(FieldSchema::new(
|
|
283
|
+
"full_name",
|
|
284
|
+
b"fullName",
|
|
285
|
+
FieldKind::Element,
|
|
286
|
+
ValueType::Scalar(ScalarType::String),
|
|
287
|
+
))
|
|
288
|
+
.field(FieldSchema::new(
|
|
289
|
+
"score",
|
|
290
|
+
b"score",
|
|
291
|
+
FieldKind::Element,
|
|
292
|
+
ValueType::Scalar(ScalarType::Float),
|
|
293
|
+
))
|
|
294
|
+
.field(FieldSchema::new(
|
|
295
|
+
"is_admin",
|
|
296
|
+
b"isAdmin",
|
|
297
|
+
FieldKind::Element,
|
|
298
|
+
ValueType::Scalar(ScalarType::Bool),
|
|
299
|
+
))
|
|
300
|
+
.build();
|
|
301
|
+
|
|
302
|
+
let mut map = HashMap::new();
|
|
303
|
+
map.insert("user_id".to_string(), PolyValue::Int(42));
|
|
304
|
+
map.insert(
|
|
305
|
+
"full_name".to_string(),
|
|
306
|
+
PolyValue::String("Ada Lovelace".to_string()),
|
|
307
|
+
);
|
|
308
|
+
map.insert("score".to_string(), PolyValue::Float(99.5));
|
|
309
|
+
map.insert("is_admin".to_string(), PolyValue::Bool(true));
|
|
310
|
+
let poly_val = PolyValue::Object(map);
|
|
311
|
+
|
|
312
|
+
// Serialize with by_alias = true
|
|
313
|
+
let json_bytes = serialize_json(&poly_val, &schema, None, true).unwrap();
|
|
314
|
+
let json_str = std::str::from_utf8(&json_bytes).unwrap();
|
|
315
|
+
assert!(json_str.contains("\"userId\":42"));
|
|
316
|
+
assert!(json_str.contains("\"fullName\":\"Ada Lovelace\""));
|
|
317
|
+
assert!(json_str.contains("\"score\":99.5"));
|
|
318
|
+
assert!(json_str.contains("\"isAdmin\":true"));
|
|
319
|
+
|
|
320
|
+
// Deserialize back
|
|
321
|
+
let parsed = deserialize_json(&json_bytes, schema).unwrap();
|
|
322
|
+
assert_eq!(parsed.get("user_id"), Some(&PolyValue::Int(42)));
|
|
323
|
+
assert_eq!(
|
|
324
|
+
parsed.get("full_name"),
|
|
325
|
+
Some(&PolyValue::String("Ada Lovelace".to_string()))
|
|
326
|
+
);
|
|
327
|
+
assert_eq!(parsed.get("score"), Some(&PolyValue::Float(99.5)));
|
|
328
|
+
assert_eq!(parsed.get("is_admin"), Some(&PolyValue::Bool(true)));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
#[test]
|
|
332
|
+
fn test_json_deserialization_accepts_python_field_names() {
|
|
333
|
+
let schema = ModelSchema::builder("User")
|
|
334
|
+
.field(FieldSchema::new(
|
|
335
|
+
"user_id",
|
|
336
|
+
b"userId",
|
|
337
|
+
FieldKind::Attribute,
|
|
338
|
+
ValueType::Scalar(ScalarType::Int),
|
|
339
|
+
))
|
|
340
|
+
.field(FieldSchema::new(
|
|
341
|
+
"full_name",
|
|
342
|
+
b"fullName",
|
|
343
|
+
FieldKind::Element,
|
|
344
|
+
ValueType::Scalar(ScalarType::String),
|
|
345
|
+
))
|
|
346
|
+
.build();
|
|
347
|
+
|
|
348
|
+
let json = br#"{"user_id": 100, "full_name": "Grace Hopper"}"#;
|
|
349
|
+
let parsed = deserialize_json(json, schema).unwrap();
|
|
350
|
+
assert_eq!(parsed.get("user_id"), Some(&PolyValue::Int(100)));
|
|
351
|
+
assert_eq!(
|
|
352
|
+
parsed.get("full_name"),
|
|
353
|
+
Some(&PolyValue::String("Grace Hopper".to_string()))
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
@@ -2,6 +2,7 @@ pub mod codegen;
|
|
|
2
2
|
pub mod converters;
|
|
3
3
|
pub mod error;
|
|
4
4
|
pub mod ir;
|
|
5
|
+
pub mod json;
|
|
5
6
|
pub mod parser;
|
|
6
7
|
pub mod schema;
|
|
7
8
|
pub mod schema_parser;
|
|
@@ -9,6 +10,7 @@ pub mod serializer;
|
|
|
9
10
|
pub mod value;
|
|
10
11
|
|
|
11
12
|
pub use error::{PolyXmlError, Result};
|
|
13
|
+
pub use json::{deserialize_json, serialize_json};
|
|
12
14
|
pub use parser::{XmlDeserializer, XmlItemStream};
|
|
13
15
|
pub use schema::{FieldKind, FieldSchema, ModelSchema, ScalarType, ValueType};
|
|
14
16
|
pub use serializer::XmlSerializer;
|
|
@@ -64,4 +64,27 @@ impl PolyValue {
|
|
|
64
64
|
_ => None,
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
/// Serialize this PolyValue into raw JSON bytes.
|
|
69
|
+
pub fn to_json(&self, indent: Option<usize>) -> crate::error::Result<Vec<u8>> {
|
|
70
|
+
let json_val = serde_json::Value::from(self);
|
|
71
|
+
match indent {
|
|
72
|
+
Some(spaces) if spaces > 0 => {
|
|
73
|
+
let indent_str = " ".repeat(spaces);
|
|
74
|
+
let formatter =
|
|
75
|
+
serde_json::ser::PrettyFormatter::with_indent(indent_str.as_bytes());
|
|
76
|
+
let mut buf = Vec::new();
|
|
77
|
+
let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
|
|
78
|
+
serde::Serialize::serialize(&json_val, &mut ser)?;
|
|
79
|
+
Ok(buf)
|
|
80
|
+
}
|
|
81
|
+
_ => Ok(serde_json::to_vec(&json_val)?),
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Deserialize raw JSON bytes into a dynamic PolyValue without a schema.
|
|
86
|
+
pub fn from_json(json_bytes: &[u8]) -> crate::error::Result<Self> {
|
|
87
|
+
let json_val: serde_json::Value = serde_json::from_slice(json_bytes)?;
|
|
88
|
+
Ok(PolyValue::from(json_val))
|
|
89
|
+
}
|
|
67
90
|
}
|