structurize 2.18.2__py3-none-any.whl → 2.20.0__py3-none-any.whl
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.
- avrotize/_version.py +3 -3
- avrotize/avrotocsharp.py +21 -2
- avrotize/avrotojava.py +16 -6
- avrotize/avrotots.py +96 -0
- avrotize/cddltostructure.py +1841 -0
- avrotize/commands.json +226 -0
- avrotize/constants.py +71 -4
- avrotize/dependencies/cpp/vcpkg/vcpkg.json +19 -0
- avrotize/dependencies/typescript/node22/package.json +16 -0
- avrotize/dependency_version.py +432 -0
- avrotize/structuretocddl.py +597 -0
- avrotize/structuretocsharp.py +311 -21
- avrotize/structuretojava.py +853 -0
- {structurize-2.18.2.dist-info → structurize-2.20.0.dist-info}/METADATA +1 -1
- {structurize-2.18.2.dist-info → structurize-2.20.0.dist-info}/RECORD +19 -13
- {structurize-2.18.2.dist-info → structurize-2.20.0.dist-info}/WHEEL +0 -0
- {structurize-2.18.2.dist-info → structurize-2.20.0.dist-info}/entry_points.txt +0 -0
- {structurize-2.18.2.dist-info → structurize-2.20.0.dist-info}/licenses/LICENSE +0 -0
- {structurize-2.18.2.dist-info → structurize-2.20.0.dist-info}/top_level.txt +0 -0
avrotize/structuretocsharp.py
CHANGED
|
@@ -9,6 +9,14 @@ from typing import Any, Dict, List, Tuple, Union, cast, Optional
|
|
|
9
9
|
import uuid
|
|
10
10
|
|
|
11
11
|
from avrotize.common import pascal, process_template
|
|
12
|
+
from avrotize.constants import (
|
|
13
|
+
NEWTONSOFT_JSON_VERSION,
|
|
14
|
+
SYSTEM_TEXT_JSON_VERSION,
|
|
15
|
+
SYSTEM_MEMORY_DATA_VERSION,
|
|
16
|
+
NUNIT_VERSION,
|
|
17
|
+
NUNIT_ADAPTER_VERSION,
|
|
18
|
+
MSTEST_SDK_VERSION,
|
|
19
|
+
)
|
|
12
20
|
import glob
|
|
13
21
|
|
|
14
22
|
JsonNode = Dict[str, 'JsonNode'] | List['JsonNode'] | str | None
|
|
@@ -35,6 +43,7 @@ class StructureToCSharp:
|
|
|
35
43
|
self.definitions: Dict[str, Any] = {}
|
|
36
44
|
self.schema_registry: Dict[str, Dict] = {} # Maps $id URIs to schemas
|
|
37
45
|
self.offers: Dict[str, Any] = {} # Maps add-in names to property definitions from $offers
|
|
46
|
+
self.needs_json_structure_converters = False # Track if any types need JSON Structure converters
|
|
38
47
|
|
|
39
48
|
def get_qualified_name(self, namespace: str, name: str) -> str:
|
|
40
49
|
""" Concatenates namespace and name with a dot separator """
|
|
@@ -91,6 +100,36 @@ class StructureToCSharp:
|
|
|
91
100
|
result = mapping.get(structure_type, 'object')
|
|
92
101
|
return result
|
|
93
102
|
|
|
103
|
+
def get_json_structure_converter(self, schema_type: str, is_required: bool) -> str | None:
|
|
104
|
+
""" Returns the appropriate JSON Structure converter type for types requiring string serialization.
|
|
105
|
+
|
|
106
|
+
Per JSON Structure Core spec, int64, uint64, int128, uint128, and decimal types
|
|
107
|
+
use string representation in JSON to preserve precision. Duration (TimeSpan)
|
|
108
|
+
uses ISO 8601 format.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
schema_type: The JSON Structure type name
|
|
112
|
+
is_required: Whether the property is required (affects nullable converter selection)
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
The converter class name if needed, or None if no special converter is required
|
|
116
|
+
"""
|
|
117
|
+
# Map JSON Structure types to their converter class names
|
|
118
|
+
converter_map = {
|
|
119
|
+
'int64': ('Int64StringConverter', 'NullableInt64StringConverter'),
|
|
120
|
+
'uint64': ('UInt64StringConverter', 'NullableUInt64StringConverter'),
|
|
121
|
+
'int128': ('Int128StringConverter', 'NullableInt128StringConverter'),
|
|
122
|
+
'uint128': ('UInt128StringConverter', 'NullableUInt128StringConverter'),
|
|
123
|
+
'decimal': ('DecimalStringConverter', 'NullableDecimalStringConverter'),
|
|
124
|
+
'duration': ('TimeSpanIso8601Converter', 'NullableTimeSpanIso8601Converter'),
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if schema_type in converter_map:
|
|
128
|
+
required_converter, nullable_converter = converter_map[schema_type]
|
|
129
|
+
return required_converter if is_required else nullable_converter
|
|
130
|
+
|
|
131
|
+
return None
|
|
132
|
+
|
|
94
133
|
def is_csharp_reserved_word(self, word: str) -> bool:
|
|
95
134
|
""" Checks if a word is a reserved C# keyword """
|
|
96
135
|
reserved_words = [
|
|
@@ -326,14 +365,15 @@ class StructureToCSharp:
|
|
|
326
365
|
|
|
327
366
|
# Add dictionary for additional properties if needed
|
|
328
367
|
if additional_props is not False and additional_props is not None:
|
|
368
|
+
fields_str.append(f"{INDENT}/// <summary>\n{INDENT}/// Additional properties not defined in schema\n{INDENT}/// </summary>\n")
|
|
369
|
+
# Use JsonExtensionData for automatic capture of unknown properties during deserialization
|
|
370
|
+
fields_str.append(f"{INDENT}[System.Text.Json.Serialization.JsonExtensionData]\n")
|
|
329
371
|
if isinstance(additional_props, dict):
|
|
330
|
-
# additionalProperties is a schema
|
|
372
|
+
# additionalProperties is a schema - use the typed value
|
|
331
373
|
value_type = self.convert_structure_type_to_csharp(class_name, 'additionalValue', additional_props, schema_namespace)
|
|
332
|
-
fields_str.append(f"{INDENT}/// <summary>\n{INDENT}/// Additional properties not defined in schema\n{INDENT}/// </summary>\n")
|
|
333
374
|
fields_str.append(f"{INDENT}public Dictionary<string, {value_type}>? AdditionalProperties {{ get; set; }}\n")
|
|
334
|
-
|
|
335
|
-
#
|
|
336
|
-
fields_str.append(f"{INDENT}/// <summary>\n{INDENT}/// Additional properties not defined in schema\n{INDENT}/// </summary>\n")
|
|
375
|
+
else:
|
|
376
|
+
# additionalProperties: true - allow any additional properties with boxed values
|
|
337
377
|
fields_str.append(f"{INDENT}public Dictionary<string, object>? AdditionalProperties {{ get; set; }}\n")
|
|
338
378
|
|
|
339
379
|
class_body = "\n".join(fields_str)
|
|
@@ -400,8 +440,9 @@ class StructureToCSharp:
|
|
|
400
440
|
doc = prop_schema.get('description', prop_schema.get('doc', field_name_cs))
|
|
401
441
|
property_definition += f"{INDENT}/// <summary>\n{INDENT}/// {doc}\n{INDENT}/// </summary>\n"
|
|
402
442
|
|
|
403
|
-
# Add JSON property name annotation
|
|
404
|
-
|
|
443
|
+
# Add JSON property name annotation when property name differs from schema name
|
|
444
|
+
# This is needed for proper JSON serialization/deserialization, especially with pascal_properties
|
|
445
|
+
if field_name != field_name_cs:
|
|
405
446
|
property_definition += f'{INDENT}[System.Text.Json.Serialization.JsonPropertyName("{prop_name}")]\n'
|
|
406
447
|
if self.newtonsoft_json_annotation and field_name != field_name_cs:
|
|
407
448
|
property_definition += f'{INDENT}[Newtonsoft.Json.JsonProperty("{prop_name}")]\n'
|
|
@@ -430,8 +471,9 @@ class StructureToCSharp:
|
|
|
430
471
|
doc = prop_schema.get('description', prop_schema.get('doc', field_name_cs))
|
|
431
472
|
property_definition += f"{INDENT}/// <summary>\n{INDENT}/// {doc}\n{INDENT}/// </summary>\n"
|
|
432
473
|
|
|
433
|
-
# Add JSON property name annotation
|
|
434
|
-
|
|
474
|
+
# Add JSON property name annotation when property name differs from schema name
|
|
475
|
+
# This is needed for proper JSON serialization/deserialization, especially with pascal_properties
|
|
476
|
+
if field_name != field_name_cs:
|
|
435
477
|
property_definition += f'{INDENT}[System.Text.Json.Serialization.JsonPropertyName("{prop_name}")]\n'
|
|
436
478
|
if self.newtonsoft_json_annotation and field_name != field_name_cs:
|
|
437
479
|
property_definition += f'{INDENT}[Newtonsoft.Json.JsonProperty("{prop_name}")]\n'
|
|
@@ -440,6 +482,14 @@ class StructureToCSharp:
|
|
|
440
482
|
if self.system_xml_annotation:
|
|
441
483
|
property_definition += f'{INDENT}[System.Xml.Serialization.XmlElement("{prop_name}")]\n'
|
|
442
484
|
|
|
485
|
+
# Add JSON Structure converters for types requiring string serialization
|
|
486
|
+
if self.system_text_json_annotation:
|
|
487
|
+
schema_type = prop_schema.get('type', '')
|
|
488
|
+
converter_type = self.get_json_structure_converter(schema_type, is_required)
|
|
489
|
+
if converter_type:
|
|
490
|
+
property_definition += f'{INDENT}[System.Text.Json.Serialization.JsonConverter(typeof({converter_type}))]\n'
|
|
491
|
+
self.needs_json_structure_converters = True
|
|
492
|
+
|
|
443
493
|
# Add validation attributes based on schema constraints
|
|
444
494
|
# StringLength attribute for maxLength
|
|
445
495
|
if 'maxLength' in prop_schema:
|
|
@@ -556,13 +606,16 @@ class StructureToCSharp:
|
|
|
556
606
|
deprecated_msg = structure_schema.get('description', f'{enum_name} is deprecated')
|
|
557
607
|
enum_definition += f"[System.Obsolete(\"{deprecated_msg}\")]\n"
|
|
558
608
|
|
|
609
|
+
# Add converter attributes - always include System.Text.Json since it's the default .NET serializer
|
|
610
|
+
# This ensures enums serialize correctly with proper value mapping even if system_text_json_annotation is False
|
|
611
|
+
enum_definition += f"[System.Text.Json.Serialization.JsonConverter(typeof({enum_name}Converter))]\n"
|
|
612
|
+
if self.newtonsoft_json_annotation:
|
|
613
|
+
enum_definition += f"[Newtonsoft.Json.JsonConverter(typeof({enum_name}NewtonsoftConverter))]\n"
|
|
614
|
+
|
|
559
615
|
if is_numeric:
|
|
560
616
|
cs_base_type = self.map_primitive_to_csharp(base_type)
|
|
561
617
|
enum_definition += f"public enum {enum_name} : {cs_base_type}\n{{\n"
|
|
562
618
|
else:
|
|
563
|
-
# String enum - for System.Text.Json, use JsonConverter with JsonStringEnumConverter
|
|
564
|
-
if self.system_text_json_annotation:
|
|
565
|
-
enum_definition += f"[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]\n"
|
|
566
619
|
enum_definition += f"public enum {enum_name}\n{{\n"
|
|
567
620
|
|
|
568
621
|
# Generate enum members
|
|
@@ -581,7 +634,103 @@ class StructureToCSharp:
|
|
|
581
634
|
else:
|
|
582
635
|
enum_definition += "\n"
|
|
583
636
|
|
|
584
|
-
enum_definition += "}"
|
|
637
|
+
enum_definition += "}\n\n"
|
|
638
|
+
|
|
639
|
+
# Always generate System.Text.Json converter since it's the default .NET serializer
|
|
640
|
+
# This ensures enums serialize correctly even when system_text_json_annotation is False
|
|
641
|
+
enum_definition += f"/// <summary>\n/// System.Text.Json converter for {enum_name} that maps to schema values\n/// </summary>\n"
|
|
642
|
+
enum_definition += f"public class {enum_name}Converter : System.Text.Json.Serialization.JsonConverter<{enum_name}>\n{{\n"
|
|
643
|
+
|
|
644
|
+
# Read method
|
|
645
|
+
enum_definition += f"{INDENT}/// <inheritdoc/>\n"
|
|
646
|
+
enum_definition += f"{INDENT}public override {enum_name} Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options)\n"
|
|
647
|
+
enum_definition += f"{INDENT}{{\n"
|
|
648
|
+
|
|
649
|
+
if is_numeric:
|
|
650
|
+
enum_definition += f"{INDENT*2}if (reader.TokenType == System.Text.Json.JsonTokenType.Number)\n"
|
|
651
|
+
enum_definition += f"{INDENT*2}{{\n"
|
|
652
|
+
enum_definition += f"{INDENT*3}return ({enum_name})reader.GetInt32();\n"
|
|
653
|
+
enum_definition += f"{INDENT*2}}}\n"
|
|
654
|
+
enum_definition += f"{INDENT*2}throw new System.Text.Json.JsonException($\"Expected number for {enum_name}\");\n"
|
|
655
|
+
else:
|
|
656
|
+
enum_definition += f"{INDENT*2}var stringValue = reader.GetString();\n"
|
|
657
|
+
enum_definition += f"{INDENT*2}return stringValue switch\n"
|
|
658
|
+
enum_definition += f"{INDENT*2}{{\n"
|
|
659
|
+
for value in enum_values:
|
|
660
|
+
member_name = pascal(str(value).replace('-', '_').replace(' ', '_'))
|
|
661
|
+
enum_definition += f'{INDENT*3}"{value}" => {enum_name}.{member_name},\n'
|
|
662
|
+
enum_definition += f'{INDENT*3}_ => throw new System.Text.Json.JsonException($"Unknown value \'{{stringValue}}\' for {enum_name}")\n'
|
|
663
|
+
enum_definition += f"{INDENT*2}}};\n"
|
|
664
|
+
|
|
665
|
+
enum_definition += f"{INDENT}}}\n\n"
|
|
666
|
+
|
|
667
|
+
# Write method
|
|
668
|
+
enum_definition += f"{INDENT}/// <inheritdoc/>\n"
|
|
669
|
+
enum_definition += f"{INDENT}public override void Write(System.Text.Json.Utf8JsonWriter writer, {enum_name} value, System.Text.Json.JsonSerializerOptions options)\n"
|
|
670
|
+
enum_definition += f"{INDENT}{{\n"
|
|
671
|
+
|
|
672
|
+
if is_numeric:
|
|
673
|
+
enum_definition += f"{INDENT*2}writer.WriteNumberValue((int)value);\n"
|
|
674
|
+
else:
|
|
675
|
+
enum_definition += f"{INDENT*2}var stringValue = value switch\n"
|
|
676
|
+
enum_definition += f"{INDENT*2}{{\n"
|
|
677
|
+
for value in enum_values:
|
|
678
|
+
member_name = pascal(str(value).replace('-', '_').replace(' ', '_'))
|
|
679
|
+
enum_definition += f'{INDENT*3}{enum_name}.{member_name} => "{value}",\n'
|
|
680
|
+
enum_definition += f'{INDENT*3}_ => throw new System.ArgumentOutOfRangeException(nameof(value))\n'
|
|
681
|
+
enum_definition += f"{INDENT*2}}};\n"
|
|
682
|
+
enum_definition += f"{INDENT*2}writer.WriteStringValue(stringValue);\n"
|
|
683
|
+
|
|
684
|
+
enum_definition += f"{INDENT}}}\n"
|
|
685
|
+
enum_definition += "}\n\n"
|
|
686
|
+
|
|
687
|
+
# Generate Newtonsoft.Json converter when enabled
|
|
688
|
+
if self.newtonsoft_json_annotation:
|
|
689
|
+
enum_definition += f"/// <summary>\n/// Newtonsoft.Json converter for {enum_name} that maps to schema values\n/// </summary>\n"
|
|
690
|
+
enum_definition += f"public class {enum_name}NewtonsoftConverter : Newtonsoft.Json.JsonConverter<{enum_name}>\n{{\n"
|
|
691
|
+
|
|
692
|
+
# ReadJson method
|
|
693
|
+
enum_definition += f"{INDENT}/// <inheritdoc/>\n"
|
|
694
|
+
enum_definition += f"{INDENT}public override {enum_name} ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, {enum_name} existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)\n"
|
|
695
|
+
enum_definition += f"{INDENT}{{\n"
|
|
696
|
+
|
|
697
|
+
if is_numeric:
|
|
698
|
+
enum_definition += f"{INDENT*2}if (reader.TokenType == Newtonsoft.Json.JsonToken.Integer)\n"
|
|
699
|
+
enum_definition += f"{INDENT*2}{{\n"
|
|
700
|
+
enum_definition += f"{INDENT*3}return ({enum_name})Convert.ToInt32(reader.Value);\n"
|
|
701
|
+
enum_definition += f"{INDENT*2}}}\n"
|
|
702
|
+
enum_definition += f"{INDENT*2}throw new Newtonsoft.Json.JsonException($\"Expected number for {enum_name}\");\n"
|
|
703
|
+
else:
|
|
704
|
+
enum_definition += f"{INDENT*2}var stringValue = reader.Value?.ToString();\n"
|
|
705
|
+
enum_definition += f"{INDENT*2}return stringValue switch\n"
|
|
706
|
+
enum_definition += f"{INDENT*2}{{\n"
|
|
707
|
+
for value in enum_values:
|
|
708
|
+
member_name = pascal(str(value).replace('-', '_').replace(' ', '_'))
|
|
709
|
+
enum_definition += f'{INDENT*3}"{value}" => {enum_name}.{member_name},\n'
|
|
710
|
+
enum_definition += f'{INDENT*3}_ => throw new Newtonsoft.Json.JsonException($"Unknown value \'{{stringValue}}\' for {enum_name}")\n'
|
|
711
|
+
enum_definition += f"{INDENT*2}}};\n"
|
|
712
|
+
|
|
713
|
+
enum_definition += f"{INDENT}}}\n\n"
|
|
714
|
+
|
|
715
|
+
# WriteJson method
|
|
716
|
+
enum_definition += f"{INDENT}/// <inheritdoc/>\n"
|
|
717
|
+
enum_definition += f"{INDENT}public override void WriteJson(Newtonsoft.Json.JsonWriter writer, {enum_name} value, Newtonsoft.Json.JsonSerializer serializer)\n"
|
|
718
|
+
enum_definition += f"{INDENT}{{\n"
|
|
719
|
+
|
|
720
|
+
if is_numeric:
|
|
721
|
+
enum_definition += f"{INDENT*2}writer.WriteValue((int)value);\n"
|
|
722
|
+
else:
|
|
723
|
+
enum_definition += f"{INDENT*2}var stringValue = value switch\n"
|
|
724
|
+
enum_definition += f"{INDENT*2}{{\n"
|
|
725
|
+
for value in enum_values:
|
|
726
|
+
member_name = pascal(str(value).replace('-', '_').replace(' ', '_'))
|
|
727
|
+
enum_definition += f'{INDENT*3}{enum_name}.{member_name} => "{value}",\n'
|
|
728
|
+
enum_definition += f'{INDENT*3}_ => throw new System.ArgumentOutOfRangeException(nameof(value))\n'
|
|
729
|
+
enum_definition += f"{INDENT*2}}};\n"
|
|
730
|
+
enum_definition += f"{INDENT*2}writer.WriteValue(stringValue);\n"
|
|
731
|
+
|
|
732
|
+
enum_definition += f"{INDENT}}}\n"
|
|
733
|
+
enum_definition += "}\n"
|
|
585
734
|
|
|
586
735
|
if write_file:
|
|
587
736
|
self.write_to_file(namespace, enum_name, enum_definition)
|
|
@@ -634,6 +783,8 @@ class StructureToCSharp:
|
|
|
634
783
|
|
|
635
784
|
# Generate the union class similar to Avro unions
|
|
636
785
|
class_definition = f"/// <summary>\n/// {structure_schema.get('description', class_name)}\n/// </summary>\n"
|
|
786
|
+
# Add JsonConverter attribute for proper tagged union serialization
|
|
787
|
+
class_definition += f"[System.Text.Json.Serialization.JsonConverter(typeof({class_name}JsonConverter))]\n"
|
|
637
788
|
class_definition += f"public partial class {class_name}\n{{\n"
|
|
638
789
|
|
|
639
790
|
# Generate properties for each choice
|
|
@@ -687,7 +838,58 @@ class StructureToCSharp:
|
|
|
687
838
|
|
|
688
839
|
class_definition += f"{INDENT}}}\n"
|
|
689
840
|
|
|
690
|
-
class_definition += "}"
|
|
841
|
+
class_definition += "}\n\n"
|
|
842
|
+
|
|
843
|
+
# Generate JSON converter for tagged union serialization
|
|
844
|
+
class_definition += f"/// <summary>\n/// JSON converter for {class_name} tagged union - serializes only the non-null choice\n/// </summary>\n"
|
|
845
|
+
class_definition += f"public class {class_name}JsonConverter : System.Text.Json.Serialization.JsonConverter<{class_name}>\n{{\n"
|
|
846
|
+
|
|
847
|
+
# Read method
|
|
848
|
+
class_definition += f"{INDENT}/// <inheritdoc/>\n"
|
|
849
|
+
class_definition += f"{INDENT}public override {class_name}? Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options)\n"
|
|
850
|
+
class_definition += f"{INDENT}{{\n"
|
|
851
|
+
class_definition += f"{INDENT*2}if (reader.TokenType == System.Text.Json.JsonTokenType.Null) return null;\n"
|
|
852
|
+
class_definition += f"{INDENT*2}if (reader.TokenType != System.Text.Json.JsonTokenType.StartObject)\n"
|
|
853
|
+
class_definition += f"{INDENT*3}throw new System.Text.Json.JsonException(\"Expected object for tagged union\");\n"
|
|
854
|
+
class_definition += f"{INDENT*2}var result = new {class_name}();\n"
|
|
855
|
+
class_definition += f"{INDENT*2}while (reader.Read())\n"
|
|
856
|
+
class_definition += f"{INDENT*2}{{\n"
|
|
857
|
+
class_definition += f"{INDENT*3}if (reader.TokenType == System.Text.Json.JsonTokenType.EndObject) break;\n"
|
|
858
|
+
class_definition += f"{INDENT*3}if (reader.TokenType != System.Text.Json.JsonTokenType.PropertyName)\n"
|
|
859
|
+
class_definition += f"{INDENT*4}throw new System.Text.Json.JsonException(\"Expected property name\");\n"
|
|
860
|
+
class_definition += f"{INDENT*3}var propName = reader.GetString();\n"
|
|
861
|
+
class_definition += f"{INDENT*3}reader.Read();\n"
|
|
862
|
+
class_definition += f"{INDENT*3}switch (propName)\n"
|
|
863
|
+
class_definition += f"{INDENT*3}{{\n"
|
|
864
|
+
for choice_name, choice_type in choice_types:
|
|
865
|
+
# Use original schema property name for matching
|
|
866
|
+
class_definition += f'{INDENT*4}case "{choice_name}":\n'
|
|
867
|
+
class_definition += f"{INDENT*5}result.{pascal(choice_name)} = System.Text.Json.JsonSerializer.Deserialize<{choice_type}>(ref reader, options);\n"
|
|
868
|
+
class_definition += f"{INDENT*5}break;\n"
|
|
869
|
+
class_definition += f"{INDENT*4}default:\n"
|
|
870
|
+
class_definition += f"{INDENT*5}reader.Skip();\n"
|
|
871
|
+
class_definition += f"{INDENT*5}break;\n"
|
|
872
|
+
class_definition += f"{INDENT*3}}}\n"
|
|
873
|
+
class_definition += f"{INDENT*2}}}\n"
|
|
874
|
+
class_definition += f"{INDENT*2}return result;\n"
|
|
875
|
+
class_definition += f"{INDENT}}}\n\n"
|
|
876
|
+
|
|
877
|
+
# Write method - only write the non-null choice
|
|
878
|
+
class_definition += f"{INDENT}/// <inheritdoc/>\n"
|
|
879
|
+
class_definition += f"{INDENT}public override void Write(System.Text.Json.Utf8JsonWriter writer, {class_name} value, System.Text.Json.JsonSerializerOptions options)\n"
|
|
880
|
+
class_definition += f"{INDENT}{{\n"
|
|
881
|
+
class_definition += f"{INDENT*2}writer.WriteStartObject();\n"
|
|
882
|
+
for i, (choice_name, choice_type) in enumerate(choice_types):
|
|
883
|
+
prop_name = pascal(choice_name)
|
|
884
|
+
condition = "if" if i == 0 else "else if"
|
|
885
|
+
class_definition += f"{INDENT*2}{condition} (value.{prop_name} != null)\n"
|
|
886
|
+
class_definition += f"{INDENT*2}{{\n"
|
|
887
|
+
class_definition += f'{INDENT*3}writer.WritePropertyName("{choice_name}");\n'
|
|
888
|
+
class_definition += f"{INDENT*3}System.Text.Json.JsonSerializer.Serialize(writer, value.{prop_name}, options);\n"
|
|
889
|
+
class_definition += f"{INDENT*2}}}\n"
|
|
890
|
+
class_definition += f"{INDENT*2}writer.WriteEndObject();\n"
|
|
891
|
+
class_definition += f"{INDENT}}}\n"
|
|
892
|
+
class_definition += "}\n"
|
|
691
893
|
|
|
692
894
|
if write_file:
|
|
693
895
|
self.write_to_file(namespace, class_name, class_definition)
|
|
@@ -1011,8 +1213,10 @@ class StructureToCSharp:
|
|
|
1011
1213
|
# Generate wrapper class with implicit conversions
|
|
1012
1214
|
class_definition = f"/// <summary>\n/// {structure_schema.get('description', class_name)}\n/// </summary>\n"
|
|
1013
1215
|
class_definition += f"/// <remarks>\n/// Wrapper for root-level {struct_type} type\n/// </remarks>\n"
|
|
1216
|
+
# Add JsonConverter attribute to serialize as the underlying collection
|
|
1217
|
+
class_definition += f"[System.Text.Json.Serialization.JsonConverter(typeof({class_name}JsonConverter))]\n"
|
|
1014
1218
|
class_definition += f"public class {class_name}\n{{\n"
|
|
1015
|
-
class_definition += f"{INDENT}
|
|
1219
|
+
class_definition += f"{INDENT}internal {underlying_type} _value = new();\n\n"
|
|
1016
1220
|
|
|
1017
1221
|
# Add indexer or collection access
|
|
1018
1222
|
if struct_type == 'map':
|
|
@@ -1082,6 +1286,22 @@ class StructureToCSharp:
|
|
|
1082
1286
|
# Implicit conversion from underlying type
|
|
1083
1287
|
class_definition += f"{INDENT}public static implicit operator {class_name}({underlying_type} value) => new() {{ _value = value }};\n"
|
|
1084
1288
|
|
|
1289
|
+
class_definition += "}\n\n"
|
|
1290
|
+
|
|
1291
|
+
# Generate custom JsonConverter for the wrapper class to serialize as the underlying collection
|
|
1292
|
+
class_definition += f"/// <summary>\n/// JSON converter for {class_name} to serialize as the underlying collection\n/// </summary>\n"
|
|
1293
|
+
class_definition += f"public class {class_name}JsonConverter : System.Text.Json.Serialization.JsonConverter<{class_name}>\n{{\n"
|
|
1294
|
+
class_definition += f"{INDENT}/// <inheritdoc/>\n"
|
|
1295
|
+
class_definition += f"{INDENT}public override {class_name}? Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options)\n"
|
|
1296
|
+
class_definition += f"{INDENT}{{\n"
|
|
1297
|
+
class_definition += f"{INDENT*2}var value = System.Text.Json.JsonSerializer.Deserialize<{underlying_type}>(ref reader, options);\n"
|
|
1298
|
+
class_definition += f"{INDENT*2}return value == null ? null : new {class_name}() {{ _value = value }};\n"
|
|
1299
|
+
class_definition += f"{INDENT}}}\n\n"
|
|
1300
|
+
class_definition += f"{INDENT}/// <inheritdoc/>\n"
|
|
1301
|
+
class_definition += f"{INDENT}public override void Write(System.Text.Json.Utf8JsonWriter writer, {class_name} value, System.Text.Json.JsonSerializerOptions options)\n"
|
|
1302
|
+
class_definition += f"{INDENT}{{\n"
|
|
1303
|
+
class_definition += f"{INDENT*2}System.Text.Json.JsonSerializer.Serialize(writer, value._value, options);\n"
|
|
1304
|
+
class_definition += f"{INDENT}}}\n"
|
|
1085
1305
|
class_definition += "}\n"
|
|
1086
1306
|
|
|
1087
1307
|
if write_file:
|
|
@@ -1310,7 +1530,13 @@ class StructureToCSharp:
|
|
|
1310
1530
|
project_name=project_name,
|
|
1311
1531
|
system_xml_annotation=self.system_xml_annotation,
|
|
1312
1532
|
system_text_json_annotation=self.system_text_json_annotation,
|
|
1313
|
-
newtonsoft_json_annotation=self.newtonsoft_json_annotation
|
|
1533
|
+
newtonsoft_json_annotation=self.newtonsoft_json_annotation,
|
|
1534
|
+
NEWTONSOFT_JSON_VERSION=NEWTONSOFT_JSON_VERSION,
|
|
1535
|
+
SYSTEM_TEXT_JSON_VERSION=SYSTEM_TEXT_JSON_VERSION,
|
|
1536
|
+
SYSTEM_MEMORY_DATA_VERSION=SYSTEM_MEMORY_DATA_VERSION,
|
|
1537
|
+
NUNIT_VERSION=NUNIT_VERSION,
|
|
1538
|
+
NUNIT_ADAPTER_VERSION=NUNIT_ADAPTER_VERSION,
|
|
1539
|
+
MSTEST_SDK_VERSION=MSTEST_SDK_VERSION))
|
|
1314
1540
|
|
|
1315
1541
|
# Create test project file if it doesn't exist
|
|
1316
1542
|
if not glob.glob(os.path.join(output_dir, "test", "*.csproj")):
|
|
@@ -1324,7 +1550,10 @@ class StructureToCSharp:
|
|
|
1324
1550
|
project_name=project_name,
|
|
1325
1551
|
system_xml_annotation=self.system_xml_annotation,
|
|
1326
1552
|
system_text_json_annotation=self.system_text_json_annotation,
|
|
1327
|
-
newtonsoft_json_annotation=self.newtonsoft_json_annotation
|
|
1553
|
+
newtonsoft_json_annotation=self.newtonsoft_json_annotation,
|
|
1554
|
+
NUNIT_VERSION=NUNIT_VERSION,
|
|
1555
|
+
NUNIT_ADAPTER_VERSION=NUNIT_ADAPTER_VERSION,
|
|
1556
|
+
MSTEST_SDK_VERSION=MSTEST_SDK_VERSION))
|
|
1328
1557
|
|
|
1329
1558
|
self.output_dir = output_dir
|
|
1330
1559
|
|
|
@@ -1365,6 +1594,7 @@ class StructureToCSharp:
|
|
|
1365
1594
|
# Generate tuple converter utility class if needed (after all types processed)
|
|
1366
1595
|
if self.system_text_json_annotation:
|
|
1367
1596
|
self.generate_tuple_converter(output_dir)
|
|
1597
|
+
self.generate_json_structure_converters(output_dir)
|
|
1368
1598
|
|
|
1369
1599
|
# Generate tests
|
|
1370
1600
|
self.generate_tests(output_dir)
|
|
@@ -1716,6 +1946,36 @@ class StructureToCSharp:
|
|
|
1716
1946
|
with open(converter_file_path, 'w', encoding='utf-8') as converter_file:
|
|
1717
1947
|
converter_file.write(file_content)
|
|
1718
1948
|
|
|
1949
|
+
def generate_json_structure_converters(self, output_dir: str) -> None:
|
|
1950
|
+
""" Generates JSON Structure converters for types requiring string serialization.
|
|
1951
|
+
|
|
1952
|
+
Per JSON Structure Core spec, int64, uint64, int128, uint128, decimal types
|
|
1953
|
+
use string representation in JSON to preserve precision. Duration (TimeSpan)
|
|
1954
|
+
uses ISO 8601 format.
|
|
1955
|
+
"""
|
|
1956
|
+
# Check if any types need converters
|
|
1957
|
+
if not self.needs_json_structure_converters:
|
|
1958
|
+
return # No types need special converters
|
|
1959
|
+
|
|
1960
|
+
# Convert base namespace to PascalCase for consistency with other generated classes
|
|
1961
|
+
namespace_pascal = pascal(self.base_namespace)
|
|
1962
|
+
|
|
1963
|
+
# Generate the converter class
|
|
1964
|
+
converter_definition = process_template(
|
|
1965
|
+
"structuretocsharp/json_structure_converters.cs.jinja",
|
|
1966
|
+
namespace=namespace_pascal
|
|
1967
|
+
)
|
|
1968
|
+
|
|
1969
|
+
# Write to the same directory structure as other classes (using PascalCase path)
|
|
1970
|
+
directory_path = os.path.join(
|
|
1971
|
+
output_dir, os.path.join('src', namespace_pascal.replace('.', os.sep)))
|
|
1972
|
+
if not os.path.exists(directory_path):
|
|
1973
|
+
os.makedirs(directory_path, exist_ok=True)
|
|
1974
|
+
converter_file_path = os.path.join(directory_path, "JsonStructureConverters.cs")
|
|
1975
|
+
|
|
1976
|
+
with open(converter_file_path, 'w', encoding='utf-8') as converter_file:
|
|
1977
|
+
converter_file.write(converter_definition)
|
|
1978
|
+
|
|
1719
1979
|
def generate_instance_serializer(self, output_dir: str) -> None:
|
|
1720
1980
|
""" Generates InstanceSerializer.cs that creates instances and serializes them to JSON """
|
|
1721
1981
|
test_directory_path = os.path.join(output_dir, "test")
|
|
@@ -1767,9 +2027,13 @@ class StructureToCSharp:
|
|
|
1767
2027
|
if not classes:
|
|
1768
2028
|
return # No classes to serialize
|
|
1769
2029
|
|
|
2030
|
+
# Determine if ToByteArray method is available (requires any serialization annotation)
|
|
2031
|
+
has_to_byte_array = self.system_text_json_annotation or self.newtonsoft_json_annotation or self.system_xml_annotation
|
|
2032
|
+
|
|
1770
2033
|
program_definition = process_template(
|
|
1771
2034
|
"structuretocsharp/program.cs.jinja",
|
|
1772
|
-
classes=classes
|
|
2035
|
+
classes=classes,
|
|
2036
|
+
has_to_byte_array=has_to_byte_array
|
|
1773
2037
|
)
|
|
1774
2038
|
|
|
1775
2039
|
program_file_path = os.path.join(test_directory_path, "InstanceSerializer.cs")
|
|
@@ -1860,18 +2124,24 @@ class StructureToCSharp:
|
|
|
1860
2124
|
|
|
1861
2125
|
# Check if this is a const field
|
|
1862
2126
|
is_const = 'const' in prop_schema
|
|
1863
|
-
|
|
2127
|
+
schema_type = prop_schema.get('type', '') if isinstance(prop_schema, dict) else ''
|
|
2128
|
+
test_value = self.get_test_value(field_type, schema_type) if not is_const else self.format_default_value(prop_schema['const'], field_type)
|
|
1864
2129
|
|
|
1865
2130
|
f = Field(field_name, field_type, test_value, is_const, not is_class)
|
|
1866
2131
|
fields.append(f)
|
|
1867
2132
|
return cast(List[Any], fields)
|
|
1868
2133
|
|
|
1869
|
-
def get_test_value(self, csharp_type: str) -> str:
|
|
1870
|
-
"""Returns a default test value based on the C# type"""
|
|
2134
|
+
def get_test_value(self, csharp_type: str, schema_type: str = '') -> str:
|
|
2135
|
+
"""Returns a default test value based on the C# type and schema type"""
|
|
1871
2136
|
# For nullable object types, return typed null to avoid var issues
|
|
1872
2137
|
if csharp_type == "object?" or csharp_type == "object":
|
|
1873
2138
|
return "null" # Use null for object types (typically unions) to avoid reference inequality
|
|
1874
2139
|
|
|
2140
|
+
# Special test values for JSON Structure types that map to string in C#
|
|
2141
|
+
# but have specific format requirements
|
|
2142
|
+
if schema_type == 'jsonpointer':
|
|
2143
|
+
return '"/example/path"' # Valid JSON Pointer format
|
|
2144
|
+
|
|
1875
2145
|
test_values = {
|
|
1876
2146
|
'string': '"test_string"',
|
|
1877
2147
|
'bool': 'true',
|
|
@@ -1934,6 +2204,26 @@ class StructureToCSharp:
|
|
|
1934
2204
|
# Use the constructor that takes the first choice
|
|
1935
2205
|
return f'new {base_type}({choice_test_value})'
|
|
1936
2206
|
|
|
2207
|
+
# Check if this is an enum type
|
|
2208
|
+
if qualified_ref in self.generated_types and self.generated_types[qualified_ref] == "enum":
|
|
2209
|
+
schema = self.generated_structure_types.get(qualified_ref)
|
|
2210
|
+
if schema:
|
|
2211
|
+
enum_values = schema.get('enum', [])
|
|
2212
|
+
if enum_values:
|
|
2213
|
+
first_value = enum_values[0]
|
|
2214
|
+
enum_base_type = schema.get('type', 'string')
|
|
2215
|
+
numeric_types = ['int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64']
|
|
2216
|
+
is_numeric = enum_base_type in numeric_types
|
|
2217
|
+
|
|
2218
|
+
if is_numeric:
|
|
2219
|
+
# Numeric enum - use the member name (Value1, Value2, etc.)
|
|
2220
|
+
member_name = f"Value{first_value}"
|
|
2221
|
+
else:
|
|
2222
|
+
# String enum - convert to PascalCase member name
|
|
2223
|
+
member_name = pascal(str(first_value).replace('-', '_').replace(' ', '_'))
|
|
2224
|
+
|
|
2225
|
+
return f'{base_type}.{member_name}'
|
|
2226
|
+
|
|
1937
2227
|
return test_values.get(base_type, test_values.get(csharp_type, f'new {csharp_type}()'))
|
|
1938
2228
|
|
|
1939
2229
|
|