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.
@@ -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
- elif additional_props is True:
335
- # Allow any additional properties
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
- if self.system_text_json_annotation and field_name != field_name_cs:
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
- if self.system_text_json_annotation and field_name != field_name_cs:
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}private {underlying_type} _value = new();\n\n"
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
- test_value = self.get_test_value(field_type) if not is_const else self.format_default_value(prop_schema['const'], field_type)
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