commit 2385affccb0ce07651b53cdbb49211e91bedf490 Author: Mike Phares Date: Sat Aug 13 13:01:47 2022 -0700 Converted to net6.0 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1d5923c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,244 @@ +[*.cs] +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true +csharp_prefer_braces = false +csharp_prefer_simple_default_expression = true:warning +csharp_prefer_simple_using_statement = true:warning +csharp_prefer_static_local_function = true:warning +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true +csharp_style_conditional_delegate_call = true +csharp_style_deconstructed_variable_declaration = false +csharp_style_expression_bodied_accessors = when_on_single_line:warning +csharp_style_expression_bodied_constructors = when_on_single_line:warning +csharp_style_expression_bodied_indexers = when_on_single_line:warning +csharp_style_expression_bodied_lambdas = when_on_single_line:warning +csharp_style_expression_bodied_local_functions = when_on_single_line:warning +csharp_style_expression_bodied_methods = when_on_single_line:warning +csharp_style_expression_bodied_operators = when_on_single_line:warning +csharp_style_expression_bodied_properties = when_on_single_line:warning +csharp_style_implicit_object_creation_when_type_is_apparent = true:warning +csharp_style_inlined_variable_declaration = false +csharp_style_namespace_declarations = file_scoped:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_prefer_index_operator = false:warning +csharp_style_prefer_not_pattern = true:warning +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_pattern_matching = true:warning +csharp_style_prefer_range_operator = false:warning +csharp_style_prefer_switch_expression = true:warning +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable:warning +csharp_style_unused_value_expression_statement_preference = discard_variable:warning +csharp_style_var_elsewhere = false:warning +csharp_style_var_for_built_in_types = false:warning +csharp_style_var_when_type_is_apparent = false:warning +csharp_using_directive_placement = outside_namespace +dotnet_code_quality_unused_parameters = all +dotnet_code_quality_unused_parameters = non_public # IDE0060: Remove unused parameter +dotnet_code_quality.CAXXXX.api_surface = private, internal +dotnet_diagnostic.CA1825.severity = warning # CA1823: Avoid zero-length array allocations +dotnet_diagnostic.CA1829.severity = warning # CA1829: Use Length/Count property instead of Count() when available +dotnet_diagnostic.CA1834.severity = warning # CA1834: Consider using 'StringBuilder.Append(char)' when applicable +dotnet_diagnostic.IDE0001.severity = warning # IDE0001: Simplify name +dotnet_diagnostic.IDE0002.severity = warning # Simplify (member access) - System.Version.Equals("1", "2"); Version.Equals("1", "2"); +dotnet_diagnostic.IDE0005.severity = warning # Using directive is unnecessary +dotnet_diagnostic.IDE0047.severity = warning # IDE0047: Parentheses can be removed +dotnet_diagnostic.IDE0060.severity = warning # IDE0060: Remove unused parameter +dotnet_naming_rule.abstract_method_should_be_pascal_case.severity = warning +dotnet_naming_rule.abstract_method_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.abstract_method_should_be_pascal_case.symbols = abstract_method +dotnet_naming_rule.class_should_be_pascal_case.severity = warning +dotnet_naming_rule.class_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.class_should_be_pascal_case.symbols = class +dotnet_naming_rule.delegate_should_be_pascal_case.severity = warning +dotnet_naming_rule.delegate_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.delegate_should_be_pascal_case.symbols = delegate +dotnet_naming_rule.enum_should_be_pascal_case.severity = warning +dotnet_naming_rule.enum_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.enum_should_be_pascal_case.symbols = enum +dotnet_naming_rule.event_should_be_pascal_case.severity = warning +dotnet_naming_rule.event_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.event_should_be_pascal_case.symbols = event +dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.method_should_be_pascal_case.severity = warning +dotnet_naming_rule.method_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.method_should_be_pascal_case.symbols = method +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.private_method_should_be_pascal_case.severity = warning +dotnet_naming_rule.private_method_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.private_method_should_be_pascal_case.symbols = private_method +dotnet_naming_rule.private_or_internal_field_should_be_private_of_internal_field.severity = warning +dotnet_naming_rule.private_or_internal_field_should_be_private_of_internal_field.style = private_of_internal_field +dotnet_naming_rule.private_or_internal_field_should_be_private_of_internal_field.symbols = private_or_internal_field +dotnet_naming_rule.private_or_internal_static_field_should_be_private_of_internal_field.severity = warning +dotnet_naming_rule.private_or_internal_static_field_should_be_private_of_internal_field.style = private_of_internal_field +dotnet_naming_rule.private_or_internal_static_field_should_be_private_of_internal_field.symbols = private_or_internal_static_field +dotnet_naming_rule.property_should_be_pascal_case.severity = warning +dotnet_naming_rule.property_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.property_should_be_pascal_case.symbols = property +dotnet_naming_rule.public_or_protected_field_should_be_private_of_internal_field.severity = warning +dotnet_naming_rule.public_or_protected_field_should_be_private_of_internal_field.style = private_of_internal_field +dotnet_naming_rule.public_or_protected_field_should_be_private_of_internal_field.symbols = public_or_protected_field +dotnet_naming_rule.static_field_should_be_pascal_case.severity = warning +dotnet_naming_rule.static_field_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.static_field_should_be_pascal_case.symbols = static_field +dotnet_naming_rule.static_method_should_be_pascal_case.severity = warning +dotnet_naming_rule.static_method_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.static_method_should_be_pascal_case.symbols = static_method +dotnet_naming_rule.struct_should_be_pascal_case.severity = warning +dotnet_naming_rule.struct_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.struct_should_be_pascal_case.symbols = struct +dotnet_naming_rule.types_should_be_pascal_case.severity = warning +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_style.begins_with_i.capitalization = pascal_case +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.private_of_internal_field.capitalization = pascal_case +dotnet_naming_style.private_of_internal_field.required_prefix = _ +dotnet_naming_style.private_of_internal_field.required_suffix = +dotnet_naming_style.private_of_internal_field.word_separator = +dotnet_naming_symbols.abstract_method.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.abstract_method.applicable_kinds = method +dotnet_naming_symbols.abstract_method.required_modifiers = abstract +dotnet_naming_symbols.class.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.class.applicable_kinds = class +dotnet_naming_symbols.class.required_modifiers = +dotnet_naming_symbols.delegate.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.delegate.applicable_kinds = delegate +dotnet_naming_symbols.delegate.required_modifiers = +dotnet_naming_symbols.enum.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.enum.applicable_kinds = enum +dotnet_naming_symbols.enum.required_modifiers = +dotnet_naming_symbols.event.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.event.applicable_kinds = event +dotnet_naming_symbols.event.required_modifiers = +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.required_modifiers = +dotnet_naming_symbols.method.applicable_accessibilities = public +dotnet_naming_symbols.method.applicable_kinds = method +dotnet_naming_symbols.method.required_modifiers = +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.required_modifiers = +dotnet_naming_symbols.private_method.applicable_accessibilities = private +dotnet_naming_symbols.private_method.applicable_kinds = method +dotnet_naming_symbols.private_method.required_modifiers = +dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_field.required_modifiers = +dotnet_naming_symbols.private_or_internal_static_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_static_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_static_field.required_modifiers = static +dotnet_naming_symbols.property.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.property.applicable_kinds = property +dotnet_naming_symbols.property.required_modifiers = +dotnet_naming_symbols.public_or_protected_field.applicable_accessibilities = public, protected +dotnet_naming_symbols.public_or_protected_field.applicable_kinds = field +dotnet_naming_symbols.public_or_protected_field.required_modifiers = +dotnet_naming_symbols.static_field.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.static_field.applicable_kinds = field +dotnet_naming_symbols.static_field.required_modifiers = static +dotnet_naming_symbols.static_method.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.static_method.applicable_kinds = method +dotnet_naming_symbols.static_method.required_modifiers = static +dotnet_naming_symbols.struct.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.struct.applicable_kinds = struct +dotnet_naming_symbols.struct.required_modifiers = +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.required_modifiers = +dotnet_remove_unnecessary_suppression_exclusions = 0 +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +dotnet_style_allow_multiple_blank_lines_experimental = false:warning +dotnet_style_allow_statement_immediately_after_block_experimental = true +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true:warning +dotnet_style_object_initializer = true:warning +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_compound_assignment = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = false +dotnet_style_prefer_conditional_expression_over_return = false +dotnet_diagnostic.IDE0049.severity = warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_inferred_tuple_names = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_simplified_boolean_expressions = true:warning +dotnet_style_prefer_simplified_interpolation = true +dotnet_style_qualification_for_event = false:error +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false:error +dotnet_style_qualification_for_property = false:error +dotnet_style_readonly_field = true:warning +dotnet_style_require_accessibility_modifiers = for_non_interface_members +end_of_line = crlf +file_header_template = unset +indent_size = 4 +indent_style = space +insert_final_newline = false +root = true +tab_width = 4 +# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1822 +# https://github.com/dotnet/aspnetcore/blob/main/.editorconfig +# https://github.com/dotnet/project-system/blob/main/.editorconfig \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3b451f --- /dev/null +++ b/.gitignore @@ -0,0 +1,266 @@ + +*.cache +*.testlog +.vs/Json2CSharpCodeGenerator/v16/.suo +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +[Xx]64/ +[Xx]86/ +[Bb]uild/ +bld/ +[Bb]in/ +[Oo]bj/ + + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml + +# TODO: Un-comment the next line if you do not want to checkin +# your web deploy settings because they may include unencrypted +# passwords +#*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# LightSwitch generated files +GeneratedArtifacts/ +ModelManifest.xml + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ +*.foldersync +*.sitecore +gulpfile.js + +package-lock.json + +package.json + +#Exclude Serializations Folder +!./Serializations/**/ + +# Webpack +dist/ +webpack.config.js + +#OutputDir +/Publish + +# IntelliJ-based IDE configuration (ie. Rider) +/.idea diff --git a/.vscode/format-report.json b/.vscode/format-report.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/.vscode/format-report.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..4fb1ca1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/Json2CSharpCodeGenerator.WinForms/bin/Debug/net6.0-windows/win-x64/Json2CSharpCodeGenerator.WinForms.dll", + "args": [], + "cwd": "${workspaceFolder}/Json2CSharpCodeGenerator.WinForms", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..dbf2cfe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "cSpell.enabled": false, + "files.exclude": { + "**/.git": false + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..b47a147 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Json2CSharpCodeGenerator.WinForms/Json2CSharpCodeGenerator.WinForms.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Json2CSharpCodeGenerator.WinForms/Json2CSharpCodeGenerator.WinForms.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/Json2CSharpCodeGenerator.WinForms/Json2CSharpCodeGenerator.WinForms.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/CodeWriters/CSharpCodeWriter.cs b/Json2CSharpCodeGenerator.Lib/CodeWriters/CSharpCodeWriter.cs new file mode 100644 index 0000000..65398a9 --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/CodeWriters/CSharpCodeWriter.cs @@ -0,0 +1,513 @@ +using System.Text; + +namespace Json2CSharpCodeGenerator.Lib.CodeWriters; + +public class CSharpCodeWriter : ICodeBuilder +{ + public string FileExtension => ".cs"; + + public string DisplayName => "C#"; + + private const string _NoRenameAttribute = "[Obfuscation(Feature = \"renaming\", Exclude = true)]"; + private const string _NoPruneAttribute = "[Obfuscation(Feature = \"trigger\", Exclude = false)]"; + + private static readonly HashSet _ReservedKeywords = new(comparer: StringComparer.Ordinal) { + "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", + "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", + "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", "long", + "namespace", "new", "null", "object", "operator", "out", "override", "params", "private", "protected", "public", + "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", + "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", + "virtual", "void", "volatile", "while" + }; + + public bool IsReservedKeyword(string word) => _ReservedKeywords.Contains(word ?? string.Empty); + + IReadOnlyCollection ICodeBuilder.ReservedKeywords => _ReservedKeywords; + + public string GetTypeName(JsonType type, IJsonClassGeneratorConfig config) + { + return type.Type switch + { + JsonTypeEnum.Anything => "object", + JsonTypeEnum.Array => GetCollectionTypeName(elementTypeName: GetTypeName(type.InternalType, config), config.CollectionType), + JsonTypeEnum.Dictionary => "Dictionary", + JsonTypeEnum.Boolean => "bool", + JsonTypeEnum.Float => "double", + JsonTypeEnum.Integer => "int", + JsonTypeEnum.Long => "long", + JsonTypeEnum.Date => "DateTime", + JsonTypeEnum.NonConstrained => "object", + JsonTypeEnum.NullableBoolean => "bool?", + JsonTypeEnum.NullableFloat => "double?", + JsonTypeEnum.NullableInteger => "int?", + JsonTypeEnum.NullableLong => "long?", + JsonTypeEnum.NullableDate => "DateTime?", + JsonTypeEnum.NullableSomething => "object", + JsonTypeEnum.Object => type.NewAssignedName, + JsonTypeEnum.String => "string", + _ => throw new NotSupportedException("Unsupported json type: " + type.Type), + }; + } + + private static string GetCollectionTypeName(string elementTypeName, OutputCollectionType type) + { + return type switch + { + OutputCollectionType.Array => elementTypeName + "[]", + OutputCollectionType.MutableList => "List<" + elementTypeName + ">", + OutputCollectionType.IReadOnlyList => "IReadOnlyList<" + elementTypeName + ">", + OutputCollectionType.ImmutableArray => "ImmutableArray<" + elementTypeName + ">", + _ => throw new ArgumentOutOfRangeException(paramName: nameof(type), actualValue: type, message: "Invalid " + nameof(OutputCollectionType) + " enum value."), + }; + } + + private bool ShouldApplyNoRenamingAttribute(IJsonClassGeneratorConfig config) => config.ApplyObfuscationAttributes && !config.UsePascalCase; + + private bool ShouldApplyNoPruneAttribute(IJsonClassGeneratorConfig config) => config.ApplyObfuscationAttributes && config.OutputType == OutputTypes.MutableClass && config.MutableClasses.Members == OutputMembers.AsPublicFields; + + public void WriteFileStart(IJsonClassGeneratorConfig config, StringBuilder sw) + { + if (config.HasNamespace()) + { + List importNamespaces = new() + { + "System", + "System.Collections.Generic" + }; + + if (ShouldApplyNoPruneAttribute(config) || ShouldApplyNoRenamingAttribute(config)) + { + importNamespaces.Add("System.Reflection"); + } + + switch (config.AttributeLibrary) + { + case JsonLibrary.NewtonsoftJson: + importNamespaces.Add("Newtonsoft.Json"); + importNamespaces.Add("Newtonsoft.Json.Linq"); + break; + case JsonLibrary.SystemTextJson: + importNamespaces.Add("System.Text.Json"); + break; + } + + if (!string.IsNullOrWhiteSpace(config.SecondaryNamespace) && !config.UseNestedClasses) + { + importNamespaces.Add(config.SecondaryNamespace); + } + + importNamespaces.Sort(CompareNamespacesSystemFirst); + + foreach (string ns in importNamespaces) // NOTE: Using `.Distinct()` after sorting may cause out-of-order results. + { + _ = sw.AppendFormat("using {0};{1}", ns, Environment.NewLine); + } + } + + if (config.UseNestedClasses) + { + _ = sw.AppendFormat(" {0} class {1}", config.InternalVisibility ? "internal" : "public", config.MainClass); + _ = sw.AppendLine(" {"); + } + } + + private static int CompareNamespacesSystemFirst(string x, string y) + { + if (x == "System") + return -1; + if (y == "System") + return 1; + + if (x.StartsWith("System.", StringComparison.Ordinal)) + { + if (y.StartsWith("System.", StringComparison.Ordinal)) + { + // Both start with "System." - so compare them normally. + return StringComparer.Ordinal.Compare(x, y); + } + else + { + // Only `x` starts with "System", so `x` should always come first (i.e. `x < y` or `y > x`). + return -1; + } + } + else + { + // Only `y` starts with "System", so `y` should always come first (i.e. `x > y` or `y < x`). + if (y.StartsWith("System.", StringComparison.Ordinal)) + { + return 1; + } + else + { + // Neither are "System." namespaces - so compare them normally. + return StringComparer.Ordinal.Compare(x, y); + } + } + } + + public void WriteFileEnd(IJsonClassGeneratorConfig config, StringBuilder sw) + { + if (config.UseNestedClasses) + { + _ = sw.AppendLine(" }"); + } + } + + public void WriteDeserializationComment(IJsonClassGeneratorConfig config, StringBuilder sw, bool rootIsArray = false) + { + string deserializer; + switch (config.AttributeLibrary) + { + case JsonLibrary.NewtonsoftJson: + deserializer = "JsonConvert.DeserializeObject"; + break; + case JsonLibrary.SystemTextJson: + deserializer = "JsonSerializer.Deserialize"; + break; + default: + return; + } + + string rootType = rootIsArray ? "List" : "Root"; + + _ = sw.AppendLine($"// Root myDeserializedClass = {deserializer}<{rootType}>(myJsonResponse);"); + } + + public void WriteNamespaceStart(IJsonClassGeneratorConfig config, StringBuilder sw, bool root) + { + _ = sw.AppendLine(); + _ = sw.AppendFormat("namespace {0}", root && !config.UseNestedClasses ? config.Namespace : (config.SecondaryNamespace ?? config.Namespace)); + _ = sw.AppendLine("{"); + _ = sw.AppendLine(); + } + + public void WriteNamespaceEnd(IJsonClassGeneratorConfig config, StringBuilder sw, bool root) => sw.AppendLine("}"); + + private static string GetTypeIndent(IJsonClassGeneratorConfig config, bool typeIsRoot) + { + if (config.UseNestedClasses) + { + if (typeIsRoot) + { + return " "; // 4x + } + else + { + return " "; // 8x + } + } + else + { + return " "; // 4x + } + } + + public void WriteClass(IJsonClassGeneratorConfig config, StringBuilder sw, JsonType type) + { + string indentTypes = GetTypeIndent(config, type.IsRoot); + string indentMembers = indentTypes + " "; + string indentBodies = indentMembers + " "; + + const string visibility = "public"; + + string className = type.AssignedName; + if (config.OutputType == OutputTypes.ImmutableRecord) + { + sw.AppendFormat(indentTypes + "{0} record {1}({2}", visibility, className, Environment.NewLine); + } + else + { + sw.AppendFormat(indentTypes + "{0} class {1}{2}", visibility, className, Environment.NewLine); + sw.AppendLine(indentTypes + "{"); + } + +#if CAN_SUPRESS + var shouldSuppressWarning = config.InternalVisibility && !config.UseProperties && !config.ExplicitDeserialization; + if (shouldSuppressWarning) + { + sw.AppendFormat("#pragma warning disable 0649"); + if (!config.UsePascalCase) sw.AppendLine(); + } + if (config.ExplicitDeserialization) + { + if (config.UseProperties) WriteClassWithPropertiesExplicitDeserialization(sw, type, prefix); + else WriteClassWithFieldsExplicitDeserialization(sw, type, prefix); + } + else +#endif + { + if (config.OutputType == OutputTypes.ImmutableClass) + { + WriteClassConstructor(config, sw, type, indentMembers: indentMembers, indentBodies: indentBodies); + } + + WriteClassMembers(config, sw, type, indentMembers); + } +#if CAN_SUPPRESS + if (shouldSuppressWarning) + { + sw.WriteLine(); + sw.WriteLine("#pragma warning restore 0649"); + sw.WriteLine(); + } +#endif + + if (config.OutputType == OutputTypes.ImmutableRecord) + { + sw.AppendLine(indentTypes + ");"); + } + else if ((!config.UseNestedClasses) || (config.UseNestedClasses && !type.IsRoot)) + { + sw.AppendLine(indentTypes + "}"); + } + + sw.AppendLine(); + } + + /// Converts an identifier from JSON into a C#-safe PascalCase identifier. + private string GetCSharpPascalCaseName(string name) + { + // Check if property is a reserved keyword + if (IsReservedKeyword(name)) + name = "@" + name; + + // Check if property name starts with number + if (!string.IsNullOrEmpty(name) && char.IsDigit(name[0])) + name = "_" + name; + + return name; + } + + /// Converts a camelCase identifier from JSON into a C#-safe camelCase identifier. + private string GetCSharpCamelCaseName(string camelCaseFromJson) + { + if (string.IsNullOrEmpty(camelCaseFromJson)) + throw new ArgumentException(message: "Value cannot be null or empty.", paramName: nameof(camelCaseFromJson)); + + string name = camelCaseFromJson; + + // + + if (name.Length >= 3) + { + if (char.IsUpper(name[0]) && char.IsUpper(name[1]) && char.IsLower(name[2])) + { + // "ABc" --> "abc" // this may be wrong in some cases, if the first two letters are a 2-letter acronym, like "IO". + name = name.Substring(startIndex: 0, length: 2).ToLowerInvariant() + name.Substring(startIndex: 2); + } + else if (char.IsUpper(name[0])) + { + // "Abc" --> "abc" + // "AbC" --> "abC" + name = char.ToLower(name[0]) + name.Substring(startIndex: 1); + } + } + else if (name.Length == 2) + { + if (char.IsUpper(name[0])) + { + // "AB" --> "ab" + // "Ab" --> "ab" + name = name.ToLowerInvariant(); + } + } + else // Implicit: name.Length == 1 + { + // "A" --> "a" + name = name.ToLowerInvariant(); + } + + if (!char.IsLetter(name[0])) + name = "_" + name; + else if (IsReservedKeyword(name)) + name = "@" + name; + + return name; + } + + public void WriteClassMembers(IJsonClassGeneratorConfig config, StringBuilder sw, JsonType type, string indentMembers) + { + bool first = true; + foreach (FieldInfo field in type.Fields) + { + string classPropertyName = GetCSharpPascalCaseName(field.MemberName); + string propertyAttribute = config.GetCSharpJsonAttributeCode(field); + + // If we are using record types and this is not the first iteration, add a comma and newline to the previous line + // this is required because every line except the last should end with a comma + if (config.OutputType == OutputTypes.ImmutableRecord && !first) + { + _ = sw.AppendLine(","); + } + + if (!first && ((propertyAttribute.Length > 0 && config.OutputType != OutputTypes.ImmutableRecord) || config.ExamplesInDocumentation)) + { + // If rendering examples/XML comments - or property attributes - then add a newline before the property for readability's sake (except if it's the first property in the class) + // For record types, we want all members to be next to each other, unless when using examples + _ = sw.AppendLine(); + } + + if (config.ExamplesInDocumentation) + { + _ = sw.AppendFormat(indentMembers + "/// "); + _ = sw.AppendFormat(indentMembers + "/// Examples: " + field.GetExamplesText()); + _ = sw.AppendFormat(indentMembers + "/// "); + _ = sw.AppendLine(); + } + + if (propertyAttribute.Length > 0) + { + _ = sw.Append(indentMembers); + _ = sw.Append(propertyAttribute); + + if (config.OutputType != OutputTypes.ImmutableRecord) + { + _ = sw.AppendLine(); + } + } + + // record types is not compatible with UseFields, so it comes first + if (config.OutputType == OutputTypes.ImmutableRecord) + { + // NOTE: not adding newlines here, that happens at the start of the loop. We need this so we can lazily add commas at the end. + if (field.Type.Type == JsonTypeEnum.Array) + { + // TODO: Respect config.CollectionType + _ = sw.AppendFormat(" IReadOnlyList<{0}> {1}", GetTypeName(field.Type.InternalType, config), classPropertyName); + } + else + { + _ = sw.AppendFormat(" {0} {1}", field.Type.GetTypeName(), classPropertyName); + } + } + else if (config.OutputType == OutputTypes.MutableClass) + { + if (config.MutableClasses.Members == OutputMembers.AsPublicFields) + { + // Render a field like `public int Foobar;`: + + bool useReadonlyModifier = config.OutputType == OutputTypes.ImmutableClass; + + _ = sw.AppendFormat(indentMembers + "public {0}{1} {2};{3}", useReadonlyModifier ? "readonly " : "", field.Type.GetTypeName(), classPropertyName, Environment.NewLine); + } + else if (config.MutableClasses.Members == OutputMembers.AsProperties) + { + string getterSetterPart = "{ get; set; }"; + + bool addCollectionPropertyInitializer = + config.MutableClasses.ReadOnlyCollectionProperties && + field.Type.IsCollectionType() && + config.CollectionType == OutputCollectionType.MutableList; + + if (addCollectionPropertyInitializer && field.Type.Type == JsonTypeEnum.Array) + { + getterSetterPart = "{ get; } = new " + field.Type.GetTypeName() + "();"; + } + + _ = sw.AppendFormat(indentMembers + "public {0} {1} {2}{3}", field.Type.GetTypeName(), classPropertyName, getterSetterPart, Environment.NewLine); + } + else + { + const string PATH = nameof(config) + "." + nameof(config.MutableClasses) + "." + nameof(config.MutableClasses.Members); + const string MSG_FMT = "Invalid " + nameof(OutputMembers) + " enum value for " + PATH + ": {0}"; + throw new InvalidOperationException(MSG_FMT); + } + } + else if (config.OutputType == OutputTypes.ImmutableClass) + { + if (field.Type.Type == JsonTypeEnum.Array) + { + // TODO: Respect config.CollectionType + _ = sw.AppendFormat(indentMembers + "public IReadOnlyList<{0}> {1} {{ get; }}{2}", GetTypeName(field.Type.InternalType, config), classPropertyName, Environment.NewLine); + } + else + { + _ = sw.AppendFormat(indentMembers + "public {0} {1} {{ get; }}{2}", field.Type.GetTypeName(), classPropertyName, Environment.NewLine); + } + } + else + { + throw new InvalidOperationException("Invalid " + nameof(OutputTypes) + " value: " + config.OutputType); + } + + first = false; + } + + // emit a final newline if we're dealing with record types + if (config.OutputType == OutputTypes.ImmutableRecord) + { + _ = sw.AppendLine(); + } + } + + private void WriteClassConstructor(IJsonClassGeneratorConfig config, StringBuilder sw, JsonType type, string indentMembers, string indentBodies) + { + // Write an empty constructor on a single-line: + if (type.Fields.Count == 0) + { + _ = sw.AppendFormat(indentMembers + "public {0}() {{}}{1}", type.AssignedName, Environment.NewLine); + return; + } + + // Constructor signature: + { + switch (config.AttributeLibrary) + { + case JsonLibrary.NewtonsoftJson: + case JsonLibrary.SystemTextJson: // Both libraries use the same attribute name: [JsonConstructor] + _ = sw.AppendLine(indentMembers + "[JsonConstructor]"); + break; + } + + _ = sw.AppendFormat(indentMembers + "public {0}({1}", type.AssignedName, Environment.NewLine); + + FieldInfo lastField = type.Fields[type.Fields.Count - 1]; + + foreach (FieldInfo field in type.Fields) + { + // Writes something like: `[JsonProperty("foobar")] string foobar,` + + string ctorParameterName = GetCSharpCamelCaseName(field.MemberName); + + bool isLast = ReferenceEquals(field, lastField); + string comma = isLast ? "" : ","; + + // + + _ = sw.Append(indentBodies); + + string attribute = config.GetCSharpJsonAttributeCode(field); + if (attribute.Length > 0) + { + _ = sw.Append(attribute); + _ = sw.Append(' '); + } + + // e.g. `String foobar,\r\n` + _ = sw.AppendFormat("{0} {1}{2}{3}", /*0:*/ field.Type.GetTypeName(), /*1:*/ ctorParameterName, /*2:*/ comma, /*3:*/ Environment.NewLine); + } + } + + _ = sw.AppendLine(indentMembers + ")"); + + // Constructor body: + _ = sw.AppendLine(indentMembers + "{"); + + foreach (FieldInfo field in type.Fields) + { + string ctorParameterName = GetCSharpCamelCaseName(field.MemberName); + string classPropertyName = GetCSharpPascalCaseName(field.MemberName); + + if (config.MutableClasses.ReadOnlyCollectionProperties || !string.IsNullOrEmpty(classPropertyName)) + _ = sw.AppendFormat(indentBodies + "{0} = {1};{2}", /*0:*/ classPropertyName, /*1:*/ ctorParameterName, /*2:*/ Environment.NewLine); + else + _ = sw.AppendFormat(indentBodies + "this.{0} = {1};{2}", /*0:*/ classPropertyName, /*1:*/ ctorParameterName, /*2:*/ Environment.NewLine); + } + + _ = sw.AppendLine(indentMembers + "}"); + _ = sw.AppendLine(); + } + +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/CodeWriters/DartCodeWriter.cs b/Json2CSharpCodeGenerator.Lib/CodeWriters/DartCodeWriter.cs new file mode 100644 index 0000000..f8b3bf6 --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/CodeWriters/DartCodeWriter.cs @@ -0,0 +1,209 @@ +using System.Text; + +namespace Json2CSharpCodeGenerator.Lib.CodeWriters; + +public class DartCodeWriter : ICodeBuilder +{ + public string FileExtension => throw new NotImplementedException(); + + public string DisplayName => throw new NotImplementedException(); + + public IReadOnlyCollection ReservedKeywords => throw new NotImplementedException(); + + public string GetTypeName(JsonType type, IJsonClassGeneratorConfig config) + { + return type.Type switch + { + //case JsonTypeEnum.Anything: return "object"; // Later, test to do + JsonTypeEnum.Array => GetCollectionTypeName(elementTypeName: GetTypeName(type.InternalType, config), config.CollectionType), + // case JsonTypeEnum.Dictionary: return "Dictionary"; + JsonTypeEnum.Boolean => "bool?", + JsonTypeEnum.Float => "double?", + JsonTypeEnum.Integer => "int?", + JsonTypeEnum.Long => "double?", + JsonTypeEnum.Date => "DateTime?", + //case JsonTypeEnum.NonConstrained: return "object"; // Later, test to do + //case JsonTypeEnum.NullableBoolean: return "bool?"; + //case JsonTypeEnum.NullableFloat: return "double?"; + //case JsonTypeEnum.NullableInteger: return "int?"; + //case JsonTypeEnum.NullableLong: return "long?"; + //case JsonTypeEnum.NullableDate: return "DateTime?"; + //case JsonTypeEnum.NullableSomething: return "object"; + JsonTypeEnum.Object => type.NewAssignedName + "?", + JsonTypeEnum.String => "String?", + _ => throw new NotSupportedException("Unsupported json type: " + type.Type), + }; + } + + public string GetTypeFunctionName(JsonType type, IJsonClassGeneratorConfig config) + { + return type.Type switch + { + JsonTypeEnum.Anything => "", + JsonTypeEnum.Array => "[" + type.InternalType.NewAssignedName + ".from_dict(y) for y in {0}]", + // case JsonTypeEnum.Dictionary: return "Dictionary"; + JsonTypeEnum.Boolean => "", + JsonTypeEnum.Float => "float({0})", + JsonTypeEnum.Integer => "int({0})", + JsonTypeEnum.Long => "float({0})", + //case JsonTypeEnum.Date: return "DateTime"; // Do Later + JsonTypeEnum.Date => "str({0})", + JsonTypeEnum.NonConstrained => "", + // case JsonTypeEnum.NullableBoolean: return "bool?"; + //case JsonTypeEnum.NullableFloat: return "double?"; + //case JsonTypeEnum.NullableInteger: return "int?"; + //case JsonTypeEnum.NullableLong: return "long?"; + //case JsonTypeEnum.NullableDate: return "DateTime?"; + //case JsonTypeEnum.NullableSomething: return "object"; + JsonTypeEnum.Object => type.NewAssignedName + ".from_dict({0})", + JsonTypeEnum.String => "str({0})", + _ => throw new NotSupportedException("Unsupported json type: " + type.Type), + }; + } + + public bool IsReservedKeyword(string word) => throw new NotImplementedException(); + + public void WriteClass(IJsonClassGeneratorConfig config, StringBuilder sw, JsonType type) + { + string className = type.AssignedName; + + _ = sw.AppendFormat("class {0} {{{1}", className, Environment.NewLine); + WriteClassMembers(config, sw, type, ""); + _ = sw.AppendLine("}"); + _ = sw.AppendLine(); + } + + public void WriteClassMembers(IJsonClassGeneratorConfig config, StringBuilder sw, JsonType type, string prefix) + { + StringBuilder fields = new(); + StringBuilder fromJsonMapping = new(); + StringBuilder toJsonMapping = new(); + + if (type.Fields.Any()) + { + _ = fields.Append('{'); + } + + foreach (FieldInfo field in type.Fields) + { + _ = field.MemberName; + string propertyAttribute = field.JsonMemberName.RemoveSpecialCharacters().ToCamelCase(); + string originalPropertyAttribute = field.JsonMemberName; + + _ = sw.AppendFormat(" {0} {1};{2}", field.Type.GetTypeName(), propertyAttribute, Environment.NewLine); + + _ = fields.Append("this." + propertyAttribute + ", "); + if (field.Type.Type == JsonTypeEnum.Array) + { + _ = fromJsonMapping.AppendLine(string.Format(" if (json['{0}'] != null) {{", originalPropertyAttribute)); + _ = fromJsonMapping.AppendLine(string.Format(" {0} = <{1}>[];", propertyAttribute, field.Type.InternalType.NewAssignedName)); + _ = fromJsonMapping.AppendLine(string.Format(" json['{0}'].forEach((v) {{", originalPropertyAttribute)); + _ = fromJsonMapping.AppendLine(string.Format(" {0}!.add({1}.fromJson(v));", propertyAttribute, field.Type.InternalType.NewAssignedName)); + _ = fromJsonMapping.AppendLine(" });"); + _ = fromJsonMapping.AppendLine(" }"); + + _ = toJsonMapping.AppendLine(string.Format(" data['{0}'] ={0} != null ? {1}!.map((v) => v?.toJson()).toList() : null;", originalPropertyAttribute, propertyAttribute)); + } + else if (field.Type.Type == JsonTypeEnum.Object) + { + _ = fromJsonMapping.AppendLine(string.Format(" {1} = json['{0}'] != null ? {2}.fromJson(json['{0}']) : null;", originalPropertyAttribute, propertyAttribute, field.Type.GetTypeName())); + _ = toJsonMapping.AppendLine(string.Format(" data['{0}'] = {1}!.toJson();", originalPropertyAttribute, propertyAttribute)); + } + else + { + _ = fromJsonMapping.AppendLine(string.Format(" {1} = json['{0}'];", originalPropertyAttribute, propertyAttribute)); + _ = toJsonMapping.AppendLine(string.Format(" data['{0}'] = {1};", originalPropertyAttribute, propertyAttribute)); + } + } + + if (type.Fields.Any()) + { + // Remove trailing comma + fields.Length--; + fields.Length--; + _ = fields.Append('}'); + } + + if (!config.RemoveConstructors) + { + // Create Class Constructor + _ = sw.AppendLine(); + _ = sw.AppendLine(string.Format(" {0}({1}); {2}", type.AssignedName, fields.ToString(), Environment.NewLine)); + } + + if (type.Fields.Any()) + { + if (!config.RemoveFromJson) + { + // Add From Json Function + _ = sw.AppendLine(string.Format(" {0}.fromJson(Map json) {{", type.AssignedName)); + _ = sw.Append(fromJsonMapping.ToString()); + _ = sw.AppendLine(" }"); + + _ = sw.AppendLine(); + } + + if (!config.RemoveToJson) + { + // Add To Json Function + _ = sw.AppendLine(" Map toJson() {"); + _ = sw.AppendLine(" final Map data = Map();"); + _ = sw.Append(toJsonMapping.ToString()); + _ = sw.AppendLine(" return data;"); + _ = sw.AppendLine(" }"); + } + + } + } + + public void WriteDeserializationComment(IJsonClassGeneratorConfig config, StringBuilder sw, bool rootIsArray = false) + { + return; + } + + public void WriteFileEnd(IJsonClassGeneratorConfig config, StringBuilder sw) + { + // sw.Insert(0, "import json" + Environment.NewLine); + // sw.Insert(0, "from dataclasses import dataclass" + Environment.NewLine); + // sw.Insert(0, "from typing import Any" + Environment.NewLine); + // if (sw.ToString().Contains("List")) + // sw.Insert(0, string.Format("from typing import List{0}", Environment.NewLine)); + + return; + } + + public void WriteFileStart(IJsonClassGeneratorConfig config, StringBuilder sw) + { + _ = sw.AppendLine("/* "); + _ = sw.AppendLine("// Example Usage"); + _ = sw.AppendLine("Map map = jsonDecode();"); + _ = sw.AppendLine("var myRootNode = Root.fromJson(map);"); + _ = sw.AppendLine("*/ "); + + return; + } + + public void WriteNamespaceEnd(IJsonClassGeneratorConfig config, StringBuilder sw, bool root) + { + + return; + } + + public void WriteNamespaceStart(IJsonClassGeneratorConfig config, StringBuilder sw, bool root) + { + return; + } + + private static string GetCollectionTypeName(string elementTypeName, OutputCollectionType type) + { + return type switch + { + //case OutputCollectionType.Array: return elementTypeName + "[]"; + OutputCollectionType.Array => elementTypeName + "[]", + OutputCollectionType.MutableList => "List<" + elementTypeName + ">?", + // case OutputCollectionType.IReadOnlyList: return "IReadOnlyList<" + elementTypeName + ">"; + // case OutputCollectionType.ImmutableArray: return "ImmutableArray<" + elementTypeName + ">"; + _ => throw new ArgumentOutOfRangeException(paramName: nameof(type), actualValue: type, message: "Invalid " + nameof(OutputCollectionType) + " enum value."), + }; + } +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/CodeWriters/JavaCodeWriter.cs b/Json2CSharpCodeGenerator.Lib/CodeWriters/JavaCodeWriter.cs new file mode 100644 index 0000000..3dad101 --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/CodeWriters/JavaCodeWriter.cs @@ -0,0 +1,176 @@ +using System.Text; + +namespace Json2CSharpCodeGenerator.Lib.CodeWriters; + +public class JavaCodeWriter : ICodeBuilder +{ + public string FileExtension => ".java"; + + public string DisplayName => "Java"; + + private static readonly HashSet _ReservedKeywords = new(comparer: StringComparer.Ordinal) { + // https://en.wikipedia.org/wiki/List_of_Java_keywords + // `Array.from( document.querySelectorAll('dl > dt > code') ).map( e => '"' + e.textContent + '"' ).sort().join( ", " )` + + "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "const", "continue", + "default", "do", "double", "else", "enum", "extends", "false", "final", "finally", "float", "for", "goto", "goto", + "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "non-sealed", "null", + "open", "opens", "package", "permits", "private", "protected", "provides", "public", "record", "return", + "sealed", "short", "static", "strictfp", "super", "switch", "synchronized", + "this", "throw", "throws", "to", "transient", "transitive", "true", "try", + "uses", "var", "void", "volatile", "while", "with", "yield" + }; + + IReadOnlyCollection ICodeBuilder.ReservedKeywords => _ReservedKeywords; + public bool IsReservedKeyword(string word) => _ReservedKeywords.Contains(word ?? string.Empty); + + public string GetTypeName(JsonType type, IJsonClassGeneratorConfig config) + { + return type.Type switch + { + JsonTypeEnum.Anything => "Object", + JsonTypeEnum.Array => GetCollectionTypeName(elementTypeName: GetTypeName(type.InternalType, config), config.CollectionType), + JsonTypeEnum.Dictionary => "HashMap", + JsonTypeEnum.Boolean => "boolean", + JsonTypeEnum.Float => "double", + JsonTypeEnum.Integer => "int", + JsonTypeEnum.NonConstrained => "Object", + JsonTypeEnum.NullableBoolean => "boolean", + JsonTypeEnum.NullableFloat => "double", + JsonTypeEnum.NullableInteger => "int", + JsonTypeEnum.NullableLong => "long", + JsonTypeEnum.NullableDate => "Date", + JsonTypeEnum.Long => "long", + JsonTypeEnum.Date => "Date", + JsonTypeEnum.NullableSomething => "Object", + JsonTypeEnum.Object => type.NewAssignedName, + JsonTypeEnum.String => "String", + _ => throw new NotSupportedException("Unsupported json type"), + }; + } + + private static string GetCollectionTypeName(string elementTypeName, OutputCollectionType type) + { + return type switch + { + OutputCollectionType.Array => elementTypeName + "[]", + OutputCollectionType.MutableList => "ArrayList<" + elementTypeName + ">", + OutputCollectionType.IReadOnlyList or OutputCollectionType.ImmutableArray => throw new NotSupportedException("Java does not have in-box interfaces for read-only and immutable collections."), + _ => throw new ArgumentOutOfRangeException(paramName: nameof(type), actualValue: type, message: "Invalid " + nameof(OutputCollectionType) + " enum value."), + }; + } + + public void WriteClass(IJsonClassGeneratorConfig config, StringBuilder sw, JsonType type) + { + string visibility = config.InternalVisibility ? "" : "public"; + + _ = sw.AppendFormat("{0} class {1}", visibility, type.AssignedName); + _ = sw.AppendLine("{"); + + string prefix = config.UseNestedClasses && !type.IsRoot ? "" : " "; + + WriteClassMembers(config, sw, type, prefix); + + if (config.UseNestedClasses && !type.IsRoot) + _ = sw.AppendLine(" }"); + + if (!config.UseNestedClasses) + _ = sw.AppendLine("}"); + + _ = sw.AppendLine(); + } + + public void WriteClassMembers(IJsonClassGeneratorConfig config, StringBuilder sw, JsonType type, string prefix) + { + foreach (FieldInfo field in type.Fields) + { + if (config.UsePascalCase || config.ExamplesInDocumentation) + _ = sw.AppendLine(); + + // Check if property name starts with number + string memberName = field.MemberName; + if (!string.IsNullOrEmpty(field.MemberName) && char.IsDigit(field.MemberName[0])) + memberName = "_" + memberName; + + if (IsReservedKeyword(memberName)) + memberName = "my" + memberName; + + if (config.MutableClasses.Members == OutputMembers.AsProperties) + { + _ = sw.AppendFormat(prefix + "@JsonProperty" + "(\"{0}\") {1}", field.JsonMemberName, Environment.NewLine); + _ = sw.AppendFormat(prefix + "public {0} get{1}() {{ \r\t\t return this.{2}; }} {3}", field.Type.GetTypeName(), ChangeFirstChar(memberName), ChangeFirstChar(memberName, false), Environment.NewLine); + _ = sw.AppendFormat(prefix + "public void set{1}({0} {2}) {{ \r\t\t this.{2} = {2}; }} {3}", field.Type.GetTypeName(), ChangeFirstChar(memberName), ChangeFirstChar(memberName, false), Environment.NewLine); + _ = sw.AppendFormat(prefix + "{0} {1};", field.Type.GetTypeName(), ChangeFirstChar(memberName, false)); + _ = sw.AppendLine(); + } + else if (config.MutableClasses.Members == OutputMembers.AsPublicFields) + { + memberName = ChangeFirstChar(memberName, false); + if (field.JsonMemberName != memberName) + { + _ = sw.AppendFormat(prefix + "@JsonProperty" + "(\"{0}\") {1}", field.JsonMemberName, Environment.NewLine); + } + _ = sw.AppendFormat(prefix + "public {0} {1};{2}", field.Type.GetTypeName(), memberName, Environment.NewLine); + } + else + { + throw new InvalidOperationException("Unsupported " + nameof(OutputMembers) + " value: " + config.MutableClasses.Members); + } + } + } + + private static string ChangeFirstChar(string value, bool toCaptial = true) + { + if (value == null) + throw new ArgumentNullException("value"); + if (value.Length == 0) + return value; + StringBuilder sb = new(); + + _ = sb.Append(toCaptial ? char.ToUpper(value[0]) : char.ToLower(value[0])); + _ = sb.Append(value.Substring(1)); + + return sb.ToString(); + } + + public void WriteFileStart(IJsonClassGeneratorConfig config, StringBuilder sw) + { + // foreach (var line in JsonClassGenerator.FileHeader) + // { + // sw.WriteLine("// " + line); + // } + } + + public void WriteFileEnd(IJsonClassGeneratorConfig config, StringBuilder sw) + { + if (config.UseNestedClasses) + { + _ = sw.AppendLine(" }"); + } + } + + public void WriteNamespaceStart(IJsonClassGeneratorConfig config, StringBuilder sw, bool root) + { + _ = sw.AppendLine(); + _ = sw.AppendFormat("package {0};", root && !config.UseNestedClasses ? config.Namespace : (config.SecondaryNamespace ?? config.Namespace)); + _ = sw.AppendLine(); + } + + public void WriteNamespaceEnd(IJsonClassGeneratorConfig config, StringBuilder sw, bool root) => sw.AppendLine("}"); + + public void WriteDeserializationComment(IJsonClassGeneratorConfig config, StringBuilder sw, bool rootIsArray = false) + { + _ = sw.AppendLine("// import com.fasterxml.jackson.databind.ObjectMapper; // version 2.11.1"); + _ = sw.AppendLine("// import com.fasterxml.jackson.annotation.JsonProperty; // version 2.11.1"); + _ = sw.AppendLine("/* ObjectMapper om = new ObjectMapper();"); + + if (rootIsArray) + { + _ = sw.AppendLine("Root[] root = om.readValue(myJsonString, Root[].class); */"); + } + else + { + _ = sw.AppendLine("Root root = om.readValue(myJsonString, Root.class); */"); + } + } +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/CodeWriters/PythonCodeWriter.cs b/Json2CSharpCodeGenerator.Lib/CodeWriters/PythonCodeWriter.cs new file mode 100644 index 0000000..f06ffea --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/CodeWriters/PythonCodeWriter.cs @@ -0,0 +1,155 @@ +using System.Text; + +namespace Json2CSharpCodeGenerator.Lib.CodeWriters; + +public class PythonCodeWriter : ICodeBuilder +{ + public string FileExtension => throw new NotImplementedException(); + + public string DisplayName => throw new NotImplementedException(); + + public IReadOnlyCollection ReservedKeywords => throw new NotImplementedException(); + + public string GetTypeName(JsonType type, IJsonClassGeneratorConfig config) + { + return type.Type switch + { + JsonTypeEnum.Anything => "object", + JsonTypeEnum.Array => GetCollectionTypeName(elementTypeName: GetTypeName(type.InternalType, config), config.CollectionType), + // case JsonTypeEnum.Dictionary: return "Dictionary"; + JsonTypeEnum.Boolean => "bool", + JsonTypeEnum.Float => "float", + JsonTypeEnum.Integer => "int", + JsonTypeEnum.Long => "float", + //case JsonTypeEnum.Date: return "DateTime"; // Do Later + JsonTypeEnum.Date => "str", + JsonTypeEnum.NonConstrained => "object", + // case JsonTypeEnum.NullableBoolean: return "bool?"; + //case JsonTypeEnum.NullableFloat: return "double?"; + //case JsonTypeEnum.NullableInteger: return "int?"; + //case JsonTypeEnum.NullableLong: return "long?"; + //case JsonTypeEnum.NullableDate: return "DateTime?"; + //case JsonTypeEnum.NullableSomething: return "object"; + JsonTypeEnum.Object => type.NewAssignedName, + JsonTypeEnum.String => "str", + _ => throw new NotSupportedException("Unsupported json type: " + type.Type), + }; + } + + public string GetTypeFunctionName(JsonType type, IJsonClassGeneratorConfig config) + { + return type.Type switch + { + JsonTypeEnum.Anything => "", + JsonTypeEnum.Array => "[" + type.InternalType.NewAssignedName + ".from_dict(y) for y in {0}]", + // case JsonTypeEnum.Dictionary: return "Dictionary"; + JsonTypeEnum.Boolean => "", + JsonTypeEnum.Float => "float({0})", + JsonTypeEnum.Integer => "int({0})", + JsonTypeEnum.Long => "float({0})", + //case JsonTypeEnum.Date: return "DateTime"; // Do Later + JsonTypeEnum.Date => "str({0})", + JsonTypeEnum.NonConstrained => "", + // case JsonTypeEnum.NullableBoolean: return "bool?"; + //case JsonTypeEnum.NullableFloat: return "double?"; + //case JsonTypeEnum.NullableInteger: return "int?"; + //case JsonTypeEnum.NullableLong: return "long?"; + //case JsonTypeEnum.NullableDate: return "DateTime?"; + //case JsonTypeEnum.NullableSomething: return "object"; + JsonTypeEnum.Object => type.NewAssignedName + ".from_dict({0})", + JsonTypeEnum.String => "str({0})", + _ => throw new NotSupportedException("Unsupported json type: " + type.Type), + }; + } + + public bool IsReservedKeyword(string word) => throw new NotImplementedException(); + + public void WriteClass(IJsonClassGeneratorConfig config, StringBuilder sw, JsonType type) + { + string className = type.AssignedName; + + _ = sw.AppendLine("@dataclass"); + _ = sw.AppendFormat("class {0}:{1}", className, Environment.NewLine); + WriteClassMembers(config, sw, type, ""); + _ = sw.AppendLine(); + } + + public void WriteClassMembers(IJsonClassGeneratorConfig config, StringBuilder sw, JsonType type, string prefix) + { + StringBuilder fields = new(); + StringBuilder mappingFunction = new(); + foreach (FieldInfo field in type.Fields) + { + _ = field.MemberName; + string propertyAttribute = field.JsonMemberName; + string internalPropertyAttribute = "_" + propertyAttribute; + _ = sw.AppendFormat(" {0}: {1}{2}", propertyAttribute, field.Type.GetTypeName(), Environment.NewLine); + + string mappingFragment = string.Format("obj.get(\"{0}\")", propertyAttribute); + string mappingFragment2 = string.Format(GetTypeFunctionName(field.Type, config), mappingFragment); + string mappingString = string.Format(" {0} = {1}", internalPropertyAttribute, mappingFragment2); + _ = mappingFunction.AppendLine(mappingString); + _ = fields.Append(internalPropertyAttribute + ", "); + } + + // Remove trailing comma + fields.Length--; + fields.Length--; + + // Write Dictionnary Mapping Functions + _ = sw.AppendLine(); + _ = sw.AppendLine(" @staticmethod"); + _ = sw.AppendLine(string.Format(" def from_dict(obj: Any) -> '{0}':", type.AssignedName)); + _ = sw.Append(mappingFunction.ToString()); + _ = sw.AppendLine(string.Format(" return {0}({1})", type.AssignedName, fields.ToString())); + } + + public void WriteDeserializationComment(IJsonClassGeneratorConfig config, StringBuilder sw, bool rootIsArray = false) + { + return; + } + + public void WriteFileEnd(IJsonClassGeneratorConfig config, StringBuilder sw) + { + _ = sw.Insert(0, "9 json" + Environment.NewLine); + _ = sw.Insert(0, "from dataclasses import dataclass" + Environment.NewLine); + _ = sw.Insert(0, "from typing import Any" + Environment.NewLine); + if (sw.ToString().Contains("List")) + _ = sw.Insert(0, string.Format("from typing import List{0}", Environment.NewLine)); + + _ = sw.AppendLine("# Example Usage"); + _ = sw.AppendLine("# jsonstring = json.loads(myjsonstring)"); + _ = sw.AppendLine("# root = Root.from_dict(jsonstring)"); + return; + } + + public void WriteFileStart(IJsonClassGeneratorConfig config, StringBuilder sw) + { + + return; + } + + public void WriteNamespaceEnd(IJsonClassGeneratorConfig config, StringBuilder sw, bool root) + { + + return; + } + + public void WriteNamespaceStart(IJsonClassGeneratorConfig config, StringBuilder sw, bool root) + { + return; + } + + private static string GetCollectionTypeName(string elementTypeName, OutputCollectionType type) + { + return type switch + { + //case OutputCollectionType.Array: return elementTypeName + "[]"; + OutputCollectionType.Array => "List[" + elementTypeName + "]", + OutputCollectionType.MutableList => "List[" + elementTypeName + "]", + // case OutputCollectionType.IReadOnlyList: return "IReadOnlyList<" + elementTypeName + ">"; + // case OutputCollectionType.ImmutableArray: return "ImmutableArray<" + elementTypeName + ">"; + _ => throw new ArgumentOutOfRangeException(paramName: nameof(type), actualValue: type, message: "Invalid " + nameof(OutputCollectionType) + " enum value."), + }; + } +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/CodeWriters/TypeScriptCodeWriter.cs b/Json2CSharpCodeGenerator.Lib/CodeWriters/TypeScriptCodeWriter.cs new file mode 100644 index 0000000..16ab141 --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/CodeWriters/TypeScriptCodeWriter.cs @@ -0,0 +1,97 @@ +namespace Json2CSharpCodeGenerator.Lib.CodeWriters; + +public class TypeScriptCodeWriter : ICodeWriter +{ + public string FileExtension => ".ts"; + + public string DisplayName => "TypeScript"; + + public string GetTypeName(JsonType type, IJsonClassGeneratorConfig config) + { + return type.Type switch + { + JsonTypeEnum.Anything => "any", + JsonTypeEnum.String => "string", + JsonTypeEnum.Boolean => "bool", + JsonTypeEnum.Integer or JsonTypeEnum.Long or JsonTypeEnum.Float => "number", + JsonTypeEnum.Date => "Date", + JsonTypeEnum.NullableInteger or JsonTypeEnum.NullableLong or JsonTypeEnum.NullableFloat => "number", + JsonTypeEnum.NullableBoolean => "bool", + JsonTypeEnum.NullableDate => "Date", + JsonTypeEnum.Object => type.AssignedName, + JsonTypeEnum.Array => GetTypeName(type.InternalType, config) + "[]", + JsonTypeEnum.Dictionary => "{ [key: string]: " + GetTypeName(type.InternalType, config) + "; }", + JsonTypeEnum.NullableSomething => "any", + JsonTypeEnum.NonConstrained => "any", + _ => throw new NotSupportedException("Unsupported type"), + }; + } + + public void WriteClass(IJsonClassGeneratorConfig config, TextWriter sw, JsonType type) + { + string prefix = GetNamespace(config, type.IsRoot) != null ? " " : ""; + bool exported = !config.InternalVisibility || config.SecondaryNamespace != null; + sw.WriteLine(prefix + (exported ? "export " : string.Empty) + "interface " + type.AssignedName + " {"); + foreach (FieldInfo field in type.Fields) + { + bool shouldDefineNamespace = type.IsRoot && config.SecondaryNamespace != null && config.Namespace != null && (field.Type.Type == JsonTypeEnum.Object || (field.Type.InternalType != null && field.Type.InternalType.Type == JsonTypeEnum.Object)); + if (config.ExamplesInDocumentation) + { + sw.WriteLine(); + sw.WriteLine(prefix + " /**"); + sw.WriteLine(prefix + " * Examples: " + field.GetExamplesText()); + sw.WriteLine(prefix + " */"); + } + + sw.WriteLine(prefix + " " + field.JsonMemberName + (IsNullable(field.Type.Type) ? "?" : "") + ": " + (shouldDefineNamespace ? config.SecondaryNamespace + "." : string.Empty) + GetTypeName(field.Type, config) + ";"); + } + sw.WriteLine(prefix + "}"); + sw.WriteLine(); + } + + private bool IsNullable(JsonTypeEnum type) + { + return + type is JsonTypeEnum.NullableBoolean or + JsonTypeEnum.NullableDate or + JsonTypeEnum.NullableFloat or + JsonTypeEnum.NullableInteger or + JsonTypeEnum.NullableLong or + JsonTypeEnum.NullableSomething; + } + + public void WriteFileStart(IJsonClassGeneratorConfig config, TextWriter sw) + { + // foreach (var line in JsonClassGenerator.FileHeader) + // { + // sw.WriteLine("// " + line); + // } + // sw.WriteLine(); + } + + public void WriteFileEnd(IJsonClassGeneratorConfig config, TextWriter sw) + { + } + + private string GetNamespace(IJsonClassGeneratorConfig config, bool root) => root ? config.Namespace : (config.SecondaryNamespace ?? config.Namespace); + + public void WriteNamespaceStart(IJsonClassGeneratorConfig config, TextWriter sw, bool root) + { + if (GetNamespace(config, root) != null) + { + + sw.WriteLine("module " + GetNamespace(config, root) + " {"); + sw.WriteLine(); + } + } + + public void WriteNamespaceEnd(IJsonClassGeneratorConfig config, TextWriter sw, bool root) + { + if (GetNamespace(config, root) != null) + { + sw.WriteLine("}"); + sw.WriteLine(); + } + } + +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/CodeWriters/VisualBasicCodeWriter.cs b/Json2CSharpCodeGenerator.Lib/CodeWriters/VisualBasicCodeWriter.cs new file mode 100644 index 0000000..13f4158 --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/CodeWriters/VisualBasicCodeWriter.cs @@ -0,0 +1,161 @@ +namespace Json2CSharpCodeGenerator.Lib.CodeWriters; + +public class VisualBasicCodeWriter : ICodeWriter +{ + public string FileExtension => ".vb"; + + public string DisplayName => "Visual Basic .NET"; + + private const string _NoRenameAttribute = ""; + private const string _NoPruneAttribute = ""; + + public string GetTypeName(JsonType type, IJsonClassGeneratorConfig config) + { + return type.Type switch + { + JsonTypeEnum.Anything => "Object", + JsonTypeEnum.Array => GetCollectionTypeName(GetTypeName(type.InternalType, config), config.CollectionType), + JsonTypeEnum.Dictionary => "Dictionary(Of String, " + GetTypeName(type.InternalType, config) + ")", + JsonTypeEnum.Boolean => "Boolean", + JsonTypeEnum.Float => "Double", + JsonTypeEnum.Integer => "Integer", + JsonTypeEnum.Long => "Long", + JsonTypeEnum.Date => "DateTime", + JsonTypeEnum.NonConstrained => "Object", + JsonTypeEnum.NullableBoolean => "Boolean?", + JsonTypeEnum.NullableFloat => "Double?", + JsonTypeEnum.NullableInteger => "Integer?", + JsonTypeEnum.NullableLong => "Long?", + JsonTypeEnum.NullableDate => "DateTime?", + JsonTypeEnum.NullableSomething => "Object", + JsonTypeEnum.Object => type.AssignedName, + JsonTypeEnum.String => "String", + _ => throw new NotSupportedException("Unsupported json type"), + }; + } + + private static string GetCollectionTypeName(string elementTypeName, OutputCollectionType type) + { + return type switch + { + OutputCollectionType.Array => elementTypeName + "()", + OutputCollectionType.MutableList => "List(Of" + elementTypeName + ")", + OutputCollectionType.IReadOnlyList => "IReadOnlyList(Of" + elementTypeName + ")", + OutputCollectionType.ImmutableArray => "ImmutableArray(Of" + elementTypeName + ")", + _ => throw new ArgumentOutOfRangeException(paramName: nameof(type), actualValue: type, message: "Invalid " + nameof(OutputCollectionType) + " enum value."), + }; + } + + private bool ShouldApplyNoRenamingAttribute(IJsonClassGeneratorConfig config) => config.ApplyObfuscationAttributes && !config.UsePascalCase; + + private bool ShouldApplyNoPruneAttribute(IJsonClassGeneratorConfig config) => config.ApplyObfuscationAttributes && config.OutputType == OutputTypes.MutableClass && config.MutableClasses.Members == OutputMembers.AsPublicFields; + + public void WriteClass(IJsonClassGeneratorConfig config, TextWriter sw, JsonType type) + { + string visibility = config.InternalVisibility ? "Friend" : "Public"; + + if (config.UseNestedClasses) + { + sw.WriteLine(" {0} Partial Class {1}", visibility, config.MainClass); + if (!type.IsRoot) + { + if (ShouldApplyNoRenamingAttribute(config)) + sw.WriteLine(" " + _NoRenameAttribute); + if (ShouldApplyNoPruneAttribute(config)) + sw.WriteLine(" " + _NoPruneAttribute); + sw.WriteLine(" {0} Class {1}", visibility, type.AssignedName); + } + } + else + { + if (ShouldApplyNoRenamingAttribute(config)) + sw.WriteLine(" " + _NoRenameAttribute); + if (ShouldApplyNoPruneAttribute(config)) + sw.WriteLine(" " + _NoPruneAttribute); + sw.WriteLine(" {0} Class {1}", visibility, type.AssignedName); + } + + string prefix = config.UseNestedClasses && !type.IsRoot ? " " : " "; + + WriteClassMembers(config, sw, type, prefix); + + if (config.UseNestedClasses && !type.IsRoot) + sw.WriteLine(" End Class"); + + sw.WriteLine(" End Class"); + sw.WriteLine(); + + } + + private void WriteClassMembers(IJsonClassGeneratorConfig config, TextWriter sw, JsonType type, string prefix) + { + + foreach (FieldInfo field in type.Fields) + { + if (config.UsePascalCase || config.ExamplesInDocumentation) + sw.WriteLine(); + + if (config.ExamplesInDocumentation) + { + sw.WriteLine(prefix + "''' "); + sw.WriteLine(prefix + "''' Examples: " + field.GetExamplesText()); + sw.WriteLine(prefix + "''' "); + } + + if (config.UsePascalCase) + { + sw.WriteLine(prefix + "", field.JsonMemberName); + } + + if (config.MutableClasses.Members == OutputMembers.AsProperties) + { + sw.WriteLine(prefix + "Public Property {1} As {0}", field.Type.GetTypeName(), field.MemberName); + } + else + { + sw.WriteLine(prefix + "Public {1} As {0}", field.Type.GetTypeName(), field.MemberName); + } + } + + } + + public void WriteFileStart(IJsonClassGeneratorConfig config, TextWriter sw) + { + sw.WriteLine(); + sw.WriteLine("Imports System"); + sw.WriteLine("Imports System.Collections.Generic"); + + if (ShouldApplyNoRenamingAttribute(config) || ShouldApplyNoPruneAttribute(config)) + { + sw.WriteLine("Imports System.Reflection"); + } + + if (config.AttributeLibrary == JsonLibrary.NewtonsoftJson) + { + sw.WriteLine("Imports Newtonsoft.Json"); + sw.WriteLine("Imports Newtonsoft.Json.Linq"); + } + else if (config.AttributeLibrary == JsonLibrary.SystemTextJson) + { + sw.WriteLine("Imports System.Text.Json"); + } + + if (!string.IsNullOrWhiteSpace(config.SecondaryNamespace) && !config.UseNestedClasses) + { + sw.WriteLine("Imports {0}", config.SecondaryNamespace); + } + } + + public void WriteFileEnd(IJsonClassGeneratorConfig config, TextWriter sw) + { + } + + public void WriteNamespaceStart(IJsonClassGeneratorConfig config, TextWriter sw, bool root) + { + sw.WriteLine(); + sw.WriteLine("Namespace Global.{0}", root && !config.UseNestedClasses ? config.Namespace : (config.SecondaryNamespace ?? config.Namespace)); + sw.WriteLine(); + } + + public void WriteNamespaceEnd(IJsonClassGeneratorConfig config, TextWriter sw, bool root) => sw.WriteLine("End Namespace"); +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/CodeWriters/php.txt b/Json2CSharpCodeGenerator.Lib/CodeWriters/php.txt new file mode 100644 index 0000000..4940e03 --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/CodeWriters/php.txt @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Xamasoft.JsonClassGenerator.CodeWriters +{ + public class PhpCodeWriter : ICodeWriter + { + public string FileExtension + { + get { return ".php"; } + } + + public string DisplayName + { + get { return "PHP"; } + } + + public string GetTypeName(JsonType type, IJsonClassGeneratorConfig config) + { + var arraysAsLists = !config.ExplicitDeserialization; + + switch (type.Type) + { + case JsonTypeEnum.Anything: return "Object"; + case JsonTypeEnum.Array: return "array(" + GetTypeName(type.InternalType, config) + ")"; + case JsonTypeEnum.Boolean: return "boolean"; + case JsonTypeEnum.Float: return "double"; + case JsonTypeEnum.Integer: return "int"; + case JsonTypeEnum.Long: return "long"; + case JsonTypeEnum.Date: return "Date"; + case JsonTypeEnum.NonConstrained: return "Object"; + case JsonTypeEnum.NullableBoolean: return "bool?"; + case JsonTypeEnum.NullableFloat: return "double?"; + case JsonTypeEnum.NullableInteger: return "int?"; + case JsonTypeEnum.NullableLong: return "long?"; + case JsonTypeEnum.NullableDate: return "DateTime?"; + case JsonTypeEnum.NullableSomething: return "object"; + case JsonTypeEnum.Object: return type.AssignedName; + case JsonTypeEnum.String: return "String"; + default: throw new System.NotSupportedException("Unsupported json type"); + } + } + + public void WriteClass(IJsonClassGeneratorConfig config, TextWriter sw, JsonType type) + { + //var visibility = config.InternalVisibility ? "" : "public"; + + //if (config.UseNestedClasses) + //{ + // if (!type.IsRoot) + // { + // if (config.PropertyAttribute == "DataMember") + // { + // sw.WriteLine(" [DataContract]"); + // } + + // if (ShouldApplyNoRenamingAttribute(config)) sw.WriteLine(" " + NoRenameAttribute); + // if (ShouldApplyNoPruneAttribute(config)) sw.WriteLine(" " + NoPruneAttribute); + // sw.WriteLine(" {0} class {1}", visibility, type.AssignedName); + // sw.WriteLine(" {"); + // } + //} + //else + //{ + // if (config.PropertyAttribute == "DataMember") + // { + // sw.WriteLine(" [DataContract]"); + // } + + // if (ShouldApplyNoRenamingAttribute(config)) sw.WriteLine(" " + NoRenameAttribute); + // if (ShouldApplyNoPruneAttribute(config)) sw.WriteLine(" " + NoPruneAttribute); + sw.WriteLine("class {0}", type.AssignedName); + sw.WriteLine("{"); + //} + + var prefix = config.UseNestedClasses && !type.IsRoot ? "" : " "; + + + var shouldSuppressWarning = config.InternalVisibility && !config.UseProperties && !config.ExplicitDeserialization; + if (shouldSuppressWarning) + { + sw.WriteLine("#pragma warning disable 0649"); + if (!config.UsePascalCase) sw.WriteLine(); + } + + //if (type.IsRoot && config.ExplicitDeserialization) WriteStringConstructorExplicitDeserialization(config, sw, type, prefix); + + //if (config.ExplicitDeserialization) + //{ + // if (config.UseProperties) WriteClassWithPropertiesExplicitDeserialization(sw, type, prefix); + // else WriteClassWithFieldsExplicitDeserialization(sw, type, prefix); + //} + //else + //{ + WriteClassMembers(config, sw, type, prefix); + //} + + if (shouldSuppressWarning) + { + sw.WriteLine(); + sw.WriteLine("#pragma warning restore 0649"); + sw.WriteLine(); + } + + + if (config.UseNestedClasses && !type.IsRoot) + sw.WriteLine(" }"); + + if (!config.UseNestedClasses) + sw.WriteLine("}"); + + sw.WriteLine(); + } + + public void WriteFileStart(IJsonClassGeneratorConfig config, TextWriter sw) + { + throw new NotImplementedException(); + } + + public void WriteFileEnd(IJsonClassGeneratorConfig config, TextWriter sw) + { + throw new NotImplementedException(); + } + + public void WriteNamespaceStart(IJsonClassGeneratorConfig config, TextWriter sw, bool root) + { + sw.WriteLine(""); + } + + private void WriteClassMembers(IJsonClassGeneratorConfig config, TextWriter sw, JsonType type, string prefix) + { + foreach (var field in type.Fields) + { + //if (config.UsePascalCase || config.ExamplesInDocumentation) sw.WriteLine(); + + //if (config.ExamplesInDocumentation) + //{ + // sw.WriteLine(prefix + "/// "); + // sw.WriteLine(prefix + "/// Examples: " + field.GetExamplesText()); + // sw.WriteLine(prefix + "/// "); + //} + + //if (config.UsePascalCase || config.PropertyAttribute != "None") + //{ + // if (config.UsePascalCase && config.PropertyAttribute == "None") + // sw.WriteLine(prefix + "@JsonProperty(\"{0}\")", field.JsonMemberName); + // else + // { + // //if (config.PropertyAttribute == "DataMember") + // // sw.WriteLine(prefix + "[" + config.PropertyAttribute + "(Name=\"{0}\")]", field.JsonMemberName); + // if (config.PropertyAttribute == "JsonProperty") + // sw.WriteLine(prefix + "@" + config.PropertyAttribute + "(\"{0}\")", field.JsonMemberName); + // } + //} + + if (config.UseProperties) + { + //sw.WriteLine(prefix + "@JsonProperty" + "(\"{0}\")", field.JsonMemberName); + sw.WriteLine(prefix + "public function get{0}() {{ \r\t\t return $this->{1} \r\t}}", ChangeFirstChar(field.MemberName), field.MemberName); + sw.WriteLine(prefix + "public function set{0}(${1}) {{ \r\t\t $this->{1} = ${1} \r\t}}", ChangeFirstChar(field.MemberName), field.MemberName); + sw.WriteLine(prefix + "public ${1}; //{0}", field.Type.GetTypeName(), field.MemberName); + sw.WriteLine(); + } + else + { + sw.WriteLine(prefix + "public ${1}; //{0}", field.Type.GetTypeName(), field.MemberName); + } + } + + } + + private static string ChangeFirstChar(string value, bool toCaptial = true) + { + if (value == null) + throw new ArgumentNullException("value"); + if (value.Length == 0) + return value; + StringBuilder sb = new StringBuilder(); + + sb.Append(toCaptial ? char.ToUpper(value[0]) : char.ToLower(value[0])); + sb.Append(value.Substring(1)); + + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/CodeWriters/sql.txt b/Json2CSharpCodeGenerator.Lib/CodeWriters/sql.txt new file mode 100644 index 0000000..9093b86 --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/CodeWriters/sql.txt @@ -0,0 +1,98 @@ +using System.IO; + +namespace Xamasoft.JsonClassGenerator.CodeWriters +{ + public class SqlCodeWriter : ICodeWriter + { + public string FileExtension + { + get { return ".cs"; } + } + + public string DisplayName + { + get { return "SQL"; } + } + + public string GetTypeName(JsonType type, IJsonClassGeneratorConfig config) + { + var arraysAsLists = !config.ExplicitDeserialization; + + switch (type.Type) + { + case JsonTypeEnum.Anything: return "object"; + case JsonTypeEnum.Array: return arraysAsLists ? "IList<" + GetTypeName(type.InternalType, config) + ">" : GetTypeName(type.InternalType, config) + "[]"; + case JsonTypeEnum.Dictionary: return "Dictionary"; + case JsonTypeEnum.Boolean: return "bit NOT NULL"; + case JsonTypeEnum.Float: return "[decimal](9,2) NOT NULL"; + case JsonTypeEnum.Integer: return "[int] NOT NULL"; + case JsonTypeEnum.Long: return "[bigint] NOT NULL"; + case JsonTypeEnum.Date: return "[datetime]"; + case JsonTypeEnum.NonConstrained: return "object"; + case JsonTypeEnum.NullableBoolean: return "bit NULL"; + case JsonTypeEnum.NullableFloat: return "[decimal](9,2) NULL"; + case JsonTypeEnum.NullableInteger: return "[int] NULL"; + case JsonTypeEnum.NullableLong: return "[bigint] NULL"; + case JsonTypeEnum.NullableDate: return "[datetime] NULL"; + case JsonTypeEnum.NullableSomething: return "object NULL"; + case JsonTypeEnum.Object: return type.AssignedName; + case JsonTypeEnum.String: return "[varchar](50) NULL"; + default: throw new System.NotSupportedException("Unsupported json type"); + } + } + + public void WriteFileStart(IJsonClassGeneratorConfig config, TextWriter sw) + { + + } + + public void WriteFileEnd(IJsonClassGeneratorConfig config, TextWriter sw) + { + if (config.UseNestedClasses) + { + sw.WriteLine(" }"); + } + } + + public void WriteNamespaceStart(IJsonClassGeneratorConfig config, TextWriter sw, bool root) + { + + } + + public void WriteNamespaceEnd(IJsonClassGeneratorConfig config, TextWriter sw, bool root) + { + //sw.WriteLine("}"); + } + + public void WriteClass(IJsonClassGeneratorConfig config, TextWriter sw, JsonType type) + { + sw.WriteLine("create table " + type.AssignedName + " ("); + sw.WriteLine(" [Id] [int] IDENTITY(1,1) NOT NULL,"); + + WriteClassMembers(config, sw, type); + + sw.WriteLine("CONSTRAINT [PK_" + type.AssignedName + "] PRIMARY KEY CLUSTERED"); + sw.WriteLine(" ("); + sw.WriteLine(" [Id] asc"); + sw.WriteLine(" )"); + sw.WriteLine(")"); + + sw.WriteLine(); + } + + private void WriteClassMembers(IJsonClassGeneratorConfig config, TextWriter sw, JsonType type) + { + foreach (var field in type.Fields) + { + if (config.UseProperties) + { + string typeName = field.Type.InternalType == null + ? field.Type.GetTypeName() + : field.Type.InternalType.GetTypeName(); + + sw.WriteLine(" [{0}] {1},", field.MemberName, typeName); + } + } + } + } +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/CodeWriters/typescript.txt b/Json2CSharpCodeGenerator.Lib/CodeWriters/typescript.txt new file mode 100644 index 0000000..99839eb --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/CodeWriters/typescript.txt @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Xamasoft.JsonClassGenerator.CodeWriters +{ + public class TypeScriptCodeWriter : ICodeWriter + { + public string FileExtension + { + get { return ".ts"; } + } + + public string DisplayName + { + get { return "TypeScript"; } + } + + public string GetTypeName(JsonType type, IJsonClassGeneratorConfig config) + { + switch (type.Type) + { + case JsonTypeEnum.Anything: return "any"; + case JsonTypeEnum.String: return "string"; + case JsonTypeEnum.Boolean: return "bool"; + case JsonTypeEnum.Integer: + case JsonTypeEnum.Long: + case JsonTypeEnum.Float: return "number"; + case JsonTypeEnum.Date: return "Date"; + case JsonTypeEnum.NullableInteger: + case JsonTypeEnum.NullableLong: + case JsonTypeEnum.NullableFloat: return "number"; + case JsonTypeEnum.NullableBoolean: return "bool"; + case JsonTypeEnum.NullableDate: return "Date"; + case JsonTypeEnum.Object: return type.AssignedName; + case JsonTypeEnum.Array: return GetTypeName(type.InternalType, config) + "[]"; + case JsonTypeEnum.Dictionary: return "{ [key: string]: " + GetTypeName(type.InternalType, config) + "; }"; + case JsonTypeEnum.NullableSomething: return "any"; + case JsonTypeEnum.NonConstrained: return "any"; + default: throw new NotSupportedException("Unsupported type"); + } + } + + public void WriteClass(IJsonClassGeneratorConfig config, TextWriter sw, JsonType type) + { + var prefix = GetNamespace(config, type.IsRoot) != null ? " " : ""; + var exported = !config.InternalVisibility || config.SecondaryNamespace != null; + sw.WriteLine(prefix + (exported ? "export " : string.Empty) + "interface " + type.AssignedName + " {"); + foreach (var field in type.Fields) + { + var shouldDefineNamespace = type.IsRoot && config.SecondaryNamespace != null && config.Namespace != null && (field.Type.Type == JsonTypeEnum.Object || (field.Type.InternalType != null && field.Type.InternalType.Type == JsonTypeEnum.Object)); + if (config.ExamplesInDocumentation) + { + sw.WriteLine(); + sw.WriteLine(prefix + " /**"); + sw.WriteLine(prefix + " * Examples: " + field.GetExamplesText()); + sw.WriteLine(prefix + " */"); + } + + + sw.WriteLine(prefix + " " + field.JsonMemberName + (IsNullable(field.Type.Type) ? "?" : "") + ": " + (shouldDefineNamespace ? config.SecondaryNamespace + "." : string.Empty) + GetTypeName(field.Type, config) + ";"); + } + sw.WriteLine(prefix + "}"); + sw.WriteLine(); + } + + private bool IsNullable(JsonTypeEnum type) + { + return + type == JsonTypeEnum.NullableBoolean || + type == JsonTypeEnum.NullableDate || + type == JsonTypeEnum.NullableFloat || + type == JsonTypeEnum.NullableInteger || + type == JsonTypeEnum.NullableLong || + type == JsonTypeEnum.NullableSomething; + } + + public void WriteFileStart(IJsonClassGeneratorConfig config, TextWriter sw) + { + foreach (var line in JsonClassGenerator.FileHeader) + { + sw.WriteLine("// " + line); + } + sw.WriteLine(); + } + + public void WriteFileEnd(IJsonClassGeneratorConfig config, TextWriter sw) + { + } + + private string GetNamespace(IJsonClassGeneratorConfig config, bool root) + { + return root ? config.Namespace : (config.SecondaryNamespace ?? config.Namespace); + } + + public void WriteNamespaceStart(IJsonClassGeneratorConfig config, TextWriter sw, bool root) + { + if (GetNamespace(config, root) != null) + { + + sw.WriteLine("module " + GetNamespace(config, root) + " {"); + sw.WriteLine(); + } + } + + public void WriteNamespaceEnd(IJsonClassGeneratorConfig config, TextWriter sw, bool root) + { + if (GetNamespace(config, root) != null) + { + sw.WriteLine("}"); + sw.WriteLine(); + } + } + + } +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/CodeWriters/vb.txt b/Json2CSharpCodeGenerator.Lib/CodeWriters/vb.txt new file mode 100644 index 0000000..6c0e328 --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/CodeWriters/vb.txt @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Xamasoft.JsonClassGenerator.CodeWriters +{ + public class VisualBasicCodeWriter : ICodeWriter + { + public string FileExtension + { + get { return ".vb"; } + } + + public string DisplayName + { + get { return "Visual Basic .NET"; } + } + + private const string NoRenameAttribute = ""; + private const string NoPruneAttribute = ""; + + public string GetTypeName(JsonType type, IJsonClassGeneratorConfig config) + { + var arraysAsLists = config.ExplicitDeserialization; + + switch (type.Type) + { + case JsonTypeEnum.Anything: return "Object"; + case JsonTypeEnum.Array: return arraysAsLists ? "IList(Of " + GetTypeName(type.InternalType, config) + ")" : GetTypeName(type.InternalType, config) + "()"; + case JsonTypeEnum.Dictionary: return "Dictionary(Of String, " + GetTypeName(type.InternalType, config) + ")"; + case JsonTypeEnum.Boolean: return "Boolean"; + case JsonTypeEnum.Float: return "Double"; + case JsonTypeEnum.Integer: return "Integer"; + case JsonTypeEnum.Long: return "Long"; + case JsonTypeEnum.Date: return "DateTime"; + case JsonTypeEnum.NonConstrained: return "Object"; + case JsonTypeEnum.NullableBoolean: return "Boolean?"; + case JsonTypeEnum.NullableFloat: return "Double?"; + case JsonTypeEnum.NullableInteger: return "Integer?"; + case JsonTypeEnum.NullableLong: return "Long?"; + case JsonTypeEnum.NullableDate: return "DateTime?"; + case JsonTypeEnum.NullableSomething: return "Object"; + case JsonTypeEnum.Object: return type.AssignedName; + case JsonTypeEnum.String: return "String"; + default: throw new System.NotSupportedException("Unsupported json type"); + } + } + + private bool ShouldApplyNoRenamingAttribute(IJsonClassGeneratorConfig config) + { + return config.ApplyObfuscationAttributes && !config.ExplicitDeserialization && !config.UsePascalCase; + } + private bool ShouldApplyNoPruneAttribute(IJsonClassGeneratorConfig config) + { + return config.ApplyObfuscationAttributes && !config.ExplicitDeserialization && config.UseProperties; + } + + public void WriteClass(IJsonClassGeneratorConfig config, TextWriter sw, JsonType type) + { + var visibility = config.InternalVisibility ? "Friend" : "Public"; + + if (config.UseNestedClasses) + { + sw.WriteLine(" {0} Partial Class {1}", visibility, config.MainClass); + if (!type.IsRoot) + { + if (ShouldApplyNoRenamingAttribute(config)) sw.WriteLine(" " + NoRenameAttribute); + if (ShouldApplyNoPruneAttribute(config)) sw.WriteLine(" " + NoPruneAttribute); + sw.WriteLine(" {0} Class {1}", visibility, type.AssignedName); + } + } + else + { + if (ShouldApplyNoRenamingAttribute(config)) sw.WriteLine(" " + NoRenameAttribute); + if (ShouldApplyNoPruneAttribute(config)) sw.WriteLine(" " + NoPruneAttribute); + sw.WriteLine(" {0} Class {1}", visibility, type.AssignedName); + } + + var prefix = config.UseNestedClasses && !type.IsRoot ? " " : " "; + + WriteClassMembers(config, sw, type, prefix); + + if (config.UseNestedClasses && !type.IsRoot) + sw.WriteLine(" End Class"); + + sw.WriteLine(" End Class"); + sw.WriteLine(); + + } + + + private void WriteClassMembers(IJsonClassGeneratorConfig config, TextWriter sw, JsonType type, string prefix) + { + foreach (var field in type.Fields) + { + if (config.UsePascalCase || config.ExamplesInDocumentation) sw.WriteLine(); + + if (config.ExamplesInDocumentation) + { + sw.WriteLine(prefix + "''' "); + sw.WriteLine(prefix + "''' Examples: " + field.GetExamplesText()); + sw.WriteLine(prefix + "''' "); + } + + + if (config.UsePascalCase) + { + sw.WriteLine(prefix + "", field.JsonMemberName); + } + + var validVbName = VisualBasicReservedWords.IsReserved(field.MemberName) ? $"[{field.MemberName}]" : field.MemberName; + + if (config.UseProperties) + { + sw.WriteLine(prefix + "Public Property {1} As {0}", field.Type.GetTypeName(), validVbName); + } + else + { + sw.WriteLine(prefix + "Public {1} As {0}", field.Type.GetTypeName(), validVbName); + } + } + + } + + + + + + public void WriteFileStart(IJsonClassGeneratorConfig config, TextWriter sw) + { + foreach (var line in JsonClassGenerator.FileHeader) + { + sw.WriteLine("' " + line); + } + sw.WriteLine(); + sw.WriteLine("Imports System"); + sw.WriteLine("Imports System.Collections.Generic"); + if (ShouldApplyNoRenamingAttribute(config) || ShouldApplyNoPruneAttribute(config)) + sw.WriteLine("Imports System.Reflection"); + if (config.UsePascalCase) + sw.WriteLine("Imports Newtonsoft.Json"); + sw.WriteLine("Imports Newtonsoft.Json.Linq"); + if (config.SecondaryNamespace != null && config.HasSecondaryClasses && !config.UseNestedClasses) + { + sw.WriteLine("Imports {0}", config.SecondaryNamespace); + } + } + + public void WriteFileEnd(IJsonClassGeneratorConfig config, TextWriter sw) + { + } + + + public void WriteNamespaceStart(IJsonClassGeneratorConfig config, TextWriter sw, bool root) + { + sw.WriteLine(); + sw.WriteLine("Namespace Global.{0}", root && !config.UseNestedClasses ? config.Namespace : (config.SecondaryNamespace ?? config.Namespace)); + sw.WriteLine(); + } + + public void WriteNamespaceEnd(IJsonClassGeneratorConfig config, TextWriter sw, bool root) + { + + sw.WriteLine("End Namespace"); + + } + } +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/FieldInfo.cs b/Json2CSharpCodeGenerator.Lib/FieldInfo.cs new file mode 100644 index 0000000..dc2b732 --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/FieldInfo.cs @@ -0,0 +1,128 @@ +// Copyright © 2010 Xamasoft + +using Newtonsoft.Json; +using System.Globalization; +using System.Text; + +namespace Json2CSharpCodeGenerator.Lib; + +public class FieldInfo +{ + public FieldInfo( + IJsonClassGeneratorConfig generator, + string jsonMemberName, + JsonType type, + bool usePascalCase, + IReadOnlyList examples + ) + { + this._Generator = generator ?? throw new ArgumentNullException(nameof(generator)); + JsonMemberName = jsonMemberName ?? throw new ArgumentNullException(nameof(jsonMemberName)); + MemberName = jsonMemberName; + ContainsSpecialChars = IsContainsSpecialChars(MemberName); + + if (usePascalCase || ContainsSpecialChars) + { + MemberName = JsonClassGenerator.ToTitleCase(MemberName); + } + + Type = type ?? throw new ArgumentNullException(nameof(type)); + Examples = examples ?? Array.Empty(); + } + + private readonly IJsonClassGeneratorConfig _Generator; + + /// Pascal-cased. + public string MemberName { get; } + /// Normally camelCased. + public string JsonMemberName { get; } + public JsonType Type { get; } + public IReadOnlyList Examples { get; } + + public bool ContainsSpecialChars { get; set; } + public int MyProperty { get; set; } + + private static bool IsContainsSpecialChars(string text) + { + for (int i = 0; i < text.Length; i++) + { + char c = text[i]; + if (!char.IsLetterOrDigit(c) && c != '_') + { + return true; + } + } + + return false; + } + + internal static string ToTitleCase(string text) + { + if (text is null) + throw new ArgumentNullException(nameof(text)); + if (string.IsNullOrWhiteSpace(text)) + return text; + + StringBuilder sb = new(text.Length); + bool lastCharWasSpecial = true; + + for (int i = 0; i < text.Length; i++) + { + char c = text[i]; + if (char.IsLetterOrDigit(c)) + { + _ = sb.Append(lastCharWasSpecial ? char.ToUpper(c) : c); + lastCharWasSpecial = false; + } + else + { + lastCharWasSpecial = true; + } + } + + return sb.ToString(); + } + + public string GetGenerationCode(string jObject) + { + if (jObject is null) + throw new ArgumentNullException(nameof(jObject)); + + if (Type.Type == JsonTypeEnum.Array) + { + JsonType innermost = Type.GetInnermostType(); + return string.Format(CultureInfo.InvariantCulture, "({1})JsonClassHelper.ReadArray<{5}>(JsonClassHelper.GetJToken({0}, \"{2}\"), JsonClassHelper.{3}, typeof({6}))", + jObject, + Type.GetTypeName(), + JsonMemberName, + innermost.GetReaderName(), + -1, + innermost.GetTypeName(), + Type.GetTypeName() + ); + } + else if (Type.Type == JsonTypeEnum.Dictionary) + { + return string.Format(CultureInfo.InvariantCulture, "({1})JsonClassHelper.ReadDictionary<{2}>(JsonClassHelper.GetJToken({0}, \"{3}\"))", + jObject, + Type.GetTypeName(), + Type.InternalType.GetTypeName(), + JsonMemberName, + Type.GetTypeName() + ); + } + else + { + return string.Format(CultureInfo.InvariantCulture, "JsonClassHelper.{1}(JsonClassHelper.GetJToken<{2}>({0}, \"{3}\"))", + jObject, + Type.GetReaderName(), + Type.GetJTokenType(), + JsonMemberName + ); + } + + } + + public string GetExamplesText() => string.Join(separator: ", ", values: Examples.Take(5).Select(x => JsonConvert.SerializeObject(x))); + +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/Helpers.cs b/Json2CSharpCodeGenerator.Lib/Helpers.cs new file mode 100644 index 0000000..6e6be5a --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/Helpers.cs @@ -0,0 +1,17 @@ +using System.Text.RegularExpressions; + +namespace Json2CSharpCodeGenerator.Lib; + +public static class StringExtension +{ + public static string ToCamelCase(this string str) + { + if (!string.IsNullOrEmpty(str) && str.Length > 1) + { + return char.ToLowerInvariant(str[0]) + str.Substring(1); + } + return str.ToLowerInvariant(); + } + + public static string RemoveSpecialCharacters(this string str) => Regex.Replace(str, "[^a-zA-Z0-9]+", "", RegexOptions.Compiled); +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/ICodeWriter.cs b/Json2CSharpCodeGenerator.Lib/ICodeWriter.cs new file mode 100644 index 0000000..bc5c2de --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/ICodeWriter.cs @@ -0,0 +1,34 @@ +using System.Text; + +namespace Json2CSharpCodeGenerator.Lib; + +public interface ICodeBuilder +{ + string FileExtension { get; } + string DisplayName { get; } + + string GetTypeName(JsonType type, IJsonClassGeneratorConfig config); + + void WriteClass(IJsonClassGeneratorConfig config, StringBuilder sw, JsonType type); + void WriteFileStart(IJsonClassGeneratorConfig config, StringBuilder sw); + void WriteFileEnd(IJsonClassGeneratorConfig config, StringBuilder sw); + void WriteNamespaceStart(IJsonClassGeneratorConfig config, StringBuilder sw, bool root); + void WriteNamespaceEnd(IJsonClassGeneratorConfig config, StringBuilder sw, bool root); + void WriteDeserializationComment(IJsonClassGeneratorConfig config, StringBuilder sw, bool rootIsArray = false); + void WriteClassMembers(IJsonClassGeneratorConfig config, StringBuilder sw, JsonType type, string prefix); + + IReadOnlyCollection ReservedKeywords { get; } + bool IsReservedKeyword(string word); +} + +public interface ICodeWriter +{ + string FileExtension { get; } + string DisplayName { get; } + string GetTypeName(JsonType type, IJsonClassGeneratorConfig config); + void WriteClass(IJsonClassGeneratorConfig config, TextWriter sw, JsonType type); + void WriteFileStart(IJsonClassGeneratorConfig config, TextWriter sw); + void WriteFileEnd(IJsonClassGeneratorConfig config, TextWriter sw); + void WriteNamespaceStart(IJsonClassGeneratorConfig config, TextWriter sw, bool root); + void WriteNamespaceEnd(IJsonClassGeneratorConfig config, TextWriter sw, bool root); +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/IJsonClassGeneratorConfig.cs b/Json2CSharpCodeGenerator.Lib/IJsonClassGeneratorConfig.cs new file mode 100644 index 0000000..4cfdd03 --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/IJsonClassGeneratorConfig.cs @@ -0,0 +1,192 @@ +namespace Json2CSharpCodeGenerator.Lib; + +public interface IJsonClassGeneratorConfig +{ + /// + /// The C# namespace or Java package that the generated types will reside in.
+ /// by default. If null/empty/whitespace then no enclosing namespace will be written in the output. + ///
+ string Namespace { get; set; } + + /// + /// The C# namespace or Java package that "secondary" generated types will reside in.
+ /// by default. + ///
+ string SecondaryNamespace { get; set; } + + OutputTypes OutputType { get; set; } + OutputCollectionType CollectionType { get; set; } + + /// Options contained within are only respected when == . + MutableClassConfig MutableClasses { get; } + + JsonLibrary AttributeLibrary { get; set; } + JsonPropertyAttributeUsage AttributeUsage { get; set; } + + bool InternalVisibility { get; set; } + bool NoHelperClass { get; set; } + + /// + /// When true, then the generated C# and VB.NET classes will have PascalCase property names, which means that [JsonProperty] or [JsonPropertyName] will be applied to all properties if the source JSON uses camelCase names, regardless of .
+ /// When false, then the generated C# and VB.NET classes' property names will use the same names as the source JSON (except when a JSON property name cannot be represented by a C# identifier). + ///
+ bool UsePascalCase { get; set; } + + bool UseNestedClasses { get; set; } + + /// Name of the outer class. Only used when == . + string MainClass { get; set; } + + /// When , then will be applied to generated types. + bool ApplyObfuscationAttributes { get; set; } + + bool SingleFile { get; set; } + ICodeBuilder CodeWriter { get; set; } + bool AlwaysUseNullableValues { get; set; } + bool ExamplesInDocumentation { get; set; } + bool RemoveToJson { get; set; } + bool RemoveFromJson { get; set; } + bool RemoveConstructors { get; set; } +} + +public enum OutputTypes +{ + /// + /// C#: Mutable class types.
+ /// VB.NET: Mutable Class types.
+ /// Java: Mutable Bean class + ///
+ MutableClass, + + /// + /// C#: Immutable class types. Using [JsonConstructor].
+ /// VB.NET: Immutable Class types. Using [JsonConstructor].
+ /// Java: Not yet implemented. TODO. + ///
+ ImmutableClass, + + /// + /// C#: Immutable record types.
+ /// VB.NET: Not supported.
+ /// Java: Not supported.
+ ///
+ ImmutableRecord +} + +public enum OutputCollectionType +{ + /// Expose collections as T[]. + Array, + + /// + /// C#/VB.NET: Expose collections as .
+ /// Java: Uses ArrayList<T> + ///
+ MutableList, + + /// + /// C#/VB.NET: Expose collections as .
+ /// Java: Not supported. + ///
+ IReadOnlyList, + + /// + /// C#/VB.NET: Expose collections as System.Collections.Immutable.ImmutableArray<T>.
+ /// Java: Not supported. + ///
+ /// ImmutableArray is preferred over ImmutableList when append functionality isn't required. + ImmutableArray +} + +public enum OutputMembers +{ + /// C# and VB.NET: Uses auto-properties. Java: uses getter/setter methods. + AsProperties, + AsPublicFields, + // AsPublicPropertiesOverPrivateFields // TODO +} + +public enum JsonLibrary +{ + /// Use the on generated C# class properties. + NewtonsoftJson, + + /// Use the [JsonPropertyName] attribute on generated C# class properties. + SystemTextJson +} + +public enum JsonPropertyAttributeUsage +{ + /// The [JsonProperty] or [JsonPropertyName] attributes will be applied to all properties. + Always, + + /// The [JsonProperty] or [JsonPropertyName] attributes will only be applied to properties with names that cannot be expressed as C# identifiers. + OnlyWhenNecessary +} + +public class MutableClassConfig +{ + /// + /// When , then all properties for collections are read-only, though the actual collection-type can still be mutable. e.g. public List<String> StringValues { get; }
+ /// When , then all properties for collections are read-only, though the actual collection-type can still be mutable. e.g. public List<String> StringValues { get; set; }
+ /// Default is . + ///
+ public bool ReadOnlyCollectionProperties { get; set; } + + public OutputMembers Members { get; set; } = OutputMembers.AsProperties; +} + +public static class JsonClassGeneratorConfigExtensions +{ + /// + /// Never returns . Returns either: + /// + /// "[JsonPropertyName(".")]" (for System.Text.Json). + /// "[JsonProperty(".")]" (for Newtonsoft.Json). + /// "[property: JsonPropertyName(".")]" (for System.Text.Json) when is . + /// "[property: JsonProperty(".")]" (for Newtonsoft.Json) when is . + /// An empty string depending on and . + /// + /// + /// Required. Cannot be . + /// Required. Cannot be . + /// + public static string GetCSharpJsonAttributeCode(this IJsonClassGeneratorConfig config, FieldInfo field) + { + if (config is null) + throw new ArgumentNullException(nameof(config)); + if (field is null) + throw new ArgumentNullException(nameof(field)); + + // + + if (UsePropertyAttribute(config, field)) + { + bool usingRecordTypes = config.OutputType == OutputTypes.ImmutableRecord; + string attributeTarget = usingRecordTypes ? "property: " : string.Empty; + + return config.AttributeLibrary switch + { + JsonLibrary.NewtonsoftJson => $"[{attributeTarget}JsonProperty(\"{field.JsonMemberName}\")]", + JsonLibrary.SystemTextJson => $"[{attributeTarget}JsonPropertyName(\"{field.JsonMemberName}\")]", + _ => throw new InvalidOperationException("Unrecognized " + nameof(config.AttributeLibrary) + " value: " + config.AttributeLibrary), + }; + } + else + { + return string.Empty; + } + } + + private static bool UsePropertyAttribute(IJsonClassGeneratorConfig config, FieldInfo field) + { + return config.AttributeUsage switch + { + JsonPropertyAttributeUsage.Always => true, + JsonPropertyAttributeUsage.OnlyWhenNecessary => field.ContainsSpecialChars, + _ => throw new InvalidOperationException("Unrecognized " + nameof(config.AttributeUsage) + " value: " + config.AttributeUsage), + }; + } + + public static bool HasNamespace(this IJsonClassGeneratorConfig config) => !string.IsNullOrEmpty(config.Namespace); +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/Json2CSharpCodeGenerator.Lib.csproj b/Json2CSharpCodeGenerator.Lib/Json2CSharpCodeGenerator.Lib.csproj new file mode 100644 index 0000000..1b459f3 --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/Json2CSharpCodeGenerator.Lib.csproj @@ -0,0 +1,14 @@ + + + enable + 10.0 + disable + win-x64 + netstandard2.1 + + + + + + + \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/JsonClassGenerator.cs b/Json2CSharpCodeGenerator.Lib/JsonClassGenerator.cs new file mode 100644 index 0000000..3e3720a --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/JsonClassGenerator.cs @@ -0,0 +1,364 @@ +// Copyright © 2010 Xamasoft + +using Humanizer; +using Json2CSharpCodeGenerator.Lib.CodeWriters; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Text; + +namespace Json2CSharpCodeGenerator.Lib; + +public class JsonClassGenerator : IJsonClassGeneratorConfig +{ + #region IJsonClassGeneratorConfig + + public OutputTypes OutputType { get; set; } = OutputTypes.MutableClass; + public OutputCollectionType CollectionType { get; set; } = OutputCollectionType.MutableList; + public MutableClassConfig MutableClasses { get; } = new MutableClassConfig(); + public JsonLibrary AttributeLibrary { get; set; } = JsonLibrary.NewtonsoftJson; + public JsonPropertyAttributeUsage AttributeUsage { get; set; } = JsonPropertyAttributeUsage.OnlyWhenNecessary; + + public string Namespace { get; set; } + public string SecondaryNamespace { get; set; } + + public bool InternalVisibility { get; set; } + public bool NoHelperClass { get; set; } + public string MainClass { get; set; } + public bool UsePascalCase { get; set; } + public bool UseNestedClasses { get; set; } + public bool ApplyObfuscationAttributes { get; set; } + public bool SingleFile { get; set; } + public ICodeBuilder CodeWriter { get; set; } + public bool AlwaysUseNullableValues { get; set; } + public bool ExamplesInDocumentation { get; set; } + public bool RemoveToJson { get; set; } + public bool RemoveFromJson { get; set; } + public bool RemoveConstructors { get; set; } + + #endregion + + public TextWriter OutputStream { get; set; } + + //private readonly PluralizationService pluralizationService = PluralizationService.CreateService(new CultureInfo("en-US")); + + public StringBuilder GenerateClasses(string jsonInput, out string errorMessage) + { + JObject[] examples = null; + bool rootWasArray = false; + try + { + using StringReader sr = new(jsonInput); + using JsonTextReader reader = new(sr); + JToken json = JToken.ReadFrom(reader); + if (json is JArray jArray && (jArray.Count == 0 || jArray.All(el => el is JObject))) + { + rootWasArray = true; + examples = jArray.Cast().ToArray(); + } + else if (json is JObject jObject) + { + examples = new[] { jObject }; + } + } + catch (Exception ex) + { + errorMessage = "Exception: " + ex.Message; + return new StringBuilder(); + } + + try + { + if (CodeWriter == null) + CodeWriter = new CSharpCodeWriter(); + + Types = new List(); + _ = this._Names.Add("Root"); + JsonType rootType = new(this, examples[0]) + { + IsRoot = true + }; + rootType.AssignName("Root"); + GenerateClass(examples, rootType); + + Types = HandleDuplicateClasses(Types); + + StringBuilder builder = new(); + WriteClassesToFile(builder, Types, rootWasArray); + + errorMessage = string.Empty; + return builder; + } + catch (Exception ex) + { + errorMessage = ex.ToString(); + return new StringBuilder(); + } + } + + private void WriteClassesToFile(StringBuilder sw, IEnumerable types, bool rootIsArray = false) + { + bool inNamespace = false; + bool rootNamespace = false; + + CodeWriter.WriteFileStart(this, sw); + CodeWriter.WriteDeserializationComment(this, sw, rootIsArray); + + foreach (JsonType type in types) + { + if (this.HasNamespace() && inNamespace && rootNamespace != type.IsRoot && SecondaryNamespace != null) + { + CodeWriter.WriteNamespaceEnd(this, sw, rootNamespace); + inNamespace = false; + } + + if (this.HasNamespace() && !inNamespace) + { + CodeWriter.WriteNamespaceStart(this, sw, type.IsRoot); + inNamespace = true; + rootNamespace = type.IsRoot; + } + + CodeWriter.WriteClass(this, sw, type); + } + + if (this.HasNamespace() && inNamespace) + { + CodeWriter.WriteNamespaceEnd(this, sw, rootNamespace); + } + + CodeWriter.WriteFileEnd(this, sw); + } + + private void GenerateClass(JObject[] examples, JsonType type) + { + Dictionary jsonFields = new(); + Dictionary> fieldExamples = new(); + + bool first = true; + + foreach (JObject obj in examples) + { + foreach (JProperty prop in obj.Properties()) + { + JsonType fieldType; + JsonType currentType = new(this, prop.Value); + string propName = prop.Name; + + if (jsonFields.TryGetValue(propName, out fieldType)) + { + JsonType commonType = fieldType.GetCommonType(currentType); + + jsonFields[propName] = commonType; + } + else + { + JsonType commonType = currentType; + if (first) + commonType = commonType.MaybeMakeNullable(this); + else + commonType = commonType.GetCommonType(JsonType.GetNull(this)); + + jsonFields.Add(propName, commonType); + fieldExamples[propName] = new List(); + } + + List fe = fieldExamples[propName]; + JToken val = prop.Value; + if (val.Type is JTokenType.Null or JTokenType.Undefined) + { + if (!fe.Contains(null)) + { + fe.Insert(0, null); + } + } + else + { + object v = val.Type is JTokenType.Array or JTokenType.Object ? val : val.Value(); + if (!fe.Any(x => v.Equals(x))) + { + fe.Add(v); + } + } + } + first = false; + } + + if (UseNestedClasses) + { + foreach (KeyValuePair field in jsonFields) + { + _ = this._Names.Add(field.Key.ToLower()); + } + } + + foreach (KeyValuePair field in jsonFields) + { + JsonType fieldType = field.Value; + if (fieldType.Type == JsonTypeEnum.Object) + { + List subexamples = new(examples.Length); + foreach (JObject obj in examples) + { + JToken value; + if (obj.TryGetValue(field.Key, out value)) + { + if (value.Type == JTokenType.Object) + { + subexamples.Add((JObject)value); + } + } + } + + fieldType.AssignOriginalName(field.Key); + fieldType.AssignName(CreateUniqueClassName(field.Key)); + fieldType.AssignNewAssignedName(ToTitleCase(field.Key)); + + GenerateClass(subexamples.ToArray(), fieldType); + } + + if (fieldType.InternalType != null && fieldType.InternalType.Type == JsonTypeEnum.Object) + { + List subexamples = new(examples.Length); + foreach (JObject obj in examples) + { + JToken value; + if (obj.TryGetValue(field.Key, out value)) + { + if (value is JArray jArray) + { + const int MAX_JSON_ARRAY_ITEMS = 50; // Take like 30 items from the array this will increase the chance of getting all the objects accuralty while not analyzing all the data + + subexamples.AddRange(jArray.OfType().Take(MAX_JSON_ARRAY_ITEMS)); + } + else if (value is JObject jObject) //TODO J2C : ONLY LOOP OVER 50 OBJECT AND NOT THE WHOLE THING + { + foreach (KeyValuePair jsonObjectProperty in jObject) + { + // if (!(item.Value is JObject)) throw new NotSupportedException("Arrays of non-objects are not supported yet."); + if (jsonObjectProperty.Value is JObject innerObject) + { + subexamples.Add(innerObject); + } + } + } + } + } + + field.Value.InternalType.AssignOriginalName(field.Key); + field.Value.InternalType.AssignName(CreateUniqueClassNameFromPlural(field.Key)); + field.Value.InternalType.AssignNewAssignedName(ToTitleCase(field.Key).Singularize(inputIsKnownToBePlural: false)); + + GenerateClass(subexamples.ToArray(), field.Value.InternalType); + } + } + + type.Fields = jsonFields + .Select(x => new FieldInfo( + generator: this, + jsonMemberName: x.Key, + type: x.Value, + usePascalCase: UsePascalCase || AttributeUsage == JsonPropertyAttributeUsage.Always, + examples: fieldExamples[x.Key]) + ) + .ToList(); + + if (!string.IsNullOrEmpty(type.AssignedName)) + { + Types.Add(type); + } + } + + /// Checks if there are any duplicate classes in the input, and merges its corresponding properties (TEST CASE 7) + private IList HandleDuplicateClasses(IList types) + { + // TODO: This is currently O(n*n) because it iterates through List on every loop iteration. This can be optimized. + + List typesWithNoDuplicates = new(); + types = types.OrderBy(p => p.AssignedName).ToList(); + foreach (JsonType type in types) + { + if (!typesWithNoDuplicates.Exists(p => p.OriginalName == type.OriginalName)) + { + typesWithNoDuplicates.Add(type); + } + else + { + JsonType duplicatedType = typesWithNoDuplicates.FirstOrDefault(p => p.OriginalName == type.OriginalName); + + // Rename all references of this type to the original assigned name + foreach (FieldInfo field in type.Fields) + { + if (!duplicatedType.Fields.ToList().Exists(x => x.JsonMemberName == field.JsonMemberName)) + { + duplicatedType.Fields.Add(field); + } + } + } + } + + return typesWithNoDuplicates; + } + + public IList Types { get; private set; } + private readonly HashSet _Names = new(); + + private string CreateUniqueClassName(string name) + { + name = ToTitleCase(name); + + string finalName = name; + int i = 2; + while (this._Names.Any(x => x.Equals(finalName, StringComparison.OrdinalIgnoreCase))) + { + + finalName = name + i.ToString(); + i++; + } + + _ = this._Names.Add(finalName); + return finalName; + } + + private string CreateUniqueClassNameFromPlural(string plural) + { + plural = ToTitleCase(plural); + string singular = plural.Singularize(inputIsKnownToBePlural: false); + return CreateUniqueClassName(singular); + } + + internal static string ToTitleCase(string str) + { + StringBuilder sb = new(str.Length); + bool flag = true; + + for (int i = 0; i < str.Length; i++) + { + char c = str[i]; + string specialCaseFirstCharIsNumber = string.Empty; + + // Handle the case where the first character is a number + if (i == 0 && char.IsDigit(c)) + specialCaseFirstCharIsNumber = "_" + c; + + if (char.IsLetterOrDigit(c)) + { + if (string.IsNullOrEmpty(specialCaseFirstCharIsNumber)) + { + _ = sb.Append(flag ? char.ToUpper(c) : c); + } + else + { + _ = sb.Append(flag ? specialCaseFirstCharIsNumber.ToUpper() : specialCaseFirstCharIsNumber); + } + + flag = false; + } + else + { + flag = true; + } + } + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/JsonClassHelper.cs b/Json2CSharpCodeGenerator.Lib/JsonClassHelper.cs new file mode 100644 index 0000000..cbbb7ba --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/JsonClassHelper.cs @@ -0,0 +1,203 @@ +// JSON C# Class Generator +// http://www.xamasoft.com/json-class-generator + +using Newtonsoft.Json.Linq; + +namespace JsonCSharpClassGenerator; + +internal static class JsonClassHelper +{ + + public static T GetJToken(JObject obj, string field) where T : JToken + { + JToken value; + if (obj.TryGetValue(field, out value)) + return GetJToken(value); + else + return null; + } + + private static T GetJToken(JToken token) where T : JToken + { + if (token == null) + return null; + if (token.Type == JTokenType.Null) + return null; + if (token.Type == JTokenType.Undefined) + return null; + return (T)token; + } + + public static string ReadString(JToken token) + { + JValue value = GetJToken(token); + if (value == null) + return null; + return (string)value.Value; + } + + public static bool ReadBoolean(JToken token) + { + JValue value = GetJToken(token); + if (value == null) + throw new Newtonsoft.Json.JsonSerializationException(); + return Convert.ToBoolean(value.Value); + + } + + public static bool? ReadNullableBoolean(JToken token) + { + JValue value = GetJToken(token); + if (value == null) + return null; + return Convert.ToBoolean(value.Value); + } + + public static int ReadInteger(JToken token) + { + JValue value = GetJToken(token); + if (value == null) + throw new Newtonsoft.Json.JsonSerializationException(); + return Convert.ToInt32((long)value.Value); + + } + + public static int? ReadNullableInteger(JToken token) + { + JValue value = GetJToken(token); + if (value == null) + return null; + return Convert.ToInt32((long)value.Value); + + } + + public static long ReadLong(JToken token) + { + JValue value = GetJToken(token); + if (value == null) + throw new Newtonsoft.Json.JsonSerializationException(); + return Convert.ToInt64(value.Value); + + } + + public static long? ReadNullableLong(JToken token) + { + JValue value = GetJToken(token); + if (value == null) + return null; + return Convert.ToInt64(value.Value); + } + + public static double ReadFloat(JToken token) + { + JValue value = GetJToken(token); + if (value == null) + throw new Newtonsoft.Json.JsonSerializationException(); + return Convert.ToDouble(value.Value); + + } + + public static double? ReadNullableFloat(JToken token) + { + JValue value = GetJToken(token); + if (value == null) + return null; + return Convert.ToDouble(value.Value); + + } + + public static DateTime ReadDate(JToken token) + { + JValue value = GetJToken(token); + if (value == null) + throw new Newtonsoft.Json.JsonSerializationException(); + return Convert.ToDateTime(value.Value); + + } + + public static DateTime? ReadNullableDate(JToken token) + { + JValue value = GetJToken(token); + if (value == null) + return null; + return Convert.ToDateTime(value.Value); + + } + + public static object ReadObject(JToken token) + { + JToken value = GetJToken(token); + if (value == null) + return null; + if (value.Type == JTokenType.Object) + return value; + if (value.Type == JTokenType.Array) + return ReadArray(value, ReadObject); + + if (value is JValue jvalue) + return jvalue.Value; + + return value; + } + + public static T ReadStronglyTypedObject(JToken token) where T : class + { + JObject value = GetJToken(token); + if (value == null) + return null; + return (T)Activator.CreateInstance(typeof(T), new object[] { token }); + + } + + public delegate T ValueReader(JToken token); + + public static T[] ReadArray(JToken token, ValueReader reader) + { + JArray value = GetJToken(token); + if (value == null) + return null; + + T[] array = new T[value.Count]; + for (int i = 0; i < array.Length; i++) + { + array[i] = reader(value[i]); + } + return array; + + } + + public static Dictionary ReadDictionary(JToken token) + { + JObject value = GetJToken(token); + if (value == null) + return null; + + Dictionary dict = new(); + + return dict; + } + + public static Array ReadArray(JArray jArray, ValueReader reader, Type type) + { + if (jArray == null) + return null; + + Type elemType = type.GetElementType(); + + Array array = Array.CreateInstance(elemType, jArray.Count); + for (int i = 0; i < array.Length; i++) + { + if (elemType.IsArray) + { + array.SetValue(ReadArray(GetJToken(jArray[i]), reader, elemType), i); + } + else + { + array.SetValue(reader(jArray[i]), i); + } + + } + return array; + + } +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/JsonType.cs b/Json2CSharpCodeGenerator.Lib/JsonType.cs new file mode 100644 index 0000000..435804f --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/JsonType.cs @@ -0,0 +1,356 @@ +// Copyright © 2010 Xamasoft + +using Newtonsoft.Json.Linq; + +namespace Json2CSharpCodeGenerator.Lib; + +public class JsonType +{ + + private JsonType(IJsonClassGeneratorConfig generator) => this._Generator = generator; + + public JsonType(IJsonClassGeneratorConfig generator, JToken token) + : this(generator) + { + + Type = GetFirstTypeEnum(token); + + if (Type == JsonTypeEnum.Array) + { + JArray array = (JArray)token; + InternalType = GetCommonType(generator, array.ToArray()); + } + } + + internal static JsonType GetNull(IJsonClassGeneratorConfig generator) => new(generator, JsonTypeEnum.NullableSomething); + + private readonly IJsonClassGeneratorConfig _Generator; + + internal JsonType(IJsonClassGeneratorConfig generator, JsonTypeEnum type) + : this(generator) => Type = type; + + public static JsonType GetCommonType(IJsonClassGeneratorConfig generator, JToken[] tokens) + { + + if (tokens.Length == 0) + return new JsonType(generator, JsonTypeEnum.NonConstrained); + + JsonType common = new JsonType(generator, tokens[0]).MaybeMakeNullable(generator); + + for (int i = 1; i < tokens.Length; i++) + { + JsonType current = new(generator, tokens[i]); + + try + { + common = common.GetCommonType(current); + } + catch (Exception) + { + throw; + } + } + + return common; + + } + + internal JsonType MaybeMakeNullable(IJsonClassGeneratorConfig generator) + { + if (!generator.AlwaysUseNullableValues) + return this; + return GetCommonType(GetNull(generator)); + } + + public JsonTypeEnum Type { get; private set; } + public JsonType InternalType { get; private set; } + public string AssignedName { get; private set; } + public string OriginalName { get; private set; } + public string NewAssignedName { get; private set; } + + public void AssignName(string name) => AssignedName = name; + public void AssignOriginalName(string name) => OriginalName = name; + public void AssignNewAssignedName(string name) => NewAssignedName = name; + + public bool MustCache + { + get + { + return Type switch + { + JsonTypeEnum.Array => true, + JsonTypeEnum.Object => true, + JsonTypeEnum.Anything => true, + JsonTypeEnum.Dictionary => true, + JsonTypeEnum.NonConstrained => true, + _ => false, + }; + } + } + + public string GetReaderName() + { + if (Type is JsonTypeEnum.Anything or JsonTypeEnum.NullableSomething or JsonTypeEnum.NonConstrained) + { + return "ReadObject"; + } + if (Type == JsonTypeEnum.Object) + { + return string.Format("ReadStronglyTypedObject<{0}>", AssignedName); + } + else if (Type == JsonTypeEnum.Array) + { + return string.Format("ReadArray<{0}>", InternalType.GetTypeName()); + } + else + { + return string.Format("Read{0}", Enum.GetName(typeof(JsonTypeEnum), Type)); + } + } + + public JsonType GetInnermostType() + { + if (Type != JsonTypeEnum.Array) + throw new InvalidOperationException(); + if (InternalType.Type != JsonTypeEnum.Array) + return InternalType; + return InternalType.GetInnermostType(); + } + + public bool IsCollectionType() => Type is JsonTypeEnum.Array or JsonTypeEnum.Dictionary; + + public string GetTypeName() => _Generator.CodeWriter.GetTypeName(this, _Generator); + + public string GetJTokenType() + { + return Type switch + { + JsonTypeEnum.Boolean or JsonTypeEnum.Integer or JsonTypeEnum.Long or JsonTypeEnum.Float or JsonTypeEnum.Date or JsonTypeEnum.NullableBoolean or JsonTypeEnum.NullableInteger or JsonTypeEnum.NullableLong or JsonTypeEnum.NullableFloat or JsonTypeEnum.NullableDate or JsonTypeEnum.String => "JValue", + JsonTypeEnum.Array => "JArray", + JsonTypeEnum.Dictionary => "JObject", + JsonTypeEnum.Object => "JObject", + _ => "JToken", + }; + } + + public JsonType GetCommonType(JsonType type2) + { + + JsonTypeEnum commonType = GetCommonTypeEnum(Type, type2); + + if (commonType == JsonTypeEnum.Array) + { + if (type2.Type == JsonTypeEnum.NullableSomething) + return this; + if (Type == JsonTypeEnum.NullableSomething) + return type2; + + JsonType commonInternalType; + if (InternalType == null && type2.InternalType != null) // Handling the case Test_4 where the first array is an empty object + commonInternalType = type2.InternalType; + else + commonInternalType = InternalType.GetCommonType(type2.InternalType).MaybeMakeNullable(_Generator); + + if (commonInternalType != InternalType) + return new JsonType(_Generator, JsonTypeEnum.Array) { InternalType = commonInternalType }; + } + + //if (commonType == JsonTypeEnum.Dictionary) + //{ + // var commonInternalType = InternalType.GetCommonType(type2.InternalType); + // if (commonInternalType != InternalType) return new JsonType(JsonTypeEnum.Dictionary) { InternalType = commonInternalType }; + //} + + if (Type == commonType) + return this; + return new JsonType(_Generator, commonType).MaybeMakeNullable(_Generator); + } + + private static bool IsNull(JsonTypeEnum type) => type == JsonTypeEnum.NullableSomething; + + private JsonTypeEnum GetCommonTypeEnum(JsonTypeEnum type1, JsonType type2json) + { + if (type2json == null) + return type1; + + JsonTypeEnum type2 = type2json.Type; + + if (type1 == JsonTypeEnum.NonConstrained) + return type2; + if (type2 == JsonTypeEnum.NonConstrained) + return type1; + + switch (type1) + { + case JsonTypeEnum.Boolean: + if (IsNull(type2)) + return JsonTypeEnum.NullableBoolean; + if (type2 == JsonTypeEnum.Boolean) + return type1; + break; + case JsonTypeEnum.NullableBoolean: + if (IsNull(type2)) + return type1; + if (type2 == JsonTypeEnum.Boolean) + return type1; + break; + case JsonTypeEnum.Integer: + if (IsNull(type2)) + return JsonTypeEnum.NullableInteger; + if (type2 == JsonTypeEnum.Float) + return JsonTypeEnum.Float; + if (type2 == JsonTypeEnum.Long) + return JsonTypeEnum.Long; + if (type2 == JsonTypeEnum.Integer) + return type1; + break; + case JsonTypeEnum.NullableInteger: + if (IsNull(type2)) + return type1; + if (type2 == JsonTypeEnum.Float) + return JsonTypeEnum.NullableFloat; + if (type2 == JsonTypeEnum.Long) + return JsonTypeEnum.NullableLong; + if (type2 == JsonTypeEnum.Integer) + return type1; + break; + case JsonTypeEnum.Float: + if (IsNull(type2)) + return JsonTypeEnum.NullableFloat; + if (type2 == JsonTypeEnum.Float) + return type1; + if (type2 == JsonTypeEnum.Integer) + return type1; + if (type2 == JsonTypeEnum.Long) + return type1; + break; + case JsonTypeEnum.NullableFloat: + if (IsNull(type2)) + return type1; + if (type2 == JsonTypeEnum.Float) + return type1; + if (type2 == JsonTypeEnum.Integer) + return type1; + if (type2 == JsonTypeEnum.Long) + return type1; + break; + case JsonTypeEnum.Long: + if (IsNull(type2)) + return JsonTypeEnum.NullableLong; + if (type2 == JsonTypeEnum.Float) + return JsonTypeEnum.Float; + if (type2 == JsonTypeEnum.Integer) + return type1; + break; + case JsonTypeEnum.NullableLong: + if (IsNull(type2)) + return type1; + if (type2 == JsonTypeEnum.Float) + return JsonTypeEnum.NullableFloat; + if (type2 == JsonTypeEnum.Integer) + return type1; + if (type2 == JsonTypeEnum.Long) + return type1; + break; + case JsonTypeEnum.Date: + if (IsNull(type2)) + return JsonTypeEnum.NullableDate; + if (type2 == JsonTypeEnum.Date) + return JsonTypeEnum.Date; + break; + case JsonTypeEnum.NullableDate: + if (IsNull(type2)) + return type1; + if (type2 == JsonTypeEnum.Date) + return type1; + break; + case JsonTypeEnum.NullableSomething: + if (IsNull(type2)) + return type1; + if (type2 == JsonTypeEnum.String) + return JsonTypeEnum.String; + if (type2 == JsonTypeEnum.Integer) + return JsonTypeEnum.NullableInteger; + if (type2 == JsonTypeEnum.Float) + return JsonTypeEnum.NullableFloat; + if (type2 == JsonTypeEnum.Long) + return JsonTypeEnum.NullableLong; + if (type2 == JsonTypeEnum.Boolean) + return JsonTypeEnum.NullableBoolean; + if (type2 == JsonTypeEnum.Date) + return JsonTypeEnum.NullableDate; + if (type2 == JsonTypeEnum.Array) + return JsonTypeEnum.Array; + if (type2 == JsonTypeEnum.Object) + return JsonTypeEnum.Object; + break; + case JsonTypeEnum.Object: + if (IsNull(type2)) + return type1; + if (type2 == JsonTypeEnum.Object) + return type1; + if (type2 == JsonTypeEnum.Dictionary) + throw new ArgumentException(); + break; + case JsonTypeEnum.Dictionary: + throw new ArgumentException(); + //if (IsNull(type2)) return type1; + //if (type2 == JsonTypeEnum.Object) return type1; + //if (type2 == JsonTypeEnum.Dictionary) return type1; + // break; + case JsonTypeEnum.Array: + if (IsNull(type2)) + return type1; + if (type2 == JsonTypeEnum.Array) + return type1; + break; + case JsonTypeEnum.String: + if (IsNull(type2)) + return type1; + if (type2 == JsonTypeEnum.String) + return type1; + break; + } + + return JsonTypeEnum.Anything; + + } + + private static bool IsNull(JTokenType type) => type is JTokenType.Null or JTokenType.Undefined; + + private static JsonTypeEnum GetFirstTypeEnum(JToken token) + { + JTokenType type = token.Type; + if (type == JTokenType.Integer) + { + try + { + if ((long)((JValue)token).Value < int.MaxValue) + return JsonTypeEnum.Integer; + else + return JsonTypeEnum.Long; + } + catch (Exception) + { + // Must be a BigInteger Number, either way make it as string + return JsonTypeEnum.String; + } + + } + return type switch + { + JTokenType.Array => JsonTypeEnum.Array, + JTokenType.Boolean => JsonTypeEnum.Boolean, + JTokenType.Float => JsonTypeEnum.Float, + JTokenType.Null => JsonTypeEnum.NullableSomething, + JTokenType.Undefined => JsonTypeEnum.NullableSomething, + JTokenType.String => JsonTypeEnum.String, + JTokenType.Object => JsonTypeEnum.Object, + JTokenType.Date => JsonTypeEnum.Date, + _ => JsonTypeEnum.Anything, + }; + } + + public List Fields { get; internal set; } + public bool IsRoot { get; internal set; } +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.Lib/JsonTypeEnum.cs b/Json2CSharpCodeGenerator.Lib/JsonTypeEnum.cs new file mode 100644 index 0000000..e461f40 --- /dev/null +++ b/Json2CSharpCodeGenerator.Lib/JsonTypeEnum.cs @@ -0,0 +1,24 @@ +// Copyright © 2010 Xamasoft + +namespace Json2CSharpCodeGenerator.Lib; + +public enum JsonTypeEnum +{ + Anything, + String, + Boolean, + Integer, + Long, + Float, + Date, + NullableInteger, + NullableLong, + NullableFloat, + NullableBoolean, + NullableDate, + Object, + Array, + Dictionary, + NullableSomething, + NonConstrained +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.WinForms/Json2CSharpCodeGenerator.WinForms.csproj b/Json2CSharpCodeGenerator.WinForms/Json2CSharpCodeGenerator.WinForms.csproj new file mode 100644 index 0000000..881f5cd --- /dev/null +++ b/Json2CSharpCodeGenerator.WinForms/Json2CSharpCodeGenerator.WinForms.csproj @@ -0,0 +1,14 @@ + + + enable + 10.0 + disable + WinExe + win-x64 + net6.0-windows + true + + + + + \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.WinForms/MainForm.Designer.cs b/Json2CSharpCodeGenerator.WinForms/MainForm.Designer.cs new file mode 100644 index 0000000..750e28a --- /dev/null +++ b/Json2CSharpCodeGenerator.WinForms/MainForm.Designer.cs @@ -0,0 +1,386 @@ + +namespace Json2CSharpCodeGenerator.Lib.WinForms +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose( bool disposing ) + { + if( disposing && ( components != null ) ) + { + components.Dispose(); + } + base.Dispose( disposing ); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); + this.split = new System.Windows.Forms.SplitContainer(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.jsonInputTextbox = new System.Windows.Forms.TextBox(); + this.inputLabel = new System.Windows.Forms.Label(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.csharpOutputTextbox = new System.Windows.Forms.TextBox(); + this.outputLabel = new System.Windows.Forms.Label(); + this.toolStrip = new System.Windows.Forms.ToolStrip(); + this.openButton = new System.Windows.Forms.ToolStripButton(); + this.optsPascalCase = new System.Windows.Forms.ToolStripButton(); + this.sep1 = new System.Windows.Forms.ToolStripSeparator(); + this.optsAttributeMode = new System.Windows.Forms.ToolStripDropDownButton(); + this.optAttribJP = new System.Windows.Forms.ToolStripMenuItem(); + this.optAttribJpn = new System.Windows.Forms.ToolStripMenuItem(); + this.sep2 = new System.Windows.Forms.ToolStripSeparator(); + this.optMembersMode = new System.Windows.Forms.ToolStripDropDownButton(); + this.optMemberProps = new System.Windows.Forms.ToolStripMenuItem(); + this.optMemberFields = new System.Windows.Forms.ToolStripMenuItem(); + this.sep3 = new System.Windows.Forms.ToolStripSeparator(); + this.copyOutput = new System.Windows.Forms.ToolStripButton(); + this.wrapText = new System.Windows.Forms.ToolStripButton(); + this.optTypesMode = new System.Windows.Forms.ToolStripDropDownButton(); + this.optTypesMutablePoco = new System.Windows.Forms.ToolStripMenuItem(); + this.optTypesImmutablePoco = new System.Windows.Forms.ToolStripMenuItem(); + this.optTypesRecords = new System.Windows.Forms.ToolStripMenuItem(); + this.statusStrip = new System.Windows.Forms.StatusStrip(); + this.ofd = new System.Windows.Forms.OpenFileDialog(); + ((System.ComponentModel.ISupportInitialize)(this.split)).BeginInit(); + this.split.Panel1.SuspendLayout(); + this.split.Panel2.SuspendLayout(); + this.split.SuspendLayout(); + this.tableLayoutPanel1.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); + this.toolStrip.SuspendLayout(); + this.SuspendLayout(); + // + // split + // + this.split.Dock = System.Windows.Forms.DockStyle.Fill; + this.split.Location = new System.Drawing.Point(0, 25); + this.split.Name = "split"; + // + // split.Panel1 + // + this.split.Panel1.Controls.Add(this.tableLayoutPanel1); + // + // split.Panel2 + // + this.split.Panel2.Controls.Add(this.tableLayoutPanel2); + this.split.Size = new System.Drawing.Size(1288, 253); + this.split.SplitterDistance = 644; + this.split.TabIndex = 0; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.AllowDrop = true; + this.tableLayoutPanel1.ColumnCount = 1; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.jsonInputTextbox, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.inputLabel, 0, 0); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 2; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.Size = new System.Drawing.Size(644, 253); + this.tableLayoutPanel1.TabIndex = 1; + // + // jsonInputTextbox + // + this.jsonInputTextbox.AcceptsReturn = true; + this.jsonInputTextbox.AcceptsTab = true; + this.jsonInputTextbox.AllowDrop = true; + this.jsonInputTextbox.Dock = System.Windows.Forms.DockStyle.Fill; + this.jsonInputTextbox.Location = new System.Drawing.Point(3, 32); + this.jsonInputTextbox.Multiline = true; + this.jsonInputTextbox.Name = "jsonInputTextbox"; + this.jsonInputTextbox.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.jsonInputTextbox.Size = new System.Drawing.Size(638, 218); + this.jsonInputTextbox.TabIndex = 0; + this.jsonInputTextbox.WordWrap = false; + // + // inputLabel + // + this.inputLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.inputLabel.Location = new System.Drawing.Point(1, 4); + this.inputLabel.Margin = new System.Windows.Forms.Padding(1, 4, 4, 2); + this.inputLabel.Name = "inputLabel"; + this.inputLabel.Size = new System.Drawing.Size(639, 23); + this.inputLabel.TabIndex = 1; + this.inputLabel.Text = "Paste JSON Input or drag and drop a *.json file:"; + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.ColumnCount = 1; + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.Controls.Add(this.csharpOutputTextbox, 0, 1); + this.tableLayoutPanel2.Controls.Add(this.outputLabel, 0, 0); + this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 2; + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel2.Size = new System.Drawing.Size(640, 253); + this.tableLayoutPanel2.TabIndex = 1; + // + // csharpOutputTextbox + // + this.csharpOutputTextbox.Dock = System.Windows.Forms.DockStyle.Fill; + this.csharpOutputTextbox.Location = new System.Drawing.Point(3, 32); + this.csharpOutputTextbox.Multiline = true; + this.csharpOutputTextbox.Name = "csharpOutputTextbox"; + this.csharpOutputTextbox.ReadOnly = true; + this.csharpOutputTextbox.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.csharpOutputTextbox.Size = new System.Drawing.Size(634, 218); + this.csharpOutputTextbox.TabIndex = 0; + this.csharpOutputTextbox.WordWrap = false; + // + // outputLabel + // + this.outputLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.outputLabel.Location = new System.Drawing.Point(1, 4); + this.outputLabel.Margin = new System.Windows.Forms.Padding(1, 4, 4, 2); + this.outputLabel.Name = "outputLabel"; + this.outputLabel.Size = new System.Drawing.Size(635, 23); + this.outputLabel.TabIndex = 1; + this.outputLabel.Text = "Generated C# output:"; + // + // toolStrip + // + this.toolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.openButton, + this.optsPascalCase, + this.sep1, + this.optsAttributeMode, + this.sep2, + this.optMembersMode, + this.sep3, + this.copyOutput, + this.wrapText, + this.optTypesMode}); + this.toolStrip.Location = new System.Drawing.Point(0, 0); + this.toolStrip.Name = "toolStrip"; + this.toolStrip.Size = new System.Drawing.Size(1288, 25); + this.toolStrip.TabIndex = 0; + // + // openButton + // + this.openButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + this.openButton.Name = "openButton"; + this.openButton.Size = new System.Drawing.Size(49, 22); + this.openButton.Text = "Open..."; + // + // optsPascalCase + // + this.optsPascalCase.Checked = true; + this.optsPascalCase.CheckOnClick = true; + this.optsPascalCase.CheckState = System.Windows.Forms.CheckState.Checked; + this.optsPascalCase.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + this.optsPascalCase.Name = "optsPascalCase"; + this.optsPascalCase.Size = new System.Drawing.Size(94, 22); + this.optsPascalCase.Text = "Use Pascal Case"; + // + // sep1 + // + this.sep1.Name = "sep1"; + this.sep1.Size = new System.Drawing.Size(6, 25); + // + // optsAttributeMode + // + this.optsAttributeMode.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + this.optsAttributeMode.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.optAttribJP, + this.optAttribJpn}); + this.optsAttributeMode.Name = "optsAttributeMode"; + this.optsAttributeMode.Size = new System.Drawing.Size(106, 22); + this.optsAttributeMode.Text = "Attributes mode"; + // + // optAttribJP + // + this.optAttribJP.CheckOnClick = true; + this.optAttribJP.Name = "optAttribJP"; + this.optAttribJP.Size = new System.Drawing.Size(204, 22); + this.optAttribJP.Text = "Use [JsonProperty]"; + // + // optAttribJpn + // + this.optAttribJpn.Checked = true; + this.optAttribJpn.CheckOnClick = true; + this.optAttribJpn.Name = "optAttribJpn"; + this.optAttribJpn.CheckState = System.Windows.Forms.CheckState.Checked; + this.optAttribJpn.Size = new System.Drawing.Size(204, 22); + this.optAttribJpn.Text = "Use [JsonPropertyName]"; + // + // sep2 + // + this.sep2.Name = "sep2"; + this.sep2.Size = new System.Drawing.Size(6, 25); + // + // optMembersMode + // + this.optMembersMode.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + this.optMembersMode.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.optMemberProps, + this.optMemberFields}); + this.optMembersMode.Name = "optMembersMode"; + this.optMembersMode.Size = new System.Drawing.Size(99, 22); + this.optMembersMode.Text = "Member mode"; + // + // optMemberProps + // + this.optMemberProps.Checked = true; + this.optMemberProps.CheckOnClick = true; + this.optMemberProps.CheckState = System.Windows.Forms.CheckState.Checked; + this.optMemberProps.Name = "optMemberProps"; + this.optMemberProps.Size = new System.Drawing.Size(180, 22); + this.optMemberProps.Text = "Use properties"; + // + // optMemberFields + // + this.optMemberFields.CheckOnClick = true; + this.optMemberFields.Name = "optMemberFields"; + this.optMemberFields.Size = new System.Drawing.Size(180, 22); + this.optMemberFields.Text = "Use fields"; + // + // sep3 + // + this.sep3.Name = "sep3"; + this.sep3.Size = new System.Drawing.Size(6, 25); + // + // copyOutput + // + this.copyOutput.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; + this.copyOutput.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + this.copyOutput.Name = "copyOutput"; + this.copyOutput.Size = new System.Drawing.Size(167, 22); + this.copyOutput.Text = "Copy C# Output to Clipboard"; + // + // wrapText + // + this.wrapText.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; + this.wrapText.CheckOnClick = true; + this.wrapText.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + this.wrapText.Name = "wrapText"; + this.wrapText.Size = new System.Drawing.Size(62, 22); + this.wrapText.Text = "Wrap text"; + // + // optTypesMode + // + this.optTypesMode.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + this.optTypesMode.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.optTypesMutablePoco, + this.optTypesImmutablePoco, + this.optTypesRecords}); + this.optTypesMode.Name = "optTypesMode"; + this.optTypesMode.Size = new System.Drawing.Size(89, 22); + this.optTypesMode.Text = "Output types"; + // + // optTypesMutablePoco + // + this.optTypesMutablePoco.CheckOnClick = true; + this.optTypesMutablePoco.Name = "optTypesMutablePoco"; + this.optTypesMutablePoco.Size = new System.Drawing.Size(208, 22); + this.optTypesMutablePoco.Text = "Mutable POCO classes"; + // + // optTypesImmutablePoco + // + this.optTypesImmutablePoco.Checked = true; + this.optTypesImmutablePoco.CheckOnClick = true; + this.optTypesImmutablePoco.CheckState = System.Windows.Forms.CheckState.Checked; + this.optTypesImmutablePoco.Name = "optTypesImmutablePoco"; + this.optTypesImmutablePoco.Size = new System.Drawing.Size(208, 22); + this.optTypesImmutablePoco.Text = "Immutable POCO classes"; + // + // optTypesRecords + // + this.optTypesRecords.CheckOnClick = true; + this.optTypesRecords.Name = "optTypesRecords"; + this.optTypesRecords.Size = new System.Drawing.Size(208, 22); + this.optTypesRecords.Text = "Immutable record classes"; + // + // statusStrip + // + this.statusStrip.Location = new System.Drawing.Point(0, 278); + this.statusStrip.Name = "statusStrip"; + this.statusStrip.Size = new System.Drawing.Size(1288, 22); + this.statusStrip.TabIndex = 1; + // + // ofd + // + this.ofd.Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*"; + this.ofd.SupportMultiDottedExtensions = true; + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1288, 300); + this.Controls.Add(this.split); + this.Controls.Add(this.statusStrip); + this.Controls.Add(this.toolStrip); + this.DoubleBuffered = true; + this.Name = "MainForm"; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Show; + this.Text = "JSON-to-C#"; + this.split.Panel1.ResumeLayout(false); + this.split.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.split)).EndInit(); + this.split.ResumeLayout(false); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); + this.toolStrip.ResumeLayout(false); + this.toolStrip.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.SplitContainer split; + private System.Windows.Forms.ToolStrip toolStrip; + private System.Windows.Forms.ToolStripButton optsPascalCase; + private System.Windows.Forms.ToolStripMenuItem optAttribJpn; + private System.Windows.Forms.ToolStripDropDownButton optMembersMode; + private System.Windows.Forms.ToolStripMenuItem optMemberProps; + private System.Windows.Forms.ToolStripMenuItem optMemberFields; + private System.Windows.Forms.TextBox jsonInputTextbox; + private System.Windows.Forms.TextBox csharpOutputTextbox; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; + private System.Windows.Forms.Label inputLabel; + private System.Windows.Forms.Label outputLabel; + private System.Windows.Forms.ToolStripSeparator sep1; + private System.Windows.Forms.ToolStripSeparator sep2; + private System.Windows.Forms.ToolStripSeparator sep3; + private System.Windows.Forms.ToolStripDropDownButton optsAttributeMode; + private System.Windows.Forms.ToolStripMenuItem optAttribJP; + private System.Windows.Forms.ToolStripButton copyOutput; + private System.Windows.Forms.StatusStrip statusStrip; + private System.Windows.Forms.ToolStripButton openButton; + private System.Windows.Forms.OpenFileDialog ofd; + private System.Windows.Forms.ToolStripButton wrapText; + private System.Windows.Forms.ToolStripDropDownButton optTypesMode; + private System.Windows.Forms.ToolStripMenuItem optTypesMutablePoco; + private System.Windows.Forms.ToolStripMenuItem optTypesImmutablePoco; + private System.Windows.Forms.ToolStripMenuItem optTypesRecords; + } +} + diff --git a/Json2CSharpCodeGenerator.WinForms/MainForm.cs b/Json2CSharpCodeGenerator.WinForms/MainForm.cs new file mode 100644 index 0000000..2d86b24 --- /dev/null +++ b/Json2CSharpCodeGenerator.WinForms/MainForm.cs @@ -0,0 +1,496 @@ +using Microsoft.Win32; +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; + +namespace Json2CSharpCodeGenerator.Lib.WinForms; + +public partial class MainForm : Form +{ + private bool _PreventReentrancy = false; + + public MainForm() + { + // `IconTitleFont` is what WinForms *should* be using by default. + // Need to set `Font` first, before `InitializeComponent();` to ensure font inheritance by controls in the form. + Font = SystemFonts.IconTitleFont; + + InitializeComponent(); + + ResetFonts(); + + // Also: https://docs.microsoft.com/en-us/dotnet/desktop/winforms/how-to-respond-to-font-scheme-changes-in-a-windows-forms-application?view=netframeworkdesktop-4.8 + SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged; + + // + + openButton.Click += OpenButton_Click; + + optAttribJP.CheckedChanged += OnAttributesModeCheckedChanged; + optAttribJpn.CheckedChanged += OnAttributesModeCheckedChanged; + //optAttribNone .CheckedChanged += OnAttributesModeCheckedChanged; + + optMemberFields.CheckedChanged += OnMemberModeCheckedChanged; + optMemberProps.CheckedChanged += OnMemberModeCheckedChanged; + + optTypesMutablePoco.CheckedChanged += OnOutputTypeModeCheckedChanged; + optTypesImmutablePoco.CheckedChanged += OnOutputTypeModeCheckedChanged; + optTypesRecords.CheckedChanged += OnOutputTypeModeCheckedChanged; + + optsPascalCase.CheckedChanged += OnOptionsChanged; + + wrapText.CheckedChanged += WrapText_CheckedChanged; + + copyOutput.Click += CopyOutput_Click; + copyOutput.Enabled = false; + + jsonInputTextbox.TextChanged += JsonInputTextbox_TextChanged; + jsonInputTextbox.DragDrop += JsonInputTextbox_DragDrop; + jsonInputTextbox.DragOver += JsonInputTextbox_DragOver; + //jsonInputTextbox.paste // annoyingly, it isn't (easily) feasible to hook/detect TextBox paste events, even globally... grrr. + + // Invoke event-handlers to set initial toolstrip text: + optsAttributeMode.Tag = optsAttributeMode.Text + ": {0}"; + optMembersMode.Tag = optMembersMode.Text + ": {0}"; + optTypesMode.Tag = optTypesMode.Text + ": {0}"; + + OnAttributesModeCheckedChanged(optAttribJP, EventArgs.Empty); + OnMemberModeCheckedChanged(optMemberProps, EventArgs.Empty); + OnOutputTypeModeCheckedChanged(optTypesMutablePoco, EventArgs.Empty); + } + + private void WrapText_CheckedChanged(object sender, EventArgs e) + { + ToolStripButton tsb = (ToolStripButton)sender; + + // For some reason, toggling WordWrap causes a text selection in `jsonInputTextbox`. So, doing this: + try + { + jsonInputTextbox.HideSelection = true; + // ayayayay: https://stackoverflow.com/questions/1140250/how-to-remove-the-focus-from-a-textbox-in-winforms + ActiveControl = toolStrip; + +#if WINFORMS_TEXTBOX_GET_SCROLL_POSITION_WORKS_ARGH // It's non-trivial: https://stackoverflow.com/questions/4494162/change-scrollbar-position-in-textbox + //int idx1 = jsonInputTextbox.GetFirstCharIndexOfCurrentLine(); // but what is the "current line"? + int firstLineCharIndex = -1; + if( jsonInputTextbox.Height > 10 ) + { + // https://stackoverflow.com/questions/10175400/maintain-textbox-scroll-position-while-adding-line + jsonInputTextbox.GetCharIndexFromPosition( new Point( 3, 3 ) ); + } +#endif + + jsonInputTextbox.WordWrap = tsb.Checked; + csharpOutputTextbox.WordWrap = tsb.Checked; + +#if WINFORMS_TEXTBOX_GET_SCROLL_POSITION_WORKS_ARGH + if( firstLineCharIndex > 0 ) // Greater than zero, not -1, because `GetCharIndexFromPosition` returns a meaningless zero sometimes. + { + jsonInputTextbox.SelectionStart = firstLineCharIndex; + jsonInputTextbox.ScrollToCaret(); + } +#endif + } + finally + { + jsonInputTextbox.HideSelection = false; + } + } + + #region WinForms Taxes + + private static Font GetMonospaceFont(float emFontSizePoints) + { + // See if Consolas or Lucida Sans Typewriter is available before falling-back: + string[] preferredFonts = new[] { "Consolas", "Lucida Sans Typewriter" }; + foreach (string fontName in preferredFonts) + { + if (TestFont(fontName, emFontSizePoints)) + { + return new Font(fontName, emFontSizePoints, FontStyle.Regular); + } + } + + // Fallback: + return new Font(FontFamily.GenericMonospace, emSize: emFontSizePoints); + } + + private static bool TestFont(string fontName, float emFontSizePoints) + { + try + { + using Font test = new(fontName, emFontSizePoints, FontStyle.Regular); + return test.Name == fontName; + } + catch + { + return false; + } + } + + private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) + { + switch (e.Category) + { + case UserPreferenceCategory.Accessibility: + case UserPreferenceCategory.Window: + case UserPreferenceCategory.VisualStyle: + case UserPreferenceCategory.Menu: + ResetFonts(); + break; + } + } + + private void ResetFonts() + { + Font = SystemFonts.IconTitleFont; + + Font monospaceFont = GetMonospaceFont(emFontSizePoints: SystemFonts.IconTitleFont.SizeInPoints); + jsonInputTextbox.Font = monospaceFont; + csharpOutputTextbox.Font = monospaceFont; + } + + protected override void OnFormClosing(FormClosingEventArgs e) + { + base.OnFormClosing(e); + + if (!e.Cancel) + { + SystemEvents.UserPreferenceChanged -= new UserPreferenceChangedEventHandler(SystemEvents_UserPreferenceChanged); + } + } + + #endregion + + #region Methods to ensure only a single checkbox-style menu item is checked at-a-time, and that the ToolStripDropDownButton's text indicates the currently selected option: + + private void OnAttributesModeCheckedChanged(object sender, EventArgs e) + { + EnsureSingleCheckedDropDownItemAndUpdateToolStripItemText((ToolStripMenuItem)sender, defaultItem: optAttribJP, parent: optsAttributeMode); + + GenerateCSharp(); + } + + private void OnMemberModeCheckedChanged(object sender, EventArgs e) + { + EnsureSingleCheckedDropDownItemAndUpdateToolStripItemText((ToolStripMenuItem)sender, defaultItem: optMemberProps, parent: optMembersMode); + + GenerateCSharp(); + } + + private void OnOutputTypeModeCheckedChanged(object sender, EventArgs e) + { + EnsureSingleCheckedDropDownItemAndUpdateToolStripItemText((ToolStripMenuItem)sender, defaultItem: optTypesMutablePoco, parent: optTypesMode); + + GenerateCSharp(); + } + + private void EnsureSingleCheckedDropDownItemAndUpdateToolStripItemText(ToolStripMenuItem subject, ToolStripMenuItem defaultItem, ToolStripDropDownButton parent) + { + if (_PreventReentrancy) + return; + try + { + _PreventReentrancy = true; + + ToolStripMenuItem singleCheckedItem; + if (subject.Checked) + { + singleCheckedItem = subject; + UncheckOthers(subject, parent); + } + else + { + EnsureAtLeast1IsCheckedAfterItemWasUnchecked(subject, defaultItem, parent); + singleCheckedItem = parent.DropDownItems.Cast().Single(item => item.Checked); + } + + string parentTextFormat = (string)parent.Tag; + parent.Text = string.Format(format: parentTextFormat, arg0: singleCheckedItem.Text); + } + finally + { + _PreventReentrancy = false; + } + } + + private static void UncheckOthers(ToolStripMenuItem sender, ToolStripDropDownButton parent) + { + foreach (ToolStripMenuItem menuItem in parent.DropDownItems.Cast()) // I really hate old-style IEnumerable, *grumble* + { + if (!ReferenceEquals(menuItem, sender)) + { + menuItem.Checked = false; + } + } + } + + private static void EnsureAtLeast1IsCheckedAfterItemWasUnchecked(ToolStripMenuItem subject, ToolStripMenuItem defaultItem, ToolStripDropDownButton parent) + { + int countChecked = parent.DropDownItems.Cast().Count(item => item.Checked); + + if (countChecked == 1) + { + // Is exactly 1 checked already? If so, then NOOP. + } + else if (countChecked > 1) + { + // If more than 1 are checked, then check only the default: + defaultItem.Checked = true; + UncheckOthers(sender: defaultItem, parent); + } + else + { + // If none are checked, then *if* the unchecked item is NOT the default item, then check the default item: + if (!ReferenceEquals(subject, defaultItem)) + { + defaultItem.Checked = true; + } + else + { + // Otherwise, check the first non-default item: + ToolStripMenuItem nextBestItem = parent.DropDownItems.Cast().First(item => item != defaultItem); + nextBestItem.Checked = true; + } + } + } + + #endregion + + private void OnOptionsChanged(object sender, EventArgs e) => GenerateCSharp(); + + #region Drag and Drop + + private void JsonInputTextbox_DragOver(object sender, DragEventArgs e) + { + bool acceptable = + e.Data.GetDataPresent(DataFormats.FileDrop) || + // e.Data.GetDataPresent( DataFormats.Text ) || + // e.Data.GetDataPresent( DataFormats.OemText ) || + e.Data.GetDataPresent(DataFormats.UnicodeText, autoConvert: true)// || + // e.Data.GetDataPresent( DataFormats.Html ) || + // e.Data.GetDataPresent( DataFormats.StringFormat ) || + // e.Data.GetDataPresent( DataFormats.Rtf ) + ; + + if (acceptable) + { + e.Effect = DragDropEffects.Copy; + } + else + { + e.Effect = DragDropEffects.None; + } + } + + private void JsonInputTextbox_DragDrop(object sender, DragEventArgs e) + { + if (e.Data.GetDataPresent(DataFormats.FileDrop)) + { + string[] fileNames = (string[])e.Data.GetData(DataFormats.FileDrop); + if (fileNames.Length >= 1) + { + // hmm, maybe allow multiple files by concatenating them all into a horrible JSON array? :D + TryLoadJsonFile(fileNames[0]); + } + } + else if (e.Data.GetDataPresent(DataFormats.UnicodeText, autoConvert: true)) + { + statusStrip.Text = ""; + + string text = (string)e.Data.GetData(DataFormats.UnicodeText, autoConvert: true); + if (text != null) + { + jsonInputTextbox.Text = text; // This will invoke `GenerateCSharp()`. + + statusStrip.Text = "Loaded JSON from drag and drop data."; + } + } + } + + /// This regex won't match \r\n, only \n. + private static readonly Regex _OnlyUnixLineBreaks = new("(? -1 ) + // { + // const String fmt = "Invalid file path: \"{0}\""; + // csharpOutputTextbox.Text = String.Format( CultureInfo.CurrentCulture, fmt, filePath ); + // } + else + { + FileInfo jsonFileInfo; + try + { + jsonFileInfo = new FileInfo(filePath); + } + catch (Exception ex) + { + const string fmt = "Invalid file path: \"{0}\"\r\n{1}"; + csharpOutputTextbox.Text = string.Format(CultureInfo.CurrentCulture, fmt, filePath, ex.ToString()); + return; + } + + TryLoadJsonFile(jsonFileInfo); + } + } + + private void TryLoadJsonFile(FileInfo jsonFile) + { + if (jsonFile is null) + return; + + statusStrip.Text = ""; + + try + { + jsonFile.Refresh(); + if (jsonFile.Exists) + { + string jsonText = File.ReadAllText(jsonFile.FullName); + jsonInputTextbox.Text = jsonText; // This will invoke `GenerateCSharp()`. + + statusStrip.Text = "Loaded \"" + jsonFile.FullName + "\" successfully."; + } + else + { + csharpOutputTextbox.Text = string.Format(CultureInfo.CurrentCulture, "Error: File \"{0}\" does not exist.", jsonFile.FullName); + } + } + catch (Exception ex) + { + const string fmt = "Error loading file: \"{0}\"\r\n{1}"; + + csharpOutputTextbox.Text = string.Format(CultureInfo.CurrentCulture, fmt, jsonFile.FullName, ex.ToString()); + } + } + + #endregion + + private void ConfigureGenerator(IJsonClassGeneratorConfig config) + { + config.UsePascalCase = optsPascalCase.Checked; + + // + + if (optAttribJP.Checked) + { + config.AttributeLibrary = JsonLibrary.NewtonsoftJson; + } + else// implicit: ( optAttribJpn.Checked ) + { + config.AttributeLibrary = JsonLibrary.SystemTextJson; + } + + // + + if (optMemberProps.Checked) + { + config.MutableClasses.Members = OutputMembers.AsProperties; + } + else// implicit: ( optMemberFields.Checked ) + { + config.MutableClasses.Members = OutputMembers.AsPublicFields; + } + + // + + if (optTypesImmutablePoco.Checked) + { + config.OutputType = OutputTypes.ImmutableClass; + } + else if (optTypesMutablePoco.Checked) + { + config.OutputType = OutputTypes.MutableClass; + } + else// implicit: ( optTypesRecords.Checked ) + { + config.OutputType = OutputTypes.ImmutableRecord; + } + } + + private void JsonInputTextbox_TextChanged(object sender, EventArgs e) + { + if (_PreventReentrancy) + return; + _PreventReentrancy = true; + try + { + jsonInputTextbox.Text = RepairLineBreaks(jsonInputTextbox.Text); + + GenerateCSharp(); + } + finally + { + _PreventReentrancy = false; + } + } + + private void GenerateCSharp() + { + copyOutput.Enabled = false; + + string jsonText = jsonInputTextbox.Text; + if (string.IsNullOrWhiteSpace(jsonText)) + { + csharpOutputTextbox.Text = string.Empty; + return; + } + + JsonClassGenerator generator = new(); + ConfigureGenerator(generator); + + try + { + StringBuilder sb = generator.GenerateClasses(jsonText, errorMessage: out string errorMessage); + if (!string.IsNullOrWhiteSpace(errorMessage)) + { + csharpOutputTextbox.Text = "Error:\r\n" + errorMessage; + } + else + { + csharpOutputTextbox.Text = sb.ToString(); + copyOutput.Enabled = true; + } + } + catch (Exception ex) + { + csharpOutputTextbox.Text = "Error:\r\n" + ex.ToString(); + } + } + + private void CopyOutput_Click(object sender, EventArgs e) + { + if (csharpOutputTextbox.Text?.Length > 0) + { + Clipboard.SetText(csharpOutputTextbox.Text, TextDataFormat.UnicodeText); + } + } +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.WinForms/Program.cs b/Json2CSharpCodeGenerator.WinForms/Program.cs new file mode 100644 index 0000000..a8c88cd --- /dev/null +++ b/Json2CSharpCodeGenerator.WinForms/Program.cs @@ -0,0 +1,16 @@ +namespace Json2CSharpCodeGenerator.Lib.WinForms; + +static class Program +{ + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); + Application.Run(new MainForm()); + } +} \ No newline at end of file diff --git a/Json2CSharpCodeGenerator.sln b/Json2CSharpCodeGenerator.sln new file mode 100644 index 0000000..739c430 --- /dev/null +++ b/Json2CSharpCodeGenerator.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Json2CSharpCodeGenerator.Lib", "Json2CSharpCodeGenerator.Lib\Json2CSharpCodeGenerator.Lib.csproj", "{8CC1EF05-B5FD-457A-98F4-603D81427955}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Json2CSharpCodeGenerator.WinForms", "Json2CSharpCodeGenerator.WinForms\Json2CSharpCodeGenerator.WinForms.csproj", "{0B91EC07-6FAA-4A51-BECD-8EA156B04976}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8CC1EF05-B5FD-457A-98F4-603D81427955}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CC1EF05-B5FD-457A-98F4-603D81427955}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CC1EF05-B5FD-457A-98F4-603D81427955}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CC1EF05-B5FD-457A-98F4-603D81427955}.Release|Any CPU.Build.0 = Release|Any CPU + {0B91EC07-6FAA-4A51-BECD-8EA156B04976}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B91EC07-6FAA-4A51-BECD-8EA156B04976}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B91EC07-6FAA-4A51-BECD-8EA156B04976}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B91EC07-6FAA-4A51-BECD-8EA156B04976}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..4793fc8 --- /dev/null +++ b/License.txt @@ -0,0 +1,64 @@ +Microsoft Reciprocal License (Ms-RL) + +This license governs use of the accompanying software. If you use the +software, you accept this license. If you do not accept the license, do not +use the software. + +1. Definitions +The terms "reproduce," "reproduction," "derivative works," and "distribution" +have the same meaning here as under U.S. copyright law. + +A "contribution" is the original software, or any additions or changes to the +software. + +A "contributor" is any person that distributes its contribution under this +license. + +"Licensed patents" are a contributor's patent claims that read directly on its +contribution. + +2. Grant of Rights + (A) Copyright Grant- Subject to the terms of this license, including the + license conditions and limitations in section 3, each contributor grants + you a non-exclusive, worldwide, royalty-free copyright license to + reproduce its contribution, prepare derivative works of its contribution, + and distribute its contribution or any derivative works that you create. + + (B) Patent Grant- Subject to the terms of this license, including the + license conditions and limitations in section 3, each contributor grants + you a non-exclusive, worldwide, royalty-free license under its licensed + patents to make, have made, use, sell, offer for sale, import, and/or + otherwise dispose of its contribution in the software or derivative works + of the contribution in the software. + +3. Conditions and Limitations + (A) Reciprocal Grants- For any file you distribute that contains code + from the software (in source code or binary format), you must provide + recipients the source code to that file along with a copy of this + license, which license will govern that file. You may license other files + that are entirely your own work and do not contain code from the software + under any terms you choose. + + (B) No Trademark License- This license does not grant you rights to use + any contributors' name, logo, or trademarks. + + (C) If you bring a patent claim against any contributor over patents that + you claim are infringed by the software, your patent license from such + contributor to the software ends automatically. + + (D) If you distribute any portion of the software, you must retain all + copyright, patent, trademark, and attribution notices that are present in + the software. + + (E) If you distribute any portion of the software in source code form, + you may do so only under this license by including a complete copy of + this license with your distribution. If you distribute any portion of the + software in compiled or object code form, you may only do so under a + license that complies with this license. + + (F) The software is licensed "as-is." You bear the risk of using it. The + contributors give no express warranties, guarantees, or conditions. You + may have additional consumer rights under your local laws which this + license cannot change. To the extent permitted under your local laws, the + contributors exclude the implied warranties of merchantability, fitness + for a particular purpose and non-infringement. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a1878b --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +## Special Thanks to Our Contributors !! +Updated Pluralization Package & Test cleanup +[marblekirby](https://github.com/marblekirby)
+ +Implemented Immutable Classes Feature
+[Holly-HaCKer](https://github.com/HoLLy-HaCKeR)
+ +Implemented Immutable Classes Feature
+[Jehoel](https://github.com/Jehoel)
+ + Assisted in Bug Fixing
+ [tyeth](https://github.com/tyeth), [dogac00](https://github.com/dogac00) + +## Contribution Guidelines +1 - New ideas, suggestions and improvements are welcomed.
+2 - Create a unit test with the expected output and work from there
+3 - If you're submitting a new bug, check to see if It's already submitted. Propose a brief solution to the problem to make it easier for other people to fix.
+4 - Enjoy coding :) !
+ +## Bug Fixing +### 1- Choose a problem from the issues labeled "Bug" or "Help Wanted" +### 2- Clone the repository and build it +### 3- Create a Unit Test using "CreatTest.ps1" Powershell Script +**>>>>> Unit Test Name should be in the format of "[TestNumber]_[DescribeProblem]"** +* Run the script in Powershell +* This will create 3 files: the csharp test, the Json Input text file, and the Output C# or JAVA etc.. file +![alt text](https://json2csharp.azureedge.net/images/github-repo-images/Test%20Files.png) +* Start Debugging, Put some test Json and Get to Know the solution +* +## Implementing New Code Generators : F#, Typescript etc.. ? +Implement the below functions +![alt text](https://json2csharp.azureedge.net/images/github-repo-images/IMPLEMENT.png) + +