diff --git a/.editorconfig b/.editorconfig index 6199b4c..b14bca5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,3 +1,5 @@ +root = true + [*] charset = utf-8 end_of_line = lf @@ -7,24 +9,16 @@ indent_style = space indent_size = 4 # A property with the same name was updated with a value 2 in a section [{*.yaml,*.yml}] [{*.yaml,*.yml}] -indent_style = space indent_size = 2 # A property with the same name was updated with a value 4 in a section [*]; with a value 4 in a section [*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,hlsl,hlsli,hlslinc,master,nuspec,paml,razor,resw,resx,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}] -dotnet_diagnostic.CA1047.severity = error [*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,hlsl,hlsli,hlslinc,master,nuspec,paml,razor,resw,resx,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}] -indent_style = space -indent_size = 4 # A property with the same name was updated with a value 2 in a section [{*.yaml,*.yml}] tab_width = 4 -dotnet_style_operator_placement_when_wrapping = beginning_of_line + dotnet_style_coalesce_expression = true:suggestion dotnet_style_null_propagation = true:suggestion -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error -dotnet_style_prefer_auto_properties = true:warning dotnet_style_object_initializer = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_prefer_simplified_boolean_expressions = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion -dotnet_style_prefer_conditional_expression_over_return = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion @@ -35,13 +29,8 @@ dotnet_style_namespace_match_folder = true:suggestion dotnet_style_readonly_field = true:warning dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning -dotnet_style_require_accessibility_modifiers = always:error dotnet_style_allow_multiple_blank_lines_experimental = false:silent dotnet_style_allow_statement_immediately_after_block_experimental = false:silent -dotnet_code_quality_unused_parameters = all:warning -dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning -dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent dotnet_style_qualification_for_field = false:silent dotnet_style_qualification_for_property = false:silent @@ -77,6 +66,8 @@ dotnet_diagnostic.CA1852.severity = suggestion dotnet_diagnostic.CA2012.severity = warning dotnet_diagnostic.CA2019.severity = warning dotnet_diagnostic.CA2211.severity = warning +dotnet_diagnostic.CA1822.severity = suggestion +dotnet_diagnostic.CA1725.severity = suggestion [*.cs] @@ -104,7 +95,8 @@ dotnet_style_parentheses_in_other_operators = never_if_unnecessary dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning # Modifier preferences -dotnet_style_require_accessibility_modifiers = always:error +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning +dotnet_diagnostics.IDE0040.severity = warning # Expression-level preferences dotnet_style_coalesce_expression = true @@ -712,14 +704,16 @@ resharper_blank_lines_inside_region = 1 resharper_blank_lines_inside_type = 0 resharper_blank_line_after_pi = true resharper_braces_for_dowhile = required -resharper_braces_for_fixed = required +resharper_braces_for_fixed = required_for_multiline resharper_braces_for_for = required_for_multiline resharper_braces_for_foreach = required_for_multiline resharper_braces_for_ifelse = required_for_multiline -resharper_braces_for_lock = required -resharper_braces_for_using = required +resharper_braces_for_lock = required_for_multiline +resharper_braces_for_using = required_for_multiline resharper_braces_for_while = required_for_multiline resharper_braces_redundant = true +resharper_builtin_type_apply_to_native_integer = true +resharper_csharp_empty_block_style = together_same_line resharper_break_template_declaration = line_break resharper_can_use_global_alias = false resharper_configure_await_analysis_mode = disabled @@ -817,7 +811,7 @@ resharper_indent_typearg_angles = inside resharper_indent_typeparam_angles = inside resharper_indent_type_constraints = true resharper_indent_wrapped_function_names = false -resharper_instance_members_qualify_declared_in = base_class +resharper_instance_members_qualify_declared_in = resharper_int_align_assignments = false resharper_int_align_binary_expressions = false resharper_int_align_declaration_names = false @@ -918,7 +912,7 @@ resharper_place_simple_switch_expression_on_single_line = false resharper_place_type_attribute_on_same_line = false resharper_place_type_constraints_on_same_line = true resharper_prefer_explicit_discard_declaration = false -resharper_prefer_separate_deconstructed_variables_declaration = true +resharper_prefer_separate_deconstructed_variables_declaration = false resharper_preserve_spaces_inside_tags = pre,textarea resharper_qualified_using_at_nested_scope = false resharper_quote_style = doublequoted @@ -1069,6 +1063,7 @@ resharper_space_within_typeof_parentheses = false resharper_space_within_type_argument_angles = false resharper_space_within_type_parameter_angles = false resharper_space_within_type_parameter_parentheses = false +resharper_csharp_allow_alias = false resharper_special_else_if_treatment = true resharper_static_members_qualify_members = none resharper_static_members_qualify_with = declared_type @@ -1174,7 +1169,7 @@ resharper_arrange_default_value_when_type_evident_highlighting = suggestion resharper_arrange_default_value_when_type_not_evident_highlighting = suggestion resharper_arrange_local_function_body_highlighting = error resharper_arrange_method_or_operator_body_highlighting = error -resharper_arrange_missing_parentheses_highlighting = hint +resharper_arrange_missing_parentheses_highlighting = none resharper_arrange_namespace_body_highlighting = error resharper_arrange_object_creation_when_type_evident_highlighting = suggestion resharper_arrange_object_creation_when_type_not_evident_highlighting = suggestion @@ -1289,7 +1284,7 @@ resharper_convert_to_using_declaration_highlighting = suggestion resharper_convert_to_vb_auto_property_highlighting = suggestion resharper_convert_to_vb_auto_property_when_possible_highlighting = hint resharper_convert_to_vb_auto_property_with_private_setter_highlighting = hint -resharper_convert_type_check_pattern_to_null_check_highlighting = warning +resharper_convert_type_check_pattern_to_null_check_highlighting = none resharper_convert_type_check_to_null_check_highlighting = warning resharper_co_variant_array_conversion_highlighting = warning resharper_c_declaration_with_implicit_int_type_highlighting = warning @@ -1408,8 +1403,8 @@ resharper_meaningless_default_parameter_value_highlighting = warning resharper_member_can_be_internal_highlighting = none resharper_member_can_be_made_static_global_highlighting = hint resharper_member_can_be_made_static_local_highlighting = hint -resharper_member_can_be_private_global_highlighting = suggestion -resharper_member_can_be_private_local_highlighting = suggestion +resharper_member_can_be_private_global_highlighting = hint +resharper_member_can_be_private_local_highlighting = hint resharper_member_can_be_protected_global_highlighting = suggestion resharper_member_can_be_protected_local_highlighting = suggestion resharper_member_hides_interface_member_with_default_implementation_highlighting = warning @@ -1537,7 +1532,7 @@ resharper_redundant_assignment_highlighting = warning resharper_redundant_attribute_parentheses_highlighting = hint resharper_redundant_attribute_usage_property_highlighting = suggestion resharper_redundant_base_constructor_call_highlighting = warning -resharper_redundant_base_qualifier_highlighting = warning +resharper_redundant_base_qualifier_highlighting = none resharper_redundant_blank_lines_highlighting = none resharper_redundant_bool_compare_highlighting = warning resharper_redundant_case_label_highlighting = warning @@ -1547,7 +1542,7 @@ resharper_redundant_check_before_assignment_highlighting = warning resharper_redundant_collection_initializer_element_braces_highlighting = hint resharper_redundant_configure_await_highlighting = suggestion resharper_redundant_declaration_semicolon_highlighting = hint -resharper_redundant_default_member_initializer_highlighting = warning +resharper_redundant_default_member_initializer_highlighting = hint resharper_redundant_delegate_creation_highlighting = warning resharper_redundant_disable_warning_comment_highlighting = warning resharper_redundant_discard_designation_highlighting = suggestion @@ -1872,4 +1867,18 @@ resharper_virtual_member_never_overridden_local_highlighting = suggestion resharper_void_method_with_must_use_return_value_attribute_highlighting = warning resharper_with_expression_instead_of_initializer_highlighting = suggestion resharper_wrong_indent_size_highlighting = none -resharper_xunit_xunit_test_with_console_output_highlighting = warning \ No newline at end of file +resharper_xunit_xunit_test_with_console_output_highlighting = warning +resharper_arrange_constructor_or_destructor_body_highlighting = hint +resharper_arrange_local_function_body_highlighting = hint +resharper_arrange_method_or_operator_body_highlighting = hint +resharper_arrange_null_checking_pattern_highlighting = suggestion +resharper_enforce_do_while_statement_braces_highlighting = warning +resharper_enforce_fixed_statement_braces_highlighting = warning +resharper_enforce_foreach_statement_braces_highlighting = none +resharper_enforce_for_statement_braces_highlighting = none +resharper_enforce_if_statement_braces_highlighting = none +resharper_enforce_lock_statement_braces_highlighting = warning +resharper_enforce_while_statement_braces_highlighting = none +resharper_remove_redundant_braces_highlighting = hint +resharper_suggest_discard_declaration_var_style_highlighting = warning +resharper_prefer_concrete_value_over_default_highlighting = hint \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8cd28f9..0829392 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -22,7 +22,7 @@ jobs: analyze: name: Analyze runs-on: ubuntu-latest - if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')" + if: "github.event.pull_request.draft == false || github.event_name == 'push'" permissions: actions: read contents: read @@ -42,7 +42,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -53,12 +53,12 @@ jobs: - name: Set up .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.x' + dotnet-version: '9.x' # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index aecaa16..3aee085 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -10,7 +10,7 @@ jobs: windows_build: runs-on: windows-latest - if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')" + if: "github.event.pull_request.draft == false || github.event_name == 'push'" env: DOTNET_NOLOGO: true @@ -21,7 +21,9 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.x + dotnet-version: | + 8.x + 9.x - name: Restore dependencies run: dotnet restore - name: Build @@ -32,7 +34,7 @@ jobs: linux_build: runs-on: ubuntu-latest - if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')" + if: "github.event.pull_request.draft == false || github.event_name == 'push'" env: DOTNET_NOLOGO: true @@ -43,7 +45,9 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.x + dotnet-version: | + 8.x + 9.x - name: Restore dependencies run: dotnet restore - name: Build @@ -54,7 +58,7 @@ jobs: osx_build: runs-on: macos-latest - if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')" + if: "github.event.pull_request.draft == false || github.event_name == 'push'" env: DOTNET_NOLOGO: true @@ -65,7 +69,9 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.x + dotnet-version: | + 8.x + 9.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/.github/workflows/publish_nightly.yml b/.github/workflows/publish_nightly.yml index 7a30c16..63273e0 100644 --- a/.github/workflows/publish_nightly.yml +++ b/.github/workflows/publish_nightly.yml @@ -15,7 +15,9 @@ jobs: - name: "Setup .NET" uses: "actions/setup-dotnet@v4" with: - dotnet-version: 8.x + dotnet-version: | + 8.x + 9.x - name: Build run: dotnet build -c Release diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index a9f0855..77c0f18 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -21,7 +21,9 @@ jobs: - name: "Setup .NET" uses: "actions/setup-dotnet@v4" with: - dotnet-version: 8.x + dotnet-version: | + 8.x + 9.x - name: Build run: dotnet build -c Release @@ -68,7 +70,9 @@ jobs: - name: "Setup .NET" uses: "actions/setup-dotnet@v4" with: - dotnet-version: 8.x + dotnet-version: | + 8.x + 9.x - name: Build run: dotnet build -c Release @@ -116,7 +120,9 @@ jobs: - name: "Setup .NET" uses: "actions/setup-dotnet@v4" with: - dotnet-version: 8.x + dotnet-version: | + 8.x + 9.x - name: Build run: dotnet build -c Release @@ -158,7 +164,9 @@ jobs: - name: "Setup .NET" uses: "actions/setup-dotnet@v2" with: - dotnet-version: 8.x + dotnet-version: | + 8.x + 9.x - name: Build run: dotnet build -c Release diff --git a/Directory.Build.props b/Directory.Build.props index 4a873dc..9f6a00b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ - net8.0 + net8.0;net9.0 latest latest enable @@ -10,8 +10,8 @@ Nullable True True - 1.1.12.0 - 1.1.12.0 + 1.2.0.0 + 1.2.0.0 Kotz Copyright © Kotz 2023 https://github.com/Kaoticz/Json2Sharp @@ -22,11 +22,11 @@ Apache-2.0 - + False - + True diff --git a/Json2SharpApp/Program.cs b/Json2SharpApp/Program.cs index 41f8297..652deeb 100644 --- a/Json2SharpApp/Program.cs +++ b/Json2SharpApp/Program.cs @@ -14,7 +14,7 @@ internal sealed class Program /// /// CLI arguments. /// Exit code. - private async static Task Main(string[] args) + private static async Task Main(string[] args) { var inputOption = new Option(["--input", "-i"], "The relative path to the JSON file in the file system."); var outputOption = new Option(["--output", "-o"], "The relative path to the resulting file in the file system."); diff --git a/Json2SharpLib/Common/TypeAliases.cs b/Json2SharpLib/Common/TypeAliases.cs index 42ac895..1a1d104 100644 --- a/Json2SharpLib/Common/TypeAliases.cs +++ b/Json2SharpLib/Common/TypeAliases.cs @@ -27,6 +27,9 @@ internal static class TypeAliases [typeof(object)] = "object", [typeof(bool)] = "bool", [typeof(char)] = "char", + [typeof(DateTime)] = "DateTime", + [typeof(DateTimeOffset)] = "DateTimeOffset", + [typeof(Guid)] = "Guid", [typeof(string[])] = "string[]", [typeof(int[])] = "int[]", @@ -43,6 +46,9 @@ internal static class TypeAliases [typeof(object[])] = "object[]", [typeof(bool[])] = "bool[]", [typeof(char[])] = "char[]", + [typeof(DateTime[])] = "DateTime[]", + [typeof(DateTimeOffset[])] = "DateTimeOffset[]", + [typeof(Guid[])] = "Guid[]", [typeof(int?[])] = "int?[]", [typeof(byte?[])] = "byte?[]", @@ -57,6 +63,9 @@ internal static class TypeAliases [typeof(decimal?[])] = "decimal?[]", [typeof(bool?[])] = "bool?[]", [typeof(char?[])] = "char?[]", + [typeof(DateTime?[])] = "DateTime?[]", + [typeof(DateTimeOffset?[])] = "DateTimeOffset?[]", + [typeof(Guid?[])] = "Guid?[]", }.ToFrozenDictionary(); /// @@ -81,7 +90,7 @@ internal static class TypeAliases [typeof(char)] = "str", [typeof(DateTime)] = "datetime", [typeof(DateTimeOffset)] = "datetime", - [typeof(Guid)] = "uuid", + [typeof(Guid)] = "UUID", [typeof(string[])] = "list[str]", [typeof(int[])] = "list[int]", @@ -100,7 +109,7 @@ internal static class TypeAliases [typeof(char[])] = "list[str]", [typeof(DateTime[])] = "list[datetime]", [typeof(DateTimeOffset[])] = "list[datetime]", - [typeof(Guid[])] = "list[uuid]", + [typeof(Guid[])] = "list[UUID]", [typeof(int?[])] = "Optional[list[int]]", [typeof(byte?[])] = "Optional[bytes]", @@ -117,6 +126,6 @@ internal static class TypeAliases [typeof(char?[])] = "Optional[list[str]]", [typeof(DateTime?[])] = "Optional[list[datetime]]", [typeof(DateTimeOffset?[])] = "Optional[list[datetime]]", - [typeof(Guid?[])] = "Optional[list[uuid]]", + [typeof(Guid?[])] = "Optional[list[UUID]]", }.ToFrozenDictionary(); } \ No newline at end of file diff --git a/Json2SharpLib/Emitters/Abstractions/CodeEmitter.cs b/Json2SharpLib/Emitters/Abstractions/CodeEmitter.cs index 442e371..3c6b5ac 100644 --- a/Json2SharpLib/Emitters/Abstractions/CodeEmitter.cs +++ b/Json2SharpLib/Emitters/Abstractions/CodeEmitter.cs @@ -11,6 +11,17 @@ namespace Json2SharpLib.Emitters.Abstractions; /// internal abstract class CodeEmitter : ICodeEmitter { + private readonly Func _typeNameHandler; + + /// + /// Initializes a language code emitter. + /// + /// + /// The handler that transforms the type name of each parsed property before it is used in generated class members. + /// + internal CodeEmitter(Func typeNameHandler) + => _typeNameHandler = typeNameHandler; + /// public abstract string Parse(string objectName, JsonElement jsonElement); @@ -39,11 +50,11 @@ internal abstract class CodeEmitter : ICodeEmitter /// The target language for the type name. /// The type name. /// Occurs when is not of type . - protected static string GetObjectTypeName(ParsedJsonProperty property, Language language) + protected string GetObjectTypeName(ParsedJsonProperty property, Language language) { using var jsonEnumerator = property.JsonElement.EnumerateObject(); return (jsonEnumerator.Any()) - ? property.JsonName!.ToPascalCase() + ? _typeNameHandler(property.JsonName!) : J2SUtils.GetAliasName(typeof(object), language); } @@ -57,7 +68,7 @@ protected static string GetObjectTypeName(ParsedJsonProperty property, Language /// if the array contains elements, otherwise. /// Occurs when is empty. /// Occurs when is not of type . - protected static bool IsArrayOfNullableType(ParsedJsonProperty property, Language language, IReadOnlyList childrenTypes, out string typeName) + protected bool IsArrayOfNullableType(ParsedJsonProperty property, Language language, IReadOnlyList childrenTypes, out string typeName) { if (childrenTypes.Count is 0) throw new ArgumentException("Collection cannot be empty.", nameof(childrenTypes)); @@ -78,9 +89,9 @@ out var aliasName IsCustomType: aliasName?.Equals(J2SUtils.GetAliasName(typeof(object), language), StringComparison.Ordinal) ) switch // Else { - (1, true, true) => className, // Array type is custom type - (1, true, false) => aliasName, // Array type is non-object language type - (1, false, _) => className, // Array type is custom type + (1, true, true) => _typeNameHandler(className), // Array type is custom type + (1, true, false) => aliasName, // Array type is non-object language type + (1, false, _) => _typeNameHandler(className), // Array type is custom type _ => J2SUtils.GetAliasName(typeof(object), language), // Array type is object language type }; diff --git a/Json2SharpLib/Emitters/CSharp/CSharpClassEmitter.cs b/Json2SharpLib/Emitters/CSharp/CSharpClassEmitter.cs index 2359861..3ebfb84 100644 --- a/Json2SharpLib/Emitters/CSharp/CSharpClassEmitter.cs +++ b/Json2SharpLib/Emitters/CSharp/CSharpClassEmitter.cs @@ -27,7 +27,7 @@ internal sealed class CSharpClassEmitter : CodeEmitter /// Parses JSON data into a C# struct, class, or record with the base body of a class. /// /// The parsing options. - internal CSharpClassEmitter(Json2SharpCSharpOptions options) + internal CSharpClassEmitter(Json2SharpCSharpOptions options) : base(options.TypeNameHandler) { _accessibility = options.AccessibilityLevel.ToCode() + options.TargetType switch { @@ -63,7 +63,6 @@ public override string Parse(string objectName, JsonElement jsonElement) /// The C# struct, class, or record. private string InternalParse(string objectName, JsonElement jsonElement, bool emitHeaders) { - objectName = objectName.ToPascalCase(); var properties = Json2Sharp.ParseProperties(jsonElement); if (properties.Count is 0) @@ -101,9 +100,11 @@ protected override string ParseCustomType(ParsedJsonProperty property) if (!string.IsNullOrWhiteSpace(_serializationAttribute)) result += CreateMemberAttribute(_indentationPadding, _serializationAttribute, property.JsonName!) + Environment.NewLine; + var customType = GetObjectTypeName(property, Language.CSharp); + result += CreateMemberDeclaration( _indentationPadding, - GetObjectTypeName(property, Language.CSharp), + customType, propertyName, _setterType ) + Environment.NewLine; @@ -116,14 +117,14 @@ protected override string ParseArrayType(ParsedJsonProperty property, IReadOnlyL { var propertyName = property.JsonName.ToPascalCase() ?? property.BclType.Name; var result = string.Empty; - var propertyType = (IsArrayOfNullableType(property, Language.CSharp, childrenTypes, out typeName)) - ? typeName + "?[]" - : typeName + "[]"; + var arraySuffix = (IsArrayOfNullableType(property, Language.CSharp, childrenTypes, out typeName)) + ? "?[]" + : "[]"; if (!string.IsNullOrWhiteSpace(_serializationAttribute)) result += CreateMemberAttribute(_indentationPadding, _serializationAttribute, property.JsonName!) + Environment.NewLine; - result += CreateMemberDeclaration(_indentationPadding, propertyType, propertyName, _setterType) + Environment.NewLine; + result += CreateMemberDeclaration(_indentationPadding, typeName + arraySuffix, propertyName, _setterType) + Environment.NewLine; return result; } @@ -152,15 +153,19 @@ private StringBuilder BuildClassMembers(StringBuilder stringBuilder, List if custom types were added, otherwise. private static bool AddCustomTypes(StringBuilder stringBuilder, List extraTypes) { - var sanitizedExtraTypes = extraTypes.Where(x => !string.IsNullOrWhiteSpace(x)); + var sanitizedExtraTypes = extraTypes + .Where(x => !string.IsNullOrWhiteSpace(x)) + .ToArray(); - if (!sanitizedExtraTypes.Any()) + if (sanitizedExtraTypes.Length is 0) return false; stringBuilder.AppendLine(Environment.NewLine); @@ -204,12 +211,16 @@ private bool HandleCustomType(ParsedJsonProperty property, StringBuilder stringB switch (property.JsonElement.ValueKind) { case JsonValueKind.Object: - var propertyName = property.JsonName ?? property.BclType.Name; + { + var propertyName = GetObjectTypeName(property, Language.CSharp); + extraTypes.Add(InternalParse(propertyName, property.JsonElement, false)); stringBuilder.AppendLine(ParseCustomType(property)); return true; + } case JsonValueKind.Array: + { var childrenTypes = J2SUtils.GetArrayTypes(property); if (childrenTypes.Count is 0) @@ -221,6 +232,7 @@ private bool HandleCustomType(ParsedJsonProperty property, StringBuilder stringB extraTypes.Add(InternalParse(typeName, childrenTypes[0].JsonElement, false)); return true; + } default: return false; } diff --git a/Json2SharpLib/Emitters/CSharp/CSharpRecordEmitter.cs b/Json2SharpLib/Emitters/CSharp/CSharpRecordEmitter.cs index e2f8b24..715e9fb 100644 --- a/Json2SharpLib/Emitters/CSharp/CSharpRecordEmitter.cs +++ b/Json2SharpLib/Emitters/CSharp/CSharpRecordEmitter.cs @@ -24,7 +24,7 @@ internal sealed class CSharpRecordEmitter : CodeEmitter /// Creates an object that parses JSON data into a C# record using a primary constructor. /// /// The parsing options. - internal CSharpRecordEmitter(Json2SharpCSharpOptions options) + internal CSharpRecordEmitter(Json2SharpCSharpOptions options) : base(options.TypeNameHandler) { _accessibility = options.AccessibilityLevel.ToCode() + (options.IsSealed ? " sealed" : string.Empty); _serializationAttributeType = options.SerializationAttribute; @@ -50,7 +50,6 @@ public override string Parse(string objectName, JsonElement jsonElement) /// The C# record. private string InternalParse(string objectName, JsonElement jsonElement, bool emitHeaders) { - objectName = objectName.ToPascalCase(); var properties = Json2Sharp.ParseProperties(jsonElement); if (properties.Count is 0) @@ -81,12 +80,13 @@ private string InternalParse(string objectName, JsonElement jsonElement, bool em protected override string ParseCustomType(ParsedJsonProperty property) { var propertyName = property.JsonName.ToPascalCase() ?? property.BclType.Name; + var typeName = GetObjectTypeName(property, Language.CSharp); return CreateMemberDeclaration( _indentationPadding, _serializationAttribute, property.JsonName!, - GetObjectTypeName(property, Language.CSharp), + typeName, propertyName ); } @@ -95,15 +95,15 @@ protected override string ParseCustomType(ParsedJsonProperty property) protected override string ParseArrayType(ParsedJsonProperty property, IReadOnlyList childrenTypes, out string typeName) { var finalName = property.JsonName.ToPascalCase() ?? property.BclType.Name; - var propertyType = (IsArrayOfNullableType(property, Language.CSharp, childrenTypes, out typeName)) - ? typeName + "?[]" - : typeName + "[]"; + var arraySuffix = (IsArrayOfNullableType(property, Language.CSharp, childrenTypes, out typeName)) + ? "?[]" + : "[]"; return CreateMemberDeclaration( _indentationPadding, _serializationAttribute, property.JsonName!, - propertyType, + typeName + arraySuffix, finalName ); } @@ -130,14 +130,18 @@ private StringBuilder BuildPrimaryCtorArguments(StringBuilder stringBuilder, Lis ? "?" : string.Empty; + var propertyName = (property.JsonElement.ValueKind is not JsonValueKind.Array) + ? bclTypeName + nullableAnnotation + : string.IsNullOrWhiteSpace(nullableAnnotation) + ? bclTypeName + : bclTypeName.Insert(bclTypeName.Length - 2, nullableAnnotation); + stringBuilder.AppendLine( CreateMemberDeclaration( _indentationPadding, _serializationAttribute, property.JsonName!, - (property.JsonElement.ValueKind is not JsonValueKind.Array) - ? bclTypeName + nullableAnnotation - : string.IsNullOrWhiteSpace(nullableAnnotation) ? bclTypeName : bclTypeName.Insert(bclTypeName.Length - 2, nullableAnnotation), + propertyName, property.JsonName!.ToPascalCase() ) ); @@ -156,9 +160,11 @@ private StringBuilder BuildPrimaryCtorArguments(StringBuilder stringBuilder, Lis /// if custom types were added, otherwise. private static bool AddCustomTypes(StringBuilder stringBuilder, List extraTypes) { - var sanitizedExtraTypes = extraTypes.Where(x => !string.IsNullOrWhiteSpace(x)); + var sanitizedExtraTypes = extraTypes + .Where(x => !string.IsNullOrWhiteSpace(x)) + .ToArray(); - if (!sanitizedExtraTypes.Any()) + if (sanitizedExtraTypes.Length is 0) return false; stringBuilder.AppendLine(Environment.NewLine); @@ -179,12 +185,16 @@ private bool HandleCustomType(ParsedJsonProperty property, StringBuilder stringB switch (property.JsonElement.ValueKind) { case JsonValueKind.Object: - var propertyName = property.JsonName ?? property.BclType.Name; + { + var propertyName = GetObjectTypeName(property, Language.CSharp); extraTypes.Add(InternalParse(propertyName, property.JsonElement, false)); stringBuilder.AppendLine(ParseCustomType(property)); return true; + } + case JsonValueKind.Array: + { var childrenTypes = J2SUtils.GetArrayTypes(property); if (childrenTypes.Count is 0) @@ -196,6 +206,7 @@ private bool HandleCustomType(ParsedJsonProperty property, StringBuilder stringB extraTypes.Add(InternalParse(typeName, childrenTypes[0].JsonElement, false)); return true; + } default: return false; } diff --git a/Json2SharpLib/Emitters/Python/PythonClassEmitter.cs b/Json2SharpLib/Emitters/Python/PythonClassEmitter.cs index c573aa3..1afcdf6 100644 --- a/Json2SharpLib/Emitters/Python/PythonClassEmitter.cs +++ b/Json2SharpLib/Emitters/Python/PythonClassEmitter.cs @@ -22,7 +22,7 @@ internal sealed class PythonClassEmitter : CodeEmitter /// Creates an object that parses JSON data into a Python class. /// /// The parsing options. - internal PythonClassEmitter(Json2SharpPythonOptions options) + internal PythonClassEmitter(Json2SharpPythonOptions options) : base(options.TypeNameHandler) { _addTypeHint = options.AddTypeHints; _indentationPadding = new string( @@ -48,7 +48,6 @@ public override string Parse(string objectName, JsonElement jsonElement) /// The Python class. private string InternalParse(string objectName, JsonElement jsonElement, bool emitHeaders) { - objectName = objectName.ToPascalCase(); var properties = Json2Sharp.ParseProperties(jsonElement); if (properties.Count is 0) @@ -69,7 +68,7 @@ private string InternalParse(string objectName, JsonElement jsonElement, bool em // Add the imports if (emitHeaders && _addTypeHint) { - var hasUuid = stringBuilder.Contains(": uuid"); + var hasUuid = stringBuilder.Contains(": UUID"); var hasDatetime = stringBuilder.Contains(": datetime"); var hasOptional = stringBuilder.Contains("Optional["); @@ -77,7 +76,7 @@ private string InternalParse(string objectName, JsonElement jsonElement, bool em stringBuilder.Insert(0, Environment.NewLine + Environment.NewLine); if (hasUuid) - stringBuilder.Insert(0, "import uuid" + Environment.NewLine); + stringBuilder.Insert(0, "from uuid import UUID" + Environment.NewLine); if (hasDatetime) stringBuilder.Insert(0, "from datetime import datetime" + Environment.NewLine); @@ -92,11 +91,12 @@ private string InternalParse(string objectName, JsonElement jsonElement, bool em /// protected override string ParseCustomType(ParsedJsonProperty property) { - var propertyType = (_addTypeHint) - ? ": " + GetObjectTypeName(property, Language.Python) - : string.Empty; + if (!_addTypeHint) + return $"{property.JsonName.ToSnakeCase()},"; - return $"{property.JsonName.ToSnakeCase()}{propertyType},"; + var typeName = GetObjectTypeName(property, Language.Python); + + return $"{property.JsonName.ToSnakeCase()}: {typeName},"; } /// @@ -157,18 +157,18 @@ private static bool AddCustomTypes(StringBuilder stringBuilder, List ext { var sanitizedExtraTypes = extraTypes .Where(x => !string.IsNullOrWhiteSpace(x)) - .Reverse(); + .Reverse() + .ToArray(); + + if (sanitizedExtraTypes.Length is 0) + return false; // Add the custom types at the top - if (sanitizedExtraTypes.Any()) - { - stringBuilder.Insert(0, Environment.NewLine + Environment.NewLine); - stringBuilder.Insert(0, string.Join(Environment.NewLine + Environment.NewLine, sanitizedExtraTypes)); + stringBuilder.Insert(0, Environment.NewLine + Environment.NewLine); + stringBuilder.Insert(0, string.Join(Environment.NewLine + Environment.NewLine, sanitizedExtraTypes)); - return true; - } + return true; - return false; } /// @@ -184,7 +184,7 @@ private bool HandleCustomType(ParsedJsonProperty property, StringBuilder stringB { case JsonValueKind.Object: stringBuilder.AppendIndentedLine(ParseCustomType(property), _indentationPadding, 2); - extraTypes.Add(InternalParse(property.JsonName!, property.JsonElement, false)); + extraTypes.Add(InternalParse(GetObjectTypeName(property, Language.Python), property.JsonElement, false)); return true; case JsonValueKind.Array: diff --git a/Json2SharpLib/Emitters/Python/PythonDataClassEmitter.cs b/Json2SharpLib/Emitters/Python/PythonDataClassEmitter.cs index 8420730..ec75ee6 100644 --- a/Json2SharpLib/Emitters/Python/PythonDataClassEmitter.cs +++ b/Json2SharpLib/Emitters/Python/PythonDataClassEmitter.cs @@ -21,7 +21,7 @@ internal sealed class PythonDataClassEmitter : CodeEmitter /// Creates an object that parses JSON data into a Python data class. /// /// The parsing options. - internal PythonDataClassEmitter(Json2SharpPythonOptions options) + internal PythonDataClassEmitter(Json2SharpPythonOptions options) : base(options.TypeNameHandler) { _indentationPadding = new string( options.IndentationPaddingCharacter is IndentationCharacterType.Space ? ' ' : '\t', @@ -46,7 +46,6 @@ public override string Parse(string objectName, JsonElement jsonElement) /// The Python data class. private string InternalParse(string objectName, JsonElement jsonElement, bool emitHeaders) { - objectName = objectName.ToPascalCase(); var properties = Json2Sharp.ParseProperties(jsonElement); if (properties.Count is 0) @@ -62,8 +61,8 @@ private string InternalParse(string objectName, JsonElement jsonElement, bool em { stringBuilder.Insert(0, Environment.NewLine + Environment.NewLine); - if (stringBuilder.Contains(": uuid")) - stringBuilder.Insert(0, "import uuid" + Environment.NewLine); + if (stringBuilder.Contains(": UUID")) + stringBuilder.Insert(0, "from uuid import UUID" + Environment.NewLine); if (stringBuilder.Contains(": datetime")) stringBuilder.Insert(0, "from datetime import datetime" + Environment.NewLine); @@ -79,7 +78,10 @@ private string InternalParse(string objectName, JsonElement jsonElement, bool em /// protected override string ParseCustomType(ParsedJsonProperty property) - => $"{property.JsonName.ToSnakeCase()}: {GetObjectTypeName(property, Language.Python)}"; + { + var typeName = GetObjectTypeName(property, Language.Python); + return $"{property.JsonName.ToSnakeCase()}: {typeName}"; + } /// protected override string ParseArrayType(ParsedJsonProperty property, IReadOnlyList childrenTypes, out string typeName) @@ -134,18 +136,18 @@ private static bool AddCustomTypes(StringBuilder stringBuilder, List ext { var sanitizedExtraTypes = extraTypes .Where(x => !string.IsNullOrWhiteSpace(x)) - .Reverse(); + .Reverse() + .ToArray(); // Add the custom types at the top - if (sanitizedExtraTypes.Any()) - { - stringBuilder.Insert(0, Environment.NewLine + Environment.NewLine); - stringBuilder.Insert(0, string.Join(Environment.NewLine + Environment.NewLine, sanitizedExtraTypes)); + if (sanitizedExtraTypes.Length is 0) + return false; - return true; - } + stringBuilder.Insert(0, Environment.NewLine + Environment.NewLine); + stringBuilder.Insert(0, string.Join(Environment.NewLine + Environment.NewLine, sanitizedExtraTypes)); + + return true; - return false; } /// @@ -161,7 +163,7 @@ private bool HandleCustomType(ParsedJsonProperty property, StringBuilder stringB { case JsonValueKind.Object: stringBuilder.AppendIndentedLine(ParseCustomType(property), _indentationPadding, 1); - extraTypes.Add(InternalParse(property.JsonName!, property.JsonElement, false)); + extraTypes.Add(InternalParse(GetObjectTypeName(property, Language.Python), property.JsonElement, false)); return true; case JsonValueKind.Array: diff --git a/Json2SharpLib/Json2SharpLib.csproj b/Json2SharpLib/Json2SharpLib.csproj index bac2111..783a315 100644 --- a/Json2SharpLib/Json2SharpLib.csproj +++ b/Json2SharpLib/Json2SharpLib.csproj @@ -7,8 +7,8 @@ - - compile;analysers + + compile;analyzers \ No newline at end of file diff --git a/Json2SharpLib/Models/LanguageOptions/Abstractions/BaseLanguageOptions.cs b/Json2SharpLib/Models/LanguageOptions/Abstractions/BaseLanguageOptions.cs index 9be9060..cf487f1 100644 --- a/Json2SharpLib/Models/LanguageOptions/Abstractions/BaseLanguageOptions.cs +++ b/Json2SharpLib/Models/LanguageOptions/Abstractions/BaseLanguageOptions.cs @@ -7,17 +7,25 @@ namespace Json2SharpLib.Models.LanguageOptions.Abstractions; /// public abstract record BaseLanguageOptions { - private int _indentationCharacterAmount = 4; + private readonly int _indentationCharacterAmount = 4; /// - /// Defines the character that should prepend member declarations. + /// Defines the character that should prepend member declarations.
/// Default is . ///
public IndentationCharacterType IndentationPaddingCharacter { get; init; } = IndentationCharacterType.Space; /// - /// Defines the amount of that should - /// be prepended to member declarations. + /// Defines a handler that transforms the type name of each parsed property before it is used in generated class + /// members. Use it to apply custom naming logic to the type name of nested objects.
+ /// Default is a function that returns the parsed property name in the language's default naming convention. + ///
+ /// The parsed property name. + public abstract Func TypeNameHandler { get; init; } + + /// + /// Defines the amount of that should be prepended to member declarations. + ///
/// Default is 4. ///
public int IndentationCharacterAmount diff --git a/Json2SharpLib/Models/LanguageOptions/Json2SharpCSharpOptions.cs b/Json2SharpLib/Models/LanguageOptions/Json2SharpCSharpOptions.cs index c18fdfa..8c52d40 100644 --- a/Json2SharpLib/Models/LanguageOptions/Json2SharpCSharpOptions.cs +++ b/Json2SharpLib/Models/LanguageOptions/Json2SharpCSharpOptions.cs @@ -43,4 +43,7 @@ public sealed record Json2SharpCSharpOptions : BaseLanguageOptions /// Default is . /// public bool IsPropertyRequired { get; init; } = true; + + /// + public override Func TypeNameHandler { get; init; } = static propertyName => propertyName.ToPascalCase(); } \ No newline at end of file diff --git a/Json2SharpLib/Models/LanguageOptions/Json2SharpPythonOptions.cs b/Json2SharpLib/Models/LanguageOptions/Json2SharpPythonOptions.cs index 06a7b6b..f77f8e2 100644 --- a/Json2SharpLib/Models/LanguageOptions/Json2SharpPythonOptions.cs +++ b/Json2SharpLib/Models/LanguageOptions/Json2SharpPythonOptions.cs @@ -24,4 +24,7 @@ public sealed record Json2SharpPythonOptions : BaseLanguageOptions /// Default is . /// public bool UseOptional { get; init; } + + /// + public override Func TypeNameHandler { get; init; } = static propertyName => propertyName.ToPascalCase(); } \ No newline at end of file diff --git a/Json2SharpTests/CSharpTests/CSharpDataTests.cs b/Json2SharpTests/CSharpTests/CSharpDataTests.cs index 9c6b407..284812c 100644 --- a/Json2SharpTests/CSharpTests/CSharpDataTests.cs +++ b/Json2SharpTests/CSharpTests/CSharpDataTests.cs @@ -2,6 +2,7 @@ using Json2SharpLib.Enums; using Json2SharpLib.Models; using Json2SharpTests.CSharpTests.Models.Answers; +using Kotz.Extensions; namespace Json2SharpTests.CSharpTests; @@ -80,6 +81,36 @@ internal void OutputTest(string className, string input, string expectedOutput, actualOutput.Replace("\r", string.Empty) ); } + + [Theory] + [InlineData(nameof(CustomHandleTypes), CustomHandleTypes.Input, CustomHandleTypes.RecordPrimaryCtorOutputNoAtt, CSharpObjectType.Record, CSharpSerializationAttribute.NoAttribute)] + [InlineData(nameof(CustomHandleTypes), CustomHandleTypes.Input, CustomHandleTypes.RecordPrimaryCtorOutput, CSharpObjectType.Record, CSharpSerializationAttribute.NewtonsoftJson)] + [InlineData(nameof(CustomHandleTypes), CustomHandleTypes.Input, CustomHandleTypes.RecordOutput, CSharpObjectType.Record, CSharpSerializationAttribute.SystemTextJson)] + [InlineData(nameof(CustomHandleTypes), CustomHandleTypes.Input, CustomHandleTypes.ClassOutputNoAtt, CSharpObjectType.Class, CSharpSerializationAttribute.NoAttribute)] + [InlineData(nameof(CustomHandleTypes), CustomHandleTypes.Input, CustomHandleTypes.ClassOutput, CSharpObjectType.Class, CSharpSerializationAttribute.SystemTextJson)] + [InlineData(nameof(CustomHandleTypes), CustomHandleTypes.Input, CustomHandleTypes.StructOutput, CSharpObjectType.Struct, CSharpSerializationAttribute.SystemTextJson)] + internal void TypeHandleOutputTest(string className, string input, string expectedOutput, CSharpObjectType targetType, CSharpSerializationAttribute serializationAttribute) + { + var options = new Json2SharpOptions() + { + TargetLanguage = Language.CSharp, + + CSharpOptions = new() + { + TargetType = targetType, + SerializationAttribute = serializationAttribute, + TypeNameHandler = propertyType => propertyType.ToSnakeCase() + } + }; + + var actualOutput = Json2Sharp.Parse(className.ToSnakeCase(), input, options); + + Assert.Equal( + expectedOutput.Replace("\r", string.Empty), + actualOutput.Replace("\r", string.Empty) + ); + } + [Theory] [InlineData(ArrayRoot.BadInput1)] diff --git a/Json2SharpTests/CSharpTests/Models/Answers/CustomHandleTypes.cs b/Json2SharpTests/CSharpTests/Models/Answers/CustomHandleTypes.cs new file mode 100644 index 0000000..77d1e7e --- /dev/null +++ b/Json2SharpTests/CSharpTests/Models/Answers/CustomHandleTypes.cs @@ -0,0 +1,193 @@ +namespace Json2SharpTests.CSharpTests.Models.Answers; + +internal static class CustomHandleTypes +{ + public const string Input = """ + { + "null_thing": null, + "empty_thing": {}, + "thing": [ + { + "text": "hello world", + "number": 1, + "int_array": [ 1, 2, 3 ], + "prop_base:colon": 2, + "prop_custom:colon": { "blep": "nested" } + } + ] + } + """; + + public const string RecordPrimaryCtorOutput = """ + using Newtonsoft.Json; + + public sealed record custom_handle_types( + [JsonProperty("null_thing")] object? NullThing, + [JsonProperty("empty_thing")] object EmptyThing, + [JsonProperty("thing")] thing[] Thing + ); + + public sealed record thing( + [JsonProperty("text")] string Text, + [JsonProperty("number")] int Number, + [JsonProperty("int_array")] int[] IntArray, + [JsonProperty("prop_base:colon")] int PropBaseColon, + [JsonProperty("prop_custom:colon")] prop_custom_colon PropCustomColon + ); + + public sealed record prop_custom_colon( + [JsonProperty("blep")] string Blep + ); + """; + + public const string RecordPrimaryCtorOutputNoAtt = """ + public sealed record custom_handle_types( + object? NullThing, + object EmptyThing, + thing[] Thing + ); + + public sealed record thing( + string Text, + int Number, + int[] IntArray, + int PropBaseColon, + prop_custom_colon PropCustomColon + ); + + public sealed record prop_custom_colon( + string Blep + ); + """; + + public const string RecordOutput = """ + using System.Text.Json.Serialization; + + public sealed record custom_handle_types( + [property: JsonPropertyName("null_thing")] object? NullThing, + [property: JsonPropertyName("empty_thing")] object EmptyThing, + [property: JsonPropertyName("thing")] thing[] Thing + ); + + public sealed record thing( + [property: JsonPropertyName("text")] string Text, + [property: JsonPropertyName("number")] int Number, + [property: JsonPropertyName("int_array")] int[] IntArray, + [property: JsonPropertyName("prop_base:colon")] int PropBaseColon, + [property: JsonPropertyName("prop_custom:colon")] prop_custom_colon PropCustomColon + ); + + public sealed record prop_custom_colon( + [property: JsonPropertyName("blep")] string Blep + ); + """; + + public const string ClassOutput = """ + using System.Text.Json.Serialization; + + public sealed class custom_handle_types + { + [JsonPropertyName("null_thing")] + public required object? NullThing { get; init; } + + [JsonPropertyName("empty_thing")] + public required object EmptyThing { get; init; } + + [JsonPropertyName("thing")] + public required thing[] Thing { get; init; } + } + + public sealed class thing + { + [JsonPropertyName("text")] + public required string Text { get; init; } + + [JsonPropertyName("number")] + public required int Number { get; init; } + + [JsonPropertyName("int_array")] + public required int[] IntArray { get; init; } + + [JsonPropertyName("prop_base:colon")] + public required int PropBaseColon { get; init; } + + [JsonPropertyName("prop_custom:colon")] + public required prop_custom_colon PropCustomColon { get; init; } + } + + public sealed class prop_custom_colon + { + [JsonPropertyName("blep")] + public required string Blep { get; init; } + } + """; + + public const string ClassOutputNoAtt = """ + public sealed class custom_handle_types + { + public required object? NullThing { get; init; } + + public required object EmptyThing { get; init; } + + public required thing[] Thing { get; init; } + } + + public sealed class thing + { + public required string Text { get; init; } + + public required int Number { get; init; } + + public required int[] IntArray { get; init; } + + public required int PropBaseColon { get; init; } + + public required prop_custom_colon PropCustomColon { get; init; } + } + + public sealed class prop_custom_colon + { + public required string Blep { get; init; } + } + """; + + public const string StructOutput = """ + using System.Text.Json.Serialization; + + public readonly struct custom_handle_types + { + [JsonPropertyName("null_thing")] + public required object? NullThing { get; init; } + + [JsonPropertyName("empty_thing")] + public required object EmptyThing { get; init; } + + [JsonPropertyName("thing")] + public required thing[] Thing { get; init; } + } + + public readonly struct thing + { + [JsonPropertyName("text")] + public required string Text { get; init; } + + [JsonPropertyName("number")] + public required int Number { get; init; } + + [JsonPropertyName("int_array")] + public required int[] IntArray { get; init; } + + [JsonPropertyName("prop_base:colon")] + public required int PropBaseColon { get; init; } + + [JsonPropertyName("prop_custom:colon")] + public required prop_custom_colon PropCustomColon { get; init; } + } + + public readonly struct prop_custom_colon + { + [JsonPropertyName("blep")] + public required string Blep { get; init; } + } + """; +} \ No newline at end of file diff --git a/Json2SharpTests/Json2SharpTests.csproj b/Json2SharpTests/Json2SharpTests.csproj index 518decc..c77bff5 100644 --- a/Json2SharpTests/Json2SharpTests.csproj +++ b/Json2SharpTests/Json2SharpTests.csproj @@ -1,6 +1,6 @@ - net8.0 + net9.0 Unit tests enable enable @@ -9,13 +9,14 @@ False - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Json2SharpTests/PythonTests/Models/Answers/CustomHandleTypes.cs b/Json2SharpTests/PythonTests/Models/Answers/CustomHandleTypes.cs new file mode 100644 index 0000000..a36c46d --- /dev/null +++ b/Json2SharpTests/PythonTests/Models/Answers/CustomHandleTypes.cs @@ -0,0 +1,118 @@ +namespace Json2SharpTests.PythonTests.Models.Answers; + +internal static class CustomHandleTypes +{ + public const string Input = """ + { + "null_thing": null, + "empty_thing": {}, + "thing": { + "text": "hello world", + "number": 1, + "int_array": [ 1, 2, 3 ], + "prop_base:colon": 2, + "prop_custom:colon": { "blep": "nested" } + } + } + """; + + public const string DataClassOutput = """ + from dataclasses import dataclass + from typing import Optional + + + @dataclass + class prop_custom_colon: + blep: str + + + @dataclass + class thing: + text: str + number: int + int_array: list[int] + prop_base_colon: int + prop_custom_colon: prop_custom_colon + + + @dataclass + class custom_handle_types: + null_thing: Optional[object] + empty_thing: object + thing: thing + + """; + + public const string Output = """ + from typing import Optional + + + class prop_custom_colon: + def __init__( + blep: str + ) -> None: + self.blep = blep + + + class thing: + def __init__( + text: str, + number: int, + int_array: list[int], + prop_base_colon: int, + prop_custom_colon: prop_custom_colon + ) -> None: + self.text = text + self.number = number + self.int_array = int_array + self.prop_base_colon = prop_base_colon + self.prop_custom_colon = prop_custom_colon + + + class custom_handle_types: + def __init__( + null_thing: Optional[object], + empty_thing: object, + thing: thing + ) -> None: + self.null_thing = null_thing + self.empty_thing = empty_thing + self.thing = thing + + """; + + public const string OutputNoTypeHints = """ + class prop_custom_colon: + def __init__( + blep + ): + self.blep = blep + + + class thing: + def __init__( + text, + number, + int_array, + prop_base_colon, + prop_custom_colon + ): + self.text = text + self.number = number + self.int_array = int_array + self.prop_base_colon = prop_base_colon + self.prop_custom_colon = prop_custom_colon + + + class custom_handle_types: + def __init__( + null_thing, + empty_thing, + thing + ): + self.null_thing = null_thing + self.empty_thing = empty_thing + self.thing = thing + + """; +} \ No newline at end of file diff --git a/Json2SharpTests/PythonTests/Models/Answers/TextTypes.cs b/Json2SharpTests/PythonTests/Models/Answers/TextTypes.cs index 1426f99..c47855c 100644 --- a/Json2SharpTests/PythonTests/Models/Answers/TextTypes.cs +++ b/Json2SharpTests/PythonTests/Models/Answers/TextTypes.cs @@ -14,7 +14,7 @@ internal static class TextTypes public const string DataClassOutput = """ from dataclasses import dataclass from datetime import datetime - import uuid + from uuid import UUID @dataclass @@ -22,13 +22,13 @@ class TextTypes: text: str empty_text: str date_time_offset: datetime - id: uuid + id: UUID """; public const string Output = """ from datetime import datetime - import uuid + from uuid import UUID class TextTypes: @@ -36,7 +36,7 @@ def __init__( text: str, empty_text: str, date_time_offset: datetime, - id: uuid + id: UUID ) -> None: self.text = text self.empty_text = empty_text diff --git a/Json2SharpTests/PythonTests/PythonDataTests.cs b/Json2SharpTests/PythonTests/PythonDataTests.cs index 778be21..237306e 100644 --- a/Json2SharpTests/PythonTests/PythonDataTests.cs +++ b/Json2SharpTests/PythonTests/PythonDataTests.cs @@ -2,6 +2,7 @@ using Json2SharpLib.Enums; using Json2SharpLib.Models; using Json2SharpTests.PythonTests.Models.Answers; +using Kotz.Extensions; namespace Json2SharpTests.PythonTests; @@ -83,4 +84,32 @@ internal void OutputNoneTypeHintsTest(string className, string input, string exp actualOutput.Replace("\r", string.Empty) ); } + + + [Theory] + [InlineData(nameof(CustomHandleTypes), CustomHandleTypes.Input, CustomHandleTypes.Output, true, false)] + [InlineData(nameof(CustomHandleTypes), CustomHandleTypes.Input, CustomHandleTypes.DataClassOutput, true, true)] + [InlineData(nameof(CustomHandleTypes), CustomHandleTypes.Input, CustomHandleTypes.OutputNoTypeHints, false, false)] + internal void TypeHandleOutputTest(string className, string input, string expectedOutput, bool addTypeHints, bool useDataClass) + { + var options = new Json2SharpOptions() + { + TargetLanguage = Language.Python, + + PythonOptions = new() + { + AddTypeHints = addTypeHints, + UseDataClass = useDataClass, + UseOptional = true, + TypeNameHandler = propertyType => propertyType.ToSnakeCase() + } + }; + + var actualOutput = Json2Sharp.Parse(className.ToSnakeCase(), input, options); + + Assert.Equal( + expectedOutput.Replace("\r", string.Empty), + actualOutput.Replace("\r", string.Empty) + ); + } } \ No newline at end of file