structurize 3.2.0__tar.gz → 3.2.2__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.
- {structurize-3.2.0/structurize.egg-info → structurize-3.2.2}/PKG-INFO +1 -1
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/_version.py +3 -3
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/schema_inference.py +148 -24
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretocsharp.py +59 -19
- {structurize-3.2.0 → structurize-3.2.2/structurize.egg-info}/PKG-INFO +1 -1
- {structurize-3.2.0 → structurize-3.2.2}/.gitignore +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/LICENSE +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/MANIFEST.in +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/README.md +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/__init__.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/__main__.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/asn1toavro.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotize.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotocpp.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotocsharp.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotocsv.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotodatapackage.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotodb.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotogo.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotographql.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotoiceberg.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotojava.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotojs.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotojsons.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotojstruct.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotokusto.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotomd.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotools.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotoparquet.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotoproto.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotopython.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotorust.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotots.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrotoxsd.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/avrovalidator.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/cddltostructure.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/choice_inference.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/commands.json +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/common.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/constants.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/csvtoavro.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/datapackagetoavro.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/dependencies/cpp/vcpkg/vcpkg.json +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/dependencies/typescript/node22/package.json +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/dependency_resolver.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/dependency_version.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/jsonstoavro.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/jsonstostructure.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/jsontoschema.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/jstructtoavro.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/kstructtoavro.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/kustotoavro.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/openapitostructure.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/parquettoavro.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/proto2parser.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/proto3parser.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/prototoavro.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/sqltoavro.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretocddl.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretocpp.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretocsv.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretodatapackage.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretodb.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretogo.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretographql.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretoiceberg.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretojava.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretojs.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretojsons.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretokusto.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretomd.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretoproto.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretopython.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretorust.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretots.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/structuretoxsd.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/validate.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/xmltoschema.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/avrotize/xsdtoavro.py +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/build.ps1 +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/build.sh +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/pyproject.toml +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/setup.cfg +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/structurize.egg-info/SOURCES.txt +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/structurize.egg-info/dependency_links.txt +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/structurize.egg-info/entry_points.txt +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/structurize.egg-info/requires.txt +0 -0
- {structurize-3.2.0 → structurize-3.2.2}/structurize.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: structurize
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: Tools to convert from and to JSON Structure from various other schema languages.
|
|
5
5
|
Author-email: Clemens Vasters <clemensv@microsoft.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '3.2.
|
|
32
|
-
__version_tuple__ = version_tuple = (3, 2,
|
|
31
|
+
__version__ = version = '3.2.2'
|
|
32
|
+
__version_tuple__ = version_tuple = (3, 2, 2)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g4fc2eb920'
|
|
@@ -648,6 +648,22 @@ class JsonStructureSchemaInferrer(SchemaInferrer):
|
|
|
648
648
|
if python_value is None:
|
|
649
649
|
return "null"
|
|
650
650
|
|
|
651
|
+
# Handle integers with proper range detection
|
|
652
|
+
# bool is subclass of int in Python, so check bool first
|
|
653
|
+
if isinstance(python_value, bool):
|
|
654
|
+
return "boolean"
|
|
655
|
+
|
|
656
|
+
if isinstance(python_value, int):
|
|
657
|
+
# Check if value fits in int32 range
|
|
658
|
+
if -2147483648 <= python_value <= 2147483647:
|
|
659
|
+
return "integer" # int32 alias
|
|
660
|
+
else:
|
|
661
|
+
# Per JSON Structure spec, int64 values are string-encoded
|
|
662
|
+
# Since we're inferring from JSON native numbers (which can't exceed
|
|
663
|
+
# double precision ~2^53), use 'double' for large integers from JSON
|
|
664
|
+
# This allows validation of the source data as-is
|
|
665
|
+
return "double"
|
|
666
|
+
|
|
651
667
|
if isinstance(python_value, dict):
|
|
652
668
|
# Generate an object type
|
|
653
669
|
safe_name = avro_name(type_name.rsplit('.', 1)[-1])
|
|
@@ -691,7 +707,16 @@ class JsonStructureSchemaInferrer(SchemaInferrer):
|
|
|
691
707
|
items = item_types[0]
|
|
692
708
|
else:
|
|
693
709
|
# Use choice for multiple item types
|
|
694
|
-
|
|
710
|
+
# choices must be a map with type names as keys
|
|
711
|
+
choices_map: Dict[str, Any] = {}
|
|
712
|
+
for it in item_types:
|
|
713
|
+
if isinstance(it, str):
|
|
714
|
+
choices_map[it] = {"type": it}
|
|
715
|
+
elif isinstance(it, dict):
|
|
716
|
+
# For object types, use name if available
|
|
717
|
+
name = it.get("name", f"type{len(choices_map)}")
|
|
718
|
+
choices_map[name] = it
|
|
719
|
+
items = {"type": "choice", "choices": choices_map}
|
|
695
720
|
else:
|
|
696
721
|
items = {"type": "string"}
|
|
697
722
|
|
|
@@ -808,7 +833,15 @@ class JsonStructureSchemaInferrer(SchemaInferrer):
|
|
|
808
833
|
if len(item_types) == 1:
|
|
809
834
|
list_types.append({"type": "array", "items": item_types[0]})
|
|
810
835
|
else:
|
|
811
|
-
|
|
836
|
+
# Build choices map from item types
|
|
837
|
+
choices_map: Dict[str, Any] = {}
|
|
838
|
+
for it in item_types:
|
|
839
|
+
if isinstance(it, str):
|
|
840
|
+
choices_map[it] = {"type": it}
|
|
841
|
+
elif isinstance(it, dict):
|
|
842
|
+
name = it.get("name", f"type{len(choices_map)}")
|
|
843
|
+
choices_map[name] = it
|
|
844
|
+
list_types.append({"type": "array", "items": {"type": "choice", "choices": choices_map}})
|
|
812
845
|
|
|
813
846
|
value_types: List[Any] = []
|
|
814
847
|
for item3 in map_types:
|
|
@@ -944,9 +977,14 @@ class JsonStructureSchemaInferrer(SchemaInferrer):
|
|
|
944
977
|
|
|
945
978
|
if field_name == parent_field:
|
|
946
979
|
if len(variant_types) > 1:
|
|
980
|
+
# Build choices as a map (object) per JSON Structure spec
|
|
981
|
+
# Each value is a schema directly (the object type definition)
|
|
982
|
+
choices_map: Dict[str, Any] = {}
|
|
983
|
+
for vt in variant_types:
|
|
984
|
+
choices_map[vt["name"]] = vt
|
|
947
985
|
envelope_properties[safe_name] = {
|
|
948
986
|
"type": "choice",
|
|
949
|
-
"choices":
|
|
987
|
+
"choices": choices_map
|
|
950
988
|
}
|
|
951
989
|
else:
|
|
952
990
|
envelope_properties[safe_name] = variant_types[0] if variant_types else {"type": "object"}
|
|
@@ -975,9 +1013,13 @@ class JsonStructureSchemaInferrer(SchemaInferrer):
|
|
|
975
1013
|
|
|
976
1014
|
return envelope_record
|
|
977
1015
|
|
|
978
|
-
# Handle top-level discriminated union
|
|
1016
|
+
# Handle top-level discriminated union as inline union
|
|
1017
|
+
# Inline unions match the actual instance format where the discriminator
|
|
1018
|
+
# is a property value, not a key wrapper (tagged union)
|
|
979
1019
|
if result.discriminator_field:
|
|
980
|
-
|
|
1020
|
+
# Collect all fields from all variants to find common vs variant-specific
|
|
1021
|
+
all_variant_fields: Dict[str, Set[str]] = {} # variant_value -> field names
|
|
1022
|
+
variant_docs: Dict[str, Dict[str, Any]] = {} # variant_value -> sample doc
|
|
981
1023
|
|
|
982
1024
|
for value in sorted(result.discriminator_values):
|
|
983
1025
|
cluster_docs = [c for c in result.clusters
|
|
@@ -986,27 +1028,98 @@ class JsonStructureSchemaInferrer(SchemaInferrer):
|
|
|
986
1028
|
if not cluster_docs:
|
|
987
1029
|
continue
|
|
988
1030
|
cluster = cluster_docs[0]
|
|
1031
|
+
all_variant_fields[value] = set(cluster.merged_signature)
|
|
1032
|
+
variant_docs[value] = cluster.documents[0].data if cluster.documents else {}
|
|
1033
|
+
|
|
1034
|
+
if not all_variant_fields:
|
|
1035
|
+
return None
|
|
1036
|
+
|
|
1037
|
+
# Find common fields (present in ALL variants)
|
|
1038
|
+
common_fields = set.intersection(*all_variant_fields.values()) if all_variant_fields else set()
|
|
1039
|
+
|
|
1040
|
+
# Build abstract base type with common fields
|
|
1041
|
+
base_name = avro_name(type_name) + "Base"
|
|
1042
|
+
base_properties: Dict[str, Any] = {}
|
|
1043
|
+
base_required: List[str] = []
|
|
1044
|
+
|
|
1045
|
+
# Use first variant's doc for type inference of common fields
|
|
1046
|
+
first_value = sorted(result.discriminator_values)[0]
|
|
1047
|
+
rep_doc = variant_docs.get(first_value, {})
|
|
1048
|
+
|
|
1049
|
+
for field_name in sorted(common_fields):
|
|
1050
|
+
safe_name = avro_name(field_name)
|
|
1051
|
+
field_value = rep_doc.get(field_name)
|
|
1052
|
+
field_type = self.python_type_to_jstruct_type(f"{type_name}.{safe_name}", field_value)
|
|
1053
|
+
|
|
1054
|
+
if isinstance(field_type, str):
|
|
1055
|
+
base_properties[safe_name] = {"type": field_type}
|
|
1056
|
+
else:
|
|
1057
|
+
base_properties[safe_name] = field_type
|
|
1058
|
+
|
|
1059
|
+
if field_name != safe_name:
|
|
1060
|
+
base_properties[safe_name]["altnames"] = {self.altnames_key: field_name}
|
|
1061
|
+
|
|
1062
|
+
# Check if required in all clusters
|
|
1063
|
+
# Note: discriminator field is NOT required as it's handled by selector
|
|
1064
|
+
all_required = all(
|
|
1065
|
+
field_name in c.required_fields
|
|
1066
|
+
for c in result.clusters if c.merged_signature
|
|
1067
|
+
)
|
|
1068
|
+
if all_required and field_name != result.discriminator_field:
|
|
1069
|
+
base_required.append(safe_name)
|
|
1070
|
+
|
|
1071
|
+
base_type: Dict[str, Any] = {
|
|
1072
|
+
"abstract": True,
|
|
1073
|
+
"type": "object",
|
|
1074
|
+
"name": base_name,
|
|
1075
|
+
"properties": base_properties
|
|
1076
|
+
}
|
|
1077
|
+
if base_required:
|
|
1078
|
+
base_type["required"] = base_required
|
|
1079
|
+
|
|
1080
|
+
# Build variant types that extend the base
|
|
1081
|
+
definitions: Dict[str, Any] = {base_name: base_type}
|
|
1082
|
+
choices_map: Dict[str, Any] = {}
|
|
1083
|
+
|
|
1084
|
+
for value in sorted(result.discriminator_values):
|
|
1085
|
+
if value not in all_variant_fields:
|
|
1086
|
+
continue
|
|
989
1087
|
|
|
1088
|
+
# Type name is PascalCase for definitions
|
|
990
1089
|
variant_name = avro_name(''.join(word.capitalize() for word in value.replace('_', ' ').split()))
|
|
991
|
-
|
|
1090
|
+
# Choice key must match actual selector value in instances
|
|
1091
|
+
choice_key = value
|
|
1092
|
+
|
|
1093
|
+
variant_doc = variant_docs.get(value, {})
|
|
1094
|
+
variant_specific = all_variant_fields[value] - common_fields
|
|
1095
|
+
|
|
1096
|
+
# Get cluster with all documents for this variant
|
|
1097
|
+
cluster_for_variant = next(
|
|
1098
|
+
(c for c in result.clusters
|
|
1099
|
+
if any(d.field_values.get(result.discriminator_field) == value
|
|
1100
|
+
for d in c.documents)),
|
|
1101
|
+
None
|
|
1102
|
+
)
|
|
992
1103
|
|
|
993
1104
|
properties: Dict[str, Any] = {}
|
|
994
1105
|
required: List[str] = []
|
|
995
1106
|
|
|
996
|
-
# Add
|
|
997
|
-
|
|
998
|
-
properties[disc_safe] = {
|
|
999
|
-
"type": "string",
|
|
1000
|
-
"default": value
|
|
1001
|
-
}
|
|
1002
|
-
required.append(disc_safe)
|
|
1003
|
-
|
|
1004
|
-
# Add other fields
|
|
1005
|
-
for field_name in sorted(cluster.merged_signature):
|
|
1006
|
-
if field_name == result.discriminator_field:
|
|
1007
|
-
continue
|
|
1107
|
+
# Add variant-specific fields only (common fields inherited from base)
|
|
1108
|
+
for field_name in sorted(variant_specific):
|
|
1008
1109
|
safe_name = avro_name(field_name)
|
|
1009
|
-
|
|
1110
|
+
|
|
1111
|
+
# Find the first non-null value across all documents in this variant
|
|
1112
|
+
# to properly infer the type
|
|
1113
|
+
field_value = None
|
|
1114
|
+
if cluster_for_variant:
|
|
1115
|
+
for doc in cluster_for_variant.documents:
|
|
1116
|
+
val = doc.data.get(field_name)
|
|
1117
|
+
if val is not None:
|
|
1118
|
+
field_value = val
|
|
1119
|
+
break
|
|
1120
|
+
if field_value is None:
|
|
1121
|
+
field_value = variant_doc.get(field_name)
|
|
1122
|
+
|
|
1010
1123
|
field_type = self.python_type_to_jstruct_type(f"{type_name}.{safe_name}", field_value)
|
|
1011
1124
|
|
|
1012
1125
|
if isinstance(field_type, str):
|
|
@@ -1017,25 +1130,36 @@ class JsonStructureSchemaInferrer(SchemaInferrer):
|
|
|
1017
1130
|
if field_name != safe_name:
|
|
1018
1131
|
properties[safe_name]["altnames"] = {self.altnames_key: field_name}
|
|
1019
1132
|
|
|
1020
|
-
if
|
|
1133
|
+
# Field is required only if present in all documents of this variant
|
|
1134
|
+
if cluster_for_variant and field_name in cluster_for_variant.required_fields:
|
|
1021
1135
|
required.append(safe_name)
|
|
1022
1136
|
|
|
1023
1137
|
variant_record: Dict[str, Any] = {
|
|
1024
1138
|
"type": "object",
|
|
1025
1139
|
"name": variant_name,
|
|
1140
|
+
"$extends": f"#/definitions/{base_name}",
|
|
1026
1141
|
"properties": properties
|
|
1027
1142
|
}
|
|
1028
1143
|
if required:
|
|
1029
1144
|
variant_record["required"] = required
|
|
1030
|
-
|
|
1145
|
+
|
|
1146
|
+
definitions[variant_name] = variant_record
|
|
1147
|
+
# Use actual discriminator value as choice key (must match selector in instances)
|
|
1148
|
+
choices_map[choice_key] = {"type": {"$ref": f"#/definitions/{variant_name}"}}
|
|
1031
1149
|
|
|
1032
|
-
if len(
|
|
1033
|
-
return
|
|
1150
|
+
if len(choices_map) == 1:
|
|
1151
|
+
# Single variant - just return it directly
|
|
1152
|
+
return list(definitions.values())[1] # Skip base, return the variant
|
|
1034
1153
|
|
|
1154
|
+
# Build inline union choice type
|
|
1155
|
+
disc_safe = avro_name(result.discriminator_field)
|
|
1035
1156
|
return {
|
|
1036
1157
|
"type": "choice",
|
|
1037
1158
|
"name": avro_name(type_name),
|
|
1038
|
-
"
|
|
1159
|
+
"$extends": f"#/definitions/{base_name}",
|
|
1160
|
+
"selector": disc_safe,
|
|
1161
|
+
"choices": choices_map,
|
|
1162
|
+
"definitions": definitions
|
|
1039
1163
|
}
|
|
1040
1164
|
|
|
1041
1165
|
# Undiscriminated union - fall back to standard inference
|
|
@@ -47,6 +47,7 @@ class StructureToCSharp:
|
|
|
47
47
|
self.schema_registry: Dict[str, Dict] = {} # Maps $id URIs to schemas
|
|
48
48
|
self.offers: Dict[str, Any] = {} # Maps add-in names to property definitions from $offers
|
|
49
49
|
self.needs_json_structure_converters = False # Track if any types need JSON Structure converters
|
|
50
|
+
self.discriminator_properties: Dict[str, str] = {} # Maps type ref -> discriminator property name (for inline unions)
|
|
50
51
|
|
|
51
52
|
def get_qualified_name(self, namespace: str, name: str) -> str:
|
|
52
53
|
""" Concatenates namespace and name with a dot separator """
|
|
@@ -390,9 +391,13 @@ class StructureToCSharp:
|
|
|
390
391
|
# Check additionalProperties setting (Section 3.7.8)
|
|
391
392
|
additional_props = structure_schema.get('additionalProperties', True if is_abstract else None)
|
|
392
393
|
|
|
394
|
+
# Check if any property in this class is a discriminator for an inline union
|
|
395
|
+
discriminator_prop = self.discriminator_properties.get(ref, None)
|
|
396
|
+
|
|
393
397
|
fields_str = []
|
|
394
398
|
for prop_name, prop_schema in properties.items():
|
|
395
|
-
|
|
399
|
+
is_discriminator = (prop_name == discriminator_prop)
|
|
400
|
+
field_def = self.generate_property(prop_name, prop_schema, class_name, schema_namespace, required_props, is_discriminator=is_discriminator)
|
|
396
401
|
fields_str.append(field_def)
|
|
397
402
|
|
|
398
403
|
# Add dictionary for additional properties if needed
|
|
@@ -460,7 +465,7 @@ class StructureToCSharp:
|
|
|
460
465
|
self.generated_structure_types[ref] = structure_schema
|
|
461
466
|
return ref
|
|
462
467
|
|
|
463
|
-
def generate_property(self, prop_name: str, prop_schema: Dict, class_name: str, parent_namespace: str, required_props: List) -> str:
|
|
468
|
+
def generate_property(self, prop_name: str, prop_schema: Dict, class_name: str, parent_namespace: str, required_props: List, is_discriminator: bool = False) -> str:
|
|
464
469
|
""" Generates a property for a class """
|
|
465
470
|
property_definition = ''
|
|
466
471
|
|
|
@@ -521,9 +526,13 @@ class StructureToCSharp:
|
|
|
521
526
|
doc = prop_schema.get('description', prop_schema.get('doc', field_name_cs))
|
|
522
527
|
property_definition += f"{INDENT}/// <summary>\n{INDENT}/// {doc}\n{INDENT}/// </summary>\n"
|
|
523
528
|
|
|
529
|
+
# If this property is used as a discriminator in an inline union, add JsonIgnore
|
|
530
|
+
# because JsonPolymorphic handles it as metadata
|
|
531
|
+
if is_discriminator and self.system_text_json_annotation:
|
|
532
|
+
property_definition += f'{INDENT}[System.Text.Json.Serialization.JsonIgnore]\n'
|
|
524
533
|
# Add JSON property name annotation when property name differs from schema name
|
|
525
534
|
# This is needed for proper JSON serialization/deserialization, especially with pascal_properties
|
|
526
|
-
|
|
535
|
+
elif needs_json_annotation:
|
|
527
536
|
property_definition += f'{INDENT}[System.Text.Json.Serialization.JsonPropertyName("{prop_name}")]\n'
|
|
528
537
|
if self.newtonsoft_json_annotation and needs_json_annotation:
|
|
529
538
|
property_definition += f'{INDENT}[Newtonsoft.Json.JsonProperty("{prop_name}")]\n'
|
|
@@ -1027,18 +1036,30 @@ class StructureToCSharp:
|
|
|
1027
1036
|
# Fallback to tagged union if no base
|
|
1028
1037
|
return self.generate_tagged_union(structure_schema, parent_namespace, write_file, explicit_name)
|
|
1029
1038
|
|
|
1030
|
-
|
|
1039
|
+
choices = structure_schema.get('choices', {})
|
|
1040
|
+
selector = structure_schema.get('selector', 'type')
|
|
1041
|
+
|
|
1042
|
+
# Mark the selector property as a discriminator BEFORE generating the base class
|
|
1043
|
+
# This allows generate_class to add [JsonIgnore] to the property
|
|
1031
1044
|
base_schema_copy = base_schema.copy()
|
|
1032
1045
|
if 'name' not in base_schema_copy:
|
|
1033
1046
|
# Extract name from $extends ref
|
|
1034
1047
|
base_name = extends_ref.split('/')[-1]
|
|
1035
1048
|
base_schema_copy['name'] = base_name
|
|
1049
|
+
|
|
1050
|
+
# Calculate what the base class ref will be
|
|
1051
|
+
base_namespace_for_ref = pascal(self.concat_namespace(self.base_namespace, base_schema_copy.get('namespace', schema_namespace)))
|
|
1052
|
+
base_class_name_for_ref = pascal(base_schema_copy['name'])
|
|
1053
|
+
pending_base_ref = 'global::'+self.get_qualified_name(base_namespace_for_ref, base_class_name_for_ref)
|
|
1054
|
+
|
|
1055
|
+
# Record that this base type's selector property is a discriminator
|
|
1056
|
+
if self.system_text_json_annotation and selector in base_schema.get('properties', {}):
|
|
1057
|
+
self.discriminator_properties[pending_base_ref] = selector
|
|
1058
|
+
|
|
1059
|
+
# Now generate the base class (it will check discriminator_properties)
|
|
1036
1060
|
base_class_ref = self.generate_class(base_schema_copy, schema_namespace, write_file)
|
|
1037
1061
|
base_class_name = base_class_ref.split('::')[-1].split('.')[-1]
|
|
1038
1062
|
|
|
1039
|
-
choices = structure_schema.get('choices', {})
|
|
1040
|
-
selector = structure_schema.get('selector', 'type')
|
|
1041
|
-
|
|
1042
1063
|
# Generate abstract base class with selector property
|
|
1043
1064
|
class_definition = f"/// <summary>\n/// {structure_schema.get('description', class_name + ' (inline union base)')}\n/// </summary>\n"
|
|
1044
1065
|
|
|
@@ -1056,16 +1077,15 @@ class StructureToCSharp:
|
|
|
1056
1077
|
|
|
1057
1078
|
class_definition += "\n{\n"
|
|
1058
1079
|
|
|
1059
|
-
# Add selector property (not required since derived classes set it in constructor)
|
|
1060
|
-
class_definition += f"{INDENT}/// <summary>\n{INDENT}/// Type discriminator\n{INDENT}/// </summary>\n"
|
|
1061
|
-
if self.system_text_json_annotation:
|
|
1062
|
-
class_definition += f'{INDENT}[System.Text.Json.Serialization.JsonPropertyName("{selector}")]\n'
|
|
1063
|
-
|
|
1064
1080
|
# Check if selector is already in base properties
|
|
1065
1081
|
base_has_selector = selector in base_schema.get('properties', {})
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1082
|
+
|
|
1083
|
+
# Only add selector property if base class doesn't already have it
|
|
1084
|
+
# If base has the selector, JsonPolymorphic will use it directly
|
|
1085
|
+
if not base_has_selector:
|
|
1086
|
+
class_definition += f"{INDENT}/// <summary>\n{INDENT}/// Type discriminator\n{INDENT}/// </summary>\n"
|
|
1087
|
+
if self.system_text_json_annotation:
|
|
1088
|
+
class_definition += f'{INDENT}[System.Text.Json.Serialization.JsonPropertyName("{selector}")]\n'
|
|
1069
1089
|
class_definition += f"{INDENT}public string {pascal(selector)} {{ get; set; }} = \"\";\n"
|
|
1070
1090
|
|
|
1071
1091
|
class_definition += "}"
|
|
@@ -1076,11 +1096,21 @@ class StructureToCSharp:
|
|
|
1076
1096
|
# Generate derived classes for each choice with property merging
|
|
1077
1097
|
for choice_name, choice_schema_ref in choices.items():
|
|
1078
1098
|
# Resolve the choice schema
|
|
1079
|
-
|
|
1080
|
-
|
|
1099
|
+
# Handle both formats:
|
|
1100
|
+
# 1. Direct $ref: {"$ref": "#/definitions/Type"}
|
|
1101
|
+
# 2. Nested in type: {"type": {"$ref": "#/definitions/Type"}}
|
|
1102
|
+
ref_to_resolve = None
|
|
1103
|
+
if isinstance(choice_schema_ref, dict):
|
|
1104
|
+
if '$ref' in choice_schema_ref:
|
|
1105
|
+
ref_to_resolve = choice_schema_ref['$ref']
|
|
1106
|
+
elif 'type' in choice_schema_ref and isinstance(choice_schema_ref['type'], dict) and '$ref' in choice_schema_ref['type']:
|
|
1107
|
+
ref_to_resolve = choice_schema_ref['type']['$ref']
|
|
1108
|
+
|
|
1109
|
+
if ref_to_resolve:
|
|
1110
|
+
choice_schema = self.resolve_ref(ref_to_resolve, self.schema_doc)
|
|
1081
1111
|
if not choice_schema:
|
|
1082
1112
|
# Try resolving relative to the structure_schema itself
|
|
1083
|
-
choice_schema = self.resolve_ref(
|
|
1113
|
+
choice_schema = self.resolve_ref(ref_to_resolve, structure_schema)
|
|
1084
1114
|
else:
|
|
1085
1115
|
choice_schema = choice_schema_ref
|
|
1086
1116
|
|
|
@@ -1219,9 +1249,19 @@ class StructureToCSharp:
|
|
|
1219
1249
|
class_definition += field_def
|
|
1220
1250
|
|
|
1221
1251
|
# Add constructor that sets the discriminator
|
|
1252
|
+
# If the selector exists in the base schema, use the original property name (snake_case)
|
|
1253
|
+
# Otherwise use the PascalCase version we defined in the union class
|
|
1254
|
+
base_properties = schema.get('$base_properties', [])
|
|
1255
|
+
if selector in base_properties:
|
|
1256
|
+
# Use the snake_case name from the base class
|
|
1257
|
+
selector_prop_name = selector
|
|
1258
|
+
else:
|
|
1259
|
+
# Use PascalCase name we defined in the union class
|
|
1260
|
+
selector_prop_name = pascal(selector)
|
|
1261
|
+
|
|
1222
1262
|
class_definition += f"\n{INDENT}/// <summary>\n{INDENT}/// Constructor that sets the discriminator value\n{INDENT}/// </summary>\n"
|
|
1223
1263
|
class_definition += f"{INDENT}public {class_name}()\n{INDENT}{{\n"
|
|
1224
|
-
class_definition += f"{INDENT*2}this.{
|
|
1264
|
+
class_definition += f"{INDENT*2}this.{selector_prop_name} = \"{choice_name}\";\n"
|
|
1225
1265
|
class_definition += f"{INDENT}}}\n"
|
|
1226
1266
|
|
|
1227
1267
|
# Generate Equals and GetHashCode
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: structurize
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: Tools to convert from and to JSON Structure from various other schema languages.
|
|
5
5
|
Author-email: Clemens Vasters <clemensv@microsoft.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{structurize-3.2.0 → structurize-3.2.2}/avrotize/dependencies/typescript/node22/package.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|