polyxml 0.12.0__tar.gz → 0.14.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.
Files changed (55) hide show
  1. {polyxml-0.12.0 → polyxml-0.14.0}/Cargo.lock +6 -5
  2. {polyxml-0.12.0 → polyxml-0.14.0}/Cargo.toml +1 -1
  3. {polyxml-0.12.0 → polyxml-0.14.0}/PKG-INFO +1 -1
  4. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/Cargo.toml +1 -0
  5. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/codegen/csharp/mod.rs +63 -29
  6. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/codegen/go/mod.rs +62 -26
  7. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/codegen/python/mod.rs +34 -7
  8. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/codegen/rust/mod.rs +61 -4
  9. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/error.rs +3 -0
  10. polyxml-0.14.0/crates/polyxml-core/src/json.rs +356 -0
  11. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/lib.rs +4 -0
  12. polyxml-0.14.0/crates/polyxml-core/src/schema.rs +358 -0
  13. polyxml-0.14.0/crates/polyxml-core/src/transcoder.rs +377 -0
  14. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/value.rs +23 -0
  15. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/tests/test_csharp_codegen.rs +39 -9
  16. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/tests/test_go_codegen.rs +45 -16
  17. polyxml-0.14.0/crates/polyxml-core/tests/test_json.rs +223 -0
  18. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/tests/test_python_codegen.rs +15 -6
  19. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/tests/test_rust_codegen.rs +13 -0
  20. polyxml-0.14.0/crates/polyxml-core/tests/test_transcoder.rs +153 -0
  21. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-python/src/lib.rs +145 -0
  22. polyxml-0.14.0/crates/polyxml-python/tests/test_json.py +387 -0
  23. {polyxml-0.12.0 → polyxml-0.14.0}/pyproject.toml +1 -1
  24. {polyxml-0.12.0 → polyxml-0.14.0}/python/polyxml/__init__.py +242 -1
  25. polyxml-0.14.0/python/polyxml/compat/__init__.py +1 -0
  26. polyxml-0.14.0/python/polyxml/compat/xsdata.py +36 -0
  27. polyxml-0.12.0/crates/polyxml-core/src/schema.rs +0 -167
  28. {polyxml-0.12.0 → polyxml-0.14.0}/README.md +0 -0
  29. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/README.md +0 -0
  30. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  31. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  32. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
  33. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/codegen/mod.rs +0 -0
  34. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
  35. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/converters.rs +0 -0
  36. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/ir/mod.rs +0 -0
  37. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  38. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/parser.rs +0 -0
  39. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/schema_parser/mod.rs +0 -0
  40. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/src/serializer.rs +0 -0
  41. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  42. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/tests/test_core.rs +0 -0
  43. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  44. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
  45. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  46. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
  47. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/tests/test_security.rs +0 -0
  48. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  49. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-python/Cargo.toml +0 -0
  50. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-python/README.md +0 -0
  51. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-python/tests/test_conformance.py +0 -0
  52. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  53. {polyxml-0.12.0 → polyxml-0.14.0}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  54. {polyxml-0.12.0 → polyxml-0.14.0}/python/polyxml/AGENT_GUIDE.md +0 -0
  55. {polyxml-0.12.0 → polyxml-0.14.0}/python/polyxml/py.typed +0 -0
@@ -549,7 +549,7 @@ dependencies = [
549
549
 
550
550
  [[package]]
551
551
  name = "polyxml"
552
- version = "0.12.0"
552
+ version = "0.14.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.12.0"
570
+ version = "0.14.0"
570
571
  dependencies = [
571
572
  "polyxml",
572
573
  ]
573
574
 
574
575
  [[package]]
575
576
  name = "polyxml-cli"
576
- version = "0.12.0"
577
+ version = "0.14.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.12.0"
591
+ version = "0.14.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.12.0"
601
+ version = "0.14.0"
601
602
  dependencies = [
602
603
  "lexical-core",
603
604
  "polyxml",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/polyxml-core", "crates/polyxml-python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.12.0"
6
+ version = "0.14.0"
7
7
  edition = "2021"
8
8
  rust-version = "1.80"
9
9
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyxml
3
- Version: 0.12.0
3
+ Version: 0.14.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -17,6 +17,7 @@ memchr = "2.8"
17
17
  thiserror = "2.0"
18
18
  smallvec = "1.16"
19
19
  serde = { version = "1.0", features = ["derive"] }
20
+ serde_json = "1.0"
20
21
  minijinja = { version = "2.8", features = ["builtins"] }
21
22
  heck = "0.5"
22
23
 
@@ -31,6 +31,8 @@ pub struct CSharpOptions {
31
31
  pub namespace: String,
32
32
  /// Emit System.Xml.Serialization attributes ([XmlElement], [XmlAttribute], etc.)
33
33
  pub emit_xml_attributes: bool,
34
+ /// Emit System.Text.Json.Serialization attributes ([JsonPropertyName], etc.)
35
+ pub emit_json_attributes: bool,
34
36
  /// Emit IValidatableObject and restriction facet validation logic
35
37
  pub emit_validation: bool,
36
38
  /// Record emission kind (record class vs record struct)
@@ -46,6 +48,7 @@ impl Default for CSharpOptions {
46
48
  Self {
47
49
  namespace: "Generated".to_string(),
48
50
  emit_xml_attributes: true,
51
+ emit_json_attributes: true,
49
52
  emit_validation: true,
50
53
  record_kind: CSharpRecordKind::Class,
51
54
  use_file_scoped_namespaces: true,
@@ -225,6 +228,9 @@ impl CSharpCodegen {
225
228
  if self.options.emit_xml_attributes {
226
229
  writeln!(out, "using System.Xml.Serialization;").unwrap();
227
230
  }
231
+ if self.options.emit_json_attributes {
232
+ writeln!(out, "using System.Text.Json.Serialization;").unwrap();
233
+ }
228
234
  writeln!(out).unwrap();
229
235
 
230
236
  let ns = to_csharp_namespace(&self.options.namespace);
@@ -288,6 +294,14 @@ impl CSharpCodegen {
288
294
  self.emit_docstring(out, doc, indent);
289
295
  }
290
296
 
297
+ if self.options.emit_json_attributes {
298
+ writeln!(
299
+ out,
300
+ "{}[JsonConverter(typeof(JsonStringEnumConverter))]",
301
+ indent
302
+ )
303
+ .unwrap();
304
+ }
291
305
  writeln!(out, "{}public enum {}", indent, enum_name).unwrap();
292
306
  writeln!(out, "{}{{", indent).unwrap();
293
307
 
@@ -425,10 +439,17 @@ impl CSharpCodegen {
425
439
  self.emit_docstring(out, doc, &format!("{} ", indent));
426
440
  }
427
441
 
428
- let xml_attr = if self.options.emit_xml_attributes {
429
- format!("[property: XmlElement(\"{}\")] ", branch.xml_name)
430
- } else {
442
+ let mut branch_attrs = Vec::new();
443
+ if self.options.emit_xml_attributes {
444
+ branch_attrs.push(format!("XmlElement(\"{}\")", branch.xml_name));
445
+ }
446
+ if self.options.emit_json_attributes {
447
+ branch_attrs.push(format!("JsonPropertyName(\"{}\")", branch.xml_name));
448
+ }
449
+ let xml_attr = if branch_attrs.is_empty() {
431
450
  String::new()
451
+ } else {
452
+ format!("[property: {}] ", branch_attrs.join(", "))
432
453
  };
433
454
 
434
455
  writeln!(
@@ -529,11 +550,7 @@ impl CSharpCodegen {
529
550
 
530
551
  let default_val = if can_have_default { " = null" } else { "" };
531
552
 
532
- let xml_attr = if self.options.emit_xml_attributes {
533
- self.build_field_xml_attribute(f, ir)
534
- } else {
535
- String::new()
536
- };
553
+ let field_attrs = self.build_field_attributes(f, ir);
537
554
 
538
555
  if let Some(ref doc) = f.documentation {
539
556
  self.emit_docstring(out, doc, &format!("{} ", indent));
@@ -542,7 +559,7 @@ impl CSharpCodegen {
542
559
  writeln!(
543
560
  out,
544
561
  "{} {}{} {}{}{}",
545
- indent, xml_attr, field_type, prop_name, default_val, comma
562
+ indent, field_attrs, field_type, prop_name, default_val, comma
546
563
  )
547
564
  .unwrap();
548
565
  }
@@ -709,31 +726,48 @@ impl CSharpCodegen {
709
726
  }
710
727
  }
711
728
 
712
- fn build_field_xml_attribute(&self, f: &FieldDef, ir: &SchemaIR) -> String {
713
- // If the field is a choice (UnionDef), emit [property: XmlElement("branchXml", typeof(BranchType))]
714
- if let TypeRef::Named(ref qname) = f.type_ref {
715
- if let Some(TypeDef::Union(u)) = ir.types.get(qname) {
716
- let choice_name = to_csharp_type_name(&u.qname.local);
717
- let mut attrs = Vec::new();
718
- for branch in &u.branches {
719
- let variant_name = to_csharp_type_name(&branch.variant_name);
720
- attrs.push(format!(
721
- "[property: XmlElement(\"{}\", typeof({}.{}))]",
722
- branch.xml_name, choice_name, variant_name
723
- ));
729
+ fn build_field_attributes(&self, f: &FieldDef, ir: &SchemaIR) -> String {
730
+ let mut parts = Vec::new();
731
+
732
+ if self.options.emit_xml_attributes {
733
+ // If the field is a choice (UnionDef), emit XmlElement("branchXml", typeof(BranchType))
734
+ if let TypeRef::Named(ref qname) = f.type_ref {
735
+ if let Some(TypeDef::Union(u)) = ir.types.get(qname) {
736
+ let choice_name = to_csharp_type_name(&u.qname.local);
737
+ for branch in &u.branches {
738
+ let variant_name = to_csharp_type_name(&branch.variant_name);
739
+ parts.push(format!(
740
+ "XmlElement(\"{}\", typeof({}.{}))",
741
+ branch.xml_name, choice_name, variant_name
742
+ ));
743
+ }
724
744
  }
725
- if !attrs.is_empty() {
726
- return format!("{} ", attrs.join(" "));
745
+ }
746
+ if parts.is_empty() {
747
+ match f.kind {
748
+ FieldKind::Attribute => parts.push(format!("XmlAttribute(\"{}\")", f.xml_name)),
749
+ FieldKind::Text => parts.push("XmlText".to_string()),
750
+ FieldKind::Any => parts.push("XmlAnyElement".to_string()),
751
+ FieldKind::AnyAttribute => parts.push("XmlAnyAttribute".to_string()),
752
+ FieldKind::Element => parts.push(format!("XmlElement(\"{}\")", f.xml_name)),
727
753
  }
728
754
  }
729
755
  }
730
756
 
731
- match f.kind {
732
- FieldKind::Attribute => format!("[property: XmlAttribute(\"{}\")] ", f.xml_name),
733
- FieldKind::Text => "[property: XmlText] ".to_string(),
734
- FieldKind::Any => "[property: XmlAnyElement] ".to_string(),
735
- FieldKind::AnyAttribute => "[property: XmlAnyAttribute] ".to_string(),
736
- FieldKind::Element => format!("[property: XmlElement(\"{}\")] ", f.xml_name),
757
+ if self.options.emit_json_attributes {
758
+ let json_name = match f.kind {
759
+ FieldKind::Text => "value",
760
+ _ => &f.xml_name,
761
+ };
762
+ if f.kind != FieldKind::Any && f.kind != FieldKind::AnyAttribute {
763
+ parts.push(format!("JsonPropertyName(\"{}\")", json_name));
764
+ }
765
+ }
766
+
767
+ if parts.is_empty() {
768
+ String::new()
769
+ } else {
770
+ format!("[property: {}] ", parts.join(", "))
737
771
  }
738
772
  }
739
773
 
@@ -16,6 +16,8 @@ pub struct GoOptions {
16
16
  pub package_name: String,
17
17
  /// Emit encoding/xml tags (default: true)
18
18
  pub emit_xml_tags: bool,
19
+ /// Emit encoding/json tags (default: true)
20
+ pub emit_json_tags: bool,
19
21
  /// Emit custom UnmarshalXML/MarshalXML for xs:choice mutual exclusivity validation (default: true)
20
22
  pub validate_choice_exclusivity: bool,
21
23
  /// Emit Validate() error method for restriction facets (default: true)
@@ -29,6 +31,7 @@ impl Default for GoOptions {
29
31
  Self {
30
32
  package_name: "models".to_string(),
31
33
  emit_xml_tags: true,
34
+ emit_json_tags: true,
32
35
  validate_choice_exclusivity: true,
33
36
  validate_facets: true,
34
37
  emit_root_aliases: true,
@@ -540,7 +543,11 @@ impl GoCodegen {
540
543
 
541
544
  // Emit XMLName if xml tags enabled
542
545
  if self.options.emit_xml_tags {
543
- writeln!(out, " XMLName xml.Name").unwrap();
546
+ if self.options.emit_json_tags {
547
+ writeln!(out, " XMLName xml.Name `json:\"-\"`").unwrap();
548
+ } else {
549
+ writeln!(out, " XMLName xml.Name").unwrap();
550
+ }
544
551
  }
545
552
 
546
553
  // Struct composition / inheritance if base struct exists
@@ -562,10 +569,17 @@ impl GoCodegen {
562
569
  .iter()
563
570
  .any(|f| f.name == "value" || f.kind == FieldKind::Text);
564
571
  if !has_value_field {
565
- let tag = if self.options.emit_xml_tags {
566
- " `xml:\",chardata\"`"
572
+ let mut parts = Vec::new();
573
+ if self.options.emit_xml_tags {
574
+ parts.push("xml:\",chardata\"".to_string());
575
+ }
576
+ if self.options.emit_json_tags {
577
+ parts.push("json:\"value,omitempty\"".to_string());
578
+ }
579
+ let tag = if parts.is_empty() {
580
+ String::new()
567
581
  } else {
568
- ""
582
+ format!(" `{}`", parts.join(" "))
569
583
  };
570
584
  writeln!(out, " Value {}{}", base_type_str, tag).unwrap();
571
585
  }
@@ -579,7 +593,7 @@ impl GoCodegen {
579
593
 
580
594
  let field_name = to_go_field_name(&f.name);
581
595
  let field_type = self.resolve_field_type(f);
582
- let tag = self.build_field_xml_tag(f);
596
+ let tag = self.build_field_struct_tags(f);
583
597
  writeln!(out, " {} {}{}", field_name, field_type, tag).unwrap();
584
598
  }
585
599
 
@@ -606,30 +620,52 @@ impl GoCodegen {
606
620
  }
607
621
  }
608
622
 
609
- fn build_field_xml_tag(&self, f: &FieldDef) -> String {
610
- if !self.options.emit_xml_tags {
611
- return String::new();
612
- }
623
+ fn build_field_struct_tags(&self, f: &FieldDef) -> String {
624
+ let mut parts = Vec::new();
613
625
 
614
- let is_opt = f.cardinality.is_optional() || f.nillable;
615
- match f.kind {
616
- FieldKind::Attribute => {
617
- if is_opt {
618
- format!(" `xml:\"{},attr,omitempty\"`", f.xml_name)
619
- } else {
620
- format!(" `xml:\"{},attr\"`", f.xml_name)
626
+ if self.options.emit_xml_tags {
627
+ let is_opt = f.cardinality.is_optional() || f.nillable;
628
+ let xml_val = match f.kind {
629
+ FieldKind::Attribute => {
630
+ if is_opt {
631
+ format!("{},attr,omitempty", f.xml_name)
632
+ } else {
633
+ format!("{},attr", f.xml_name)
634
+ }
621
635
  }
622
- }
623
- FieldKind::Text => " `xml:\",chardata\"`".to_string(),
624
- FieldKind::Any => " `xml:\",any\"`".to_string(),
625
- FieldKind::AnyAttribute => " `xml:\",any,attr\"`".to_string(),
626
- FieldKind::Element => {
627
- if is_opt {
628
- format!(" `xml:\"{},omitempty\"`", f.xml_name)
629
- } else {
630
- format!(" `xml:\"{}\"`", f.xml_name)
636
+ FieldKind::Text => ",chardata".to_string(),
637
+ FieldKind::Any => ",any".to_string(),
638
+ FieldKind::AnyAttribute => ",any,attr".to_string(),
639
+ FieldKind::Element => {
640
+ if is_opt {
641
+ format!("{},omitempty", f.xml_name)
642
+ } else {
643
+ f.xml_name.clone()
644
+ }
631
645
  }
632
- }
646
+ };
647
+ parts.push(format!("xml:\"{}\"", xml_val));
648
+ }
649
+
650
+ if self.options.emit_json_tags {
651
+ let is_opt = f.cardinality.is_optional() || f.nillable;
652
+ let json_name = if f.kind == FieldKind::Text {
653
+ "value".to_string()
654
+ } else {
655
+ f.xml_name.clone()
656
+ };
657
+ let json_val = if is_opt {
658
+ format!("{},omitempty", json_name)
659
+ } else {
660
+ json_name
661
+ };
662
+ parts.push(format!("json:\"{}\"", json_val));
663
+ }
664
+
665
+ if parts.is_empty() {
666
+ String::new()
667
+ } else {
668
+ format!(" `{}`", parts.join(" "))
633
669
  }
634
670
  }
635
671
 
@@ -38,6 +38,7 @@ pub struct PythonOptions {
38
38
  pub emit_meta: bool,
39
39
  pub emit_root_aliases: bool,
40
40
  pub emit_codecs: bool,
41
+ pub emit_json_metadata: bool,
41
42
  }
42
43
 
43
44
  impl Default for PythonOptions {
@@ -50,6 +51,7 @@ impl Default for PythonOptions {
50
51
  emit_meta: true,
51
52
  emit_root_aliases: true,
52
53
  emit_codecs: true,
54
+ emit_json_metadata: true,
53
55
  }
54
56
  }
55
57
  }
@@ -529,7 +531,23 @@ impl PythonCodegen {
529
531
  out.push_str(" ) -> bytes:\n");
530
532
  out.push_str(" \"\"\"Serialize this model instance into XML bytes.\"\"\"\n");
531
533
  out.push_str(" import polyxml\n");
532
- out.push_str(" return polyxml.serialize(self, indent=indent, namespaces=namespaces, ns_map=ns_map)\n");
534
+ out.push_str(" return polyxml.serialize(self, indent=indent, namespaces=namespaces, ns_map=ns_map)\n\n");
535
+ out.push_str(" @classmethod\n");
536
+ out.push_str(" def from_json(cls, data: bytes | str) -> Self:\n");
537
+ out.push_str(" \"\"\"Deserialize JSON bytes or string into this model.\"\"\"\n");
538
+ out.push_str(" import polyxml\n");
539
+ out.push_str(" return polyxml.deserialize_json(data, cls)\n\n");
540
+ out.push_str(" def to_json(\n");
541
+ out.push_str(" self,\n");
542
+ out.push_str(" *,\n");
543
+ out.push_str(" indent: int | None = None,\n");
544
+ out.push_str(" by_alias: bool = True,\n");
545
+ out.push_str(" ) -> bytes:\n");
546
+ out.push_str(" \"\"\"Serialize this model instance into JSON bytes.\"\"\"\n");
547
+ out.push_str(" import polyxml\n");
548
+ out.push_str(
549
+ " return polyxml.serialize_json(self, indent=indent, by_alias=by_alias)\n",
550
+ );
533
551
  has_body = true;
534
552
  }
535
553
 
@@ -563,7 +581,7 @@ impl PythonCodegen {
563
581
  self.format_dataclass_field(field, is_list, &inner_type, &meta_dict)
564
582
  }
565
583
  PythonBackend::Pydantic => {
566
- self.format_pydantic_field(field, is_list, &inner_type, &meta_dict)
584
+ self.format_pydantic_field(field, py_name, is_list, &inner_type, &meta_dict)
567
585
  }
568
586
  };
569
587
 
@@ -616,6 +634,7 @@ impl PythonCodegen {
616
634
  fn format_pydantic_field(
617
635
  &self,
618
636
  field: &FieldDef,
637
+ py_name: &str,
619
638
  is_list: bool,
620
639
  base_type: &str,
621
640
  meta_dict: &str,
@@ -626,11 +645,16 @@ impl PythonCodegen {
626
645
  .map(|f| self.format_pydantic_facets(f))
627
646
  .unwrap_or_default();
628
647
 
629
- let extra_clause = if facet_args.is_empty() {
630
- format!("json_schema_extra={}", meta_dict)
631
- } else {
632
- format!("json_schema_extra={}, {}", meta_dict, facet_args)
633
- };
648
+ let mut clauses = Vec::new();
649
+ if self.options.emit_json_metadata && py_name != field.xml_name {
650
+ clauses.push(format!("alias=\"{}\"", field.xml_name));
651
+ clauses.push(format!("serialization_alias=\"{}\"", field.xml_name));
652
+ }
653
+ clauses.push(format!("json_schema_extra={}", meta_dict));
654
+ if !facet_args.is_empty() {
655
+ clauses.push(facet_args);
656
+ }
657
+ let extra_clause = clauses.join(", ");
634
658
 
635
659
  if is_list {
636
660
  (
@@ -675,6 +699,9 @@ impl PythonCodegen {
675
699
  let mut parts = Vec::new();
676
700
  parts.push(format!("\"type\": \"{}\"", kind_str));
677
701
  parts.push(format!("\"name\": \"{}\"", field.xml_name));
702
+ if self.options.emit_json_metadata {
703
+ parts.push(format!("\"json_name\": \"{}\"", field.xml_name));
704
+ }
678
705
 
679
706
  if let Some(ref ns) = field.namespace {
680
707
  parts.push(format!("\"namespace\": \"{}\"", ns));
@@ -1240,6 +1240,36 @@ impl RustCodegen {
1240
1240
  out.push_str(" String::from_utf8(bytes).map_err(|e| PolyXmlError::Utf8Error(e.utf8_error()))\n");
1241
1241
  out.push_str(" }\n\n");
1242
1242
 
1243
+ if self.options.derive_serde {
1244
+ let from_json_sig = if needs_lifetime {
1245
+ "pub fn from_json_str(json_str: &'a str) -> std::result::Result<Self, serde_json::Error>"
1246
+ } else {
1247
+ "pub fn from_json_str(json_str: &str) -> std::result::Result<Self, serde_json::Error>"
1248
+ };
1249
+ let _ = writeln!(out, " {}", from_json_sig);
1250
+ out.push_str(" {\n");
1251
+ out.push_str(" serde_json::from_str(json_str)\n");
1252
+ out.push_str(" }\n\n");
1253
+
1254
+ let from_json_slice_sig = if needs_lifetime {
1255
+ "pub fn from_json_slice(bytes: &'a [u8]) -> std::result::Result<Self, serde_json::Error>"
1256
+ } else {
1257
+ "pub fn from_json_slice(bytes: &[u8]) -> std::result::Result<Self, serde_json::Error>"
1258
+ };
1259
+ let _ = writeln!(out, " {}", from_json_slice_sig);
1260
+ out.push_str(" {\n");
1261
+ out.push_str(" serde_json::from_slice(bytes)\n");
1262
+ out.push_str(" }\n\n");
1263
+
1264
+ out.push_str(" pub fn to_json_string(&self) -> std::result::Result<String, serde_json::Error> {\n");
1265
+ out.push_str(" serde_json::to_string(self)\n");
1266
+ out.push_str(" }\n\n");
1267
+
1268
+ out.push_str(" pub fn to_json_vec(&self) -> std::result::Result<Vec<u8>, serde_json::Error> {\n");
1269
+ out.push_str(" serde_json::to_vec(self)\n");
1270
+ out.push_str(" }\n\n");
1271
+ }
1272
+
1243
1273
  out.push_str(" pub fn encode_xml<W: std::io::Write>(&self, writer: &mut Writer<W>, tag_name: Option<&str>) -> Result<()> {\n");
1244
1274
  let _ = writeln!(
1245
1275
  out,
@@ -1860,13 +1890,40 @@ impl RustCodegen {
1860
1890
  out.push_str(" }\n\n");
1861
1891
 
1862
1892
  out.push_str(" pub fn to_xml_string(&self) -> Result<String> {\n");
1863
- out.push_str(
1864
- " let bytes = self.to_xml()?;
1865
- ",
1866
- );
1893
+ out.push_str(" let bytes = self.to_xml()?;\n");
1867
1894
  out.push_str(" String::from_utf8(bytes).map_err(|e| PolyXmlError::Utf8Error(e.utf8_error()))\n");
1868
1895
  out.push_str(" }\n\n");
1869
1896
 
1897
+ if self.options.derive_serde {
1898
+ let from_json_sig = if needs_lifetime {
1899
+ "pub fn from_json_str(json_str: &'a str) -> std::result::Result<Self, serde_json::Error>"
1900
+ } else {
1901
+ "pub fn from_json_str(json_str: &str) -> std::result::Result<Self, serde_json::Error>"
1902
+ };
1903
+ let _ = writeln!(out, " {}", from_json_sig);
1904
+ out.push_str(" {\n");
1905
+ out.push_str(" serde_json::from_str(json_str)\n");
1906
+ out.push_str(" }\n\n");
1907
+
1908
+ let from_json_slice_sig = if needs_lifetime {
1909
+ "pub fn from_json_slice(bytes: &'a [u8]) -> std::result::Result<Self, serde_json::Error>"
1910
+ } else {
1911
+ "pub fn from_json_slice(bytes: &[u8]) -> std::result::Result<Self, serde_json::Error>"
1912
+ };
1913
+ let _ = writeln!(out, " {}", from_json_slice_sig);
1914
+ out.push_str(" {\n");
1915
+ out.push_str(" serde_json::from_slice(bytes)\n");
1916
+ out.push_str(" }\n\n");
1917
+
1918
+ out.push_str(" pub fn to_json_string(&self) -> std::result::Result<String, serde_json::Error> {\n");
1919
+ out.push_str(" serde_json::to_string(self)\n");
1920
+ out.push_str(" }\n\n");
1921
+
1922
+ out.push_str(" pub fn to_json_vec(&self) -> std::result::Result<Vec<u8>, serde_json::Error> {\n");
1923
+ out.push_str(" serde_json::to_vec(self)\n");
1924
+ out.push_str(" }\n\n");
1925
+ }
1926
+
1870
1927
  out.push_str(" pub fn encode_xml<W: std::io::Write>(&self, writer: &mut Writer<W>, tag_name: Option<&str>) -> Result<()> {\n");
1871
1928
  out.push_str(" match self {\n");
1872
1929
  for branch in &u.branches {
@@ -49,6 +49,9 @@ pub enum PolyXmlError {
49
49
 
50
50
  #[error("I/O error: {0}")]
51
51
  IoError(#[from] std::io::Error),
52
+
53
+ #[error("JSON error: {0}")]
54
+ JsonError(#[from] serde_json::Error),
52
55
  }
53
56
 
54
57
  pub type Result<T> = std::result::Result<T, PolyXmlError>;