feat(JSON to C#): update to add options from json2csharp.com

rootTypeName (choose root object name)
pascalCase (type and member names)
useFields (instead of properties)
useNullable (types)
addJsonProperty (Newtonsoft) and nullValueHandlingIgnore
addJsonPropertyName (System.Text.Json)
generateImmutableClasses
useRecordTypes
useReadonlyLists
This commit is contained in:
ShareVB 2025-03-08 21:48:19 +01:00
parent 748126f207
commit 63c842bdb3
8 changed files with 1106 additions and 64 deletions

1
components.d.ts vendored
View file

@ -140,6 +140,7 @@ declare module '@vue/runtime-core' {
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSpace: typeof import('naive-ui')['NSpace']
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']

View file

@ -64,7 +64,6 @@
"highlight.js": "^11.7.0",
"iarna-toml-esm": "^3.0.5",
"ibantools": "^4.3.3",
"json2csharp": "^1.0.3",
"json5": "^2.2.3",
"jwt-decode": "^3.1.2",
"libphonenumber-js": "^1.10.28",

200
pnpm-lock.yaml generated
View file

@ -37,7 +37,7 @@ dependencies:
version: 10.3.0(vue@3.3.4)
'@vueuse/head':
specifier: ^1.0.0
version: 1.0.0(vue@3.3.4)
version: 1.0.0(typescript@5.2.2)(vue@3.3.4)
'@vueuse/router':
specifier: ^10.0.0
version: 10.0.0(vue-router@4.1.6)(vue@3.3.4)
@ -92,9 +92,6 @@ dependencies:
ibantools:
specifier: ^4.3.3
version: 4.3.3
json2csharp:
specifier: ^1.0.3
version: 1.0.3
json5:
specifier: ^2.2.3
version: 2.2.3
@ -811,6 +808,11 @@ packages:
resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==}
engines: {node: '>=6.9.0'}
/@babel/helper-string-parser@7.25.9:
resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
engines: {node: '>=6.9.0'}
dev: false
/@babel/helper-validator-identifier@7.22.20:
resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
engines: {node: '>=6.9.0'}
@ -819,6 +821,11 @@ packages:
resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==}
engines: {node: '>=6.9.0'}
/@babel/helper-validator-identifier@7.25.9:
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
engines: {node: '>=6.9.0'}
dev: false
/@babel/helper-validator-option@7.22.15:
resolution: {integrity: sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==}
engines: {node: '>=6.9.0'}
@ -911,6 +918,14 @@ packages:
dependencies:
'@babel/types': 7.23.0
/@babel/parser@7.26.9:
resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.26.9
dev: false
/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.22.15(@babel/core@7.23.2):
resolution: {integrity: sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==}
engines: {node: '>=6.9.0'}
@ -1898,6 +1913,14 @@ packages:
'@babel/helper-validator-identifier': 7.22.20
to-fast-properties: 2.0.0
/@babel/types@7.26.9:
resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
dev: false
/@css-render/plugin-bem@0.15.12(css-render@0.15.12):
resolution: {integrity: sha512-Lq2jSOZn+wYQtsyaFj6QRz2EzAnd3iW5fZeHO1WSXQdVYwvwGX0ZiH3X2JQgtgYLT1yeGtrwrqJdNdMEUD2xTw==}
peerDependencies:
@ -2336,6 +2359,10 @@ packages:
/@jridgewell/sourcemap-codec@1.4.15:
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
/@jridgewell/sourcemap-codec@1.5.0:
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
dev: false
/@jridgewell/trace-mapping@0.3.18:
resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==}
dependencies:
@ -3347,18 +3374,18 @@ packages:
'@unhead/schema': 0.5.1
dev: false
/@unhead/vue@0.5.1(vue@3.3.4):
/@unhead/vue@0.5.1(typescript@5.2.2)(vue@3.3.4):
resolution: {integrity: sha512-s4y4uj3NMqaUs0K+WQXbWGj/2+Glk/DEJ9yeJOcJIiro/+IhUMByD71jyCM43Xn8YBPy14VY/ZYb9ZFU2WCZgw==}
peerDependencies:
vue: '>=2.7 || >=3'
dependencies:
'@unhead/dom': 0.5.1
'@unhead/schema': 0.5.1
'@vueuse/shared': 10.9.0(vue@3.3.4)
'@vueuse/shared': 12.8.2(typescript@5.2.2)
unhead: 0.5.1
vue: 3.3.4
transitivePeerDependencies:
- '@vue/composition-api'
- typescript
dev: false
/@unocss/astro@0.57.1(rollup@2.79.1)(vite@4.4.9):
@ -3747,6 +3774,16 @@ packages:
dev: true
optional: true
/@vue/compiler-core@3.5.13:
resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==}
dependencies:
'@babel/parser': 7.26.9
'@vue/shared': 3.5.13
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.2.1
dev: false
/@vue/compiler-dom@3.2.47:
resolution: {integrity: sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==}
dependencies:
@ -3768,6 +3805,13 @@ packages:
dev: true
optional: true
/@vue/compiler-dom@3.5.13:
resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==}
dependencies:
'@vue/compiler-core': 3.5.13
'@vue/shared': 3.5.13
dev: false
/@vue/compiler-sfc@3.2.47:
resolution: {integrity: sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==}
dependencies:
@ -3797,6 +3841,20 @@ packages:
postcss: 8.4.28
source-map-js: 1.0.2
/@vue/compiler-sfc@3.5.13:
resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==}
dependencies:
'@babel/parser': 7.26.9
'@vue/compiler-core': 3.5.13
'@vue/compiler-dom': 3.5.13
'@vue/compiler-ssr': 3.5.13
'@vue/shared': 3.5.13
estree-walker: 2.0.2
magic-string: 0.30.17
postcss: 8.5.3
source-map-js: 1.2.1
dev: false
/@vue/compiler-ssr@3.2.47:
resolution: {integrity: sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==}
dependencies:
@ -3819,6 +3877,13 @@ packages:
dev: true
optional: true
/@vue/compiler-ssr@3.5.13:
resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==}
dependencies:
'@vue/compiler-dom': 3.5.13
'@vue/shared': 3.5.13
dev: false
/@vue/devtools-api@6.5.0:
resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==}
@ -3865,12 +3930,25 @@ packages:
dependencies:
'@vue/shared': 3.3.4
/@vue/reactivity@3.5.13:
resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==}
dependencies:
'@vue/shared': 3.5.13
dev: false
/@vue/runtime-core@3.3.4:
resolution: {integrity: sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==}
dependencies:
'@vue/reactivity': 3.3.4
'@vue/shared': 3.3.4
/@vue/runtime-core@3.5.13:
resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==}
dependencies:
'@vue/reactivity': 3.5.13
'@vue/shared': 3.5.13
dev: false
/@vue/runtime-dom@3.3.4:
resolution: {integrity: sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==}
dependencies:
@ -3878,6 +3956,15 @@ packages:
'@vue/shared': 3.3.4
csstype: 3.1.2
/@vue/runtime-dom@3.5.13:
resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==}
dependencies:
'@vue/reactivity': 3.5.13
'@vue/runtime-core': 3.5.13
'@vue/shared': 3.5.13
csstype: 3.1.3
dev: false
/@vue/server-renderer@3.3.4(vue@3.3.4):
resolution: {integrity: sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==}
peerDependencies:
@ -3898,6 +3985,16 @@ packages:
dev: true
optional: true
/@vue/server-renderer@3.5.13(vue@3.5.13):
resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==}
peerDependencies:
vue: 3.5.13
dependencies:
'@vue/compiler-ssr': 3.5.13
'@vue/shared': 3.5.13
vue: 3.5.13(typescript@5.2.2)
dev: false
/@vue/shared@3.2.47:
resolution: {integrity: sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==}
dev: true
@ -3911,6 +4008,10 @@ packages:
dev: true
optional: true
/@vue/shared@3.5.13:
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
dev: false
/@vue/test-utils@2.3.2(vue@3.3.4):
resolution: {integrity: sha512-hJnVaYhbrIm0yBS0+e1Y0Sj85cMyAi+PAbK4JHqMRUZ6S622Goa+G7QzkRSyvCteG8wop7tipuEbHoZo26wsSA==}
peerDependencies:
@ -3948,17 +4049,17 @@ packages:
- vue
dev: false
/@vueuse/head@1.0.0(vue@3.3.4):
/@vueuse/head@1.0.0(typescript@5.2.2)(vue@3.3.4):
resolution: {integrity: sha512-wighjD6iLxEitpg6EDeS5dGDB9tcOSMhpblrAOKR6qBP93U3cjG72n0LhlBUD9miu41lNxXFVGHgSc6BVJ9BMg==}
peerDependencies:
vue: '>=2.7 || >=3'
dependencies:
'@unhead/schema': 0.5.1
'@unhead/ssr': 0.5.1
'@unhead/vue': 0.5.1(vue@3.3.4)
'@unhead/vue': 0.5.1(typescript@5.2.2)(vue@3.3.4)
vue: 3.3.4
transitivePeerDependencies:
- '@vue/composition-api'
- typescript
dev: false
/@vueuse/metadata@10.3.0:
@ -3996,13 +4097,12 @@ packages:
- vue
dev: false
/@vueuse/shared@10.9.0(vue@3.3.4):
resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==}
/@vueuse/shared@12.8.2(typescript@5.2.2):
resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==}
dependencies:
vue-demi: 0.14.7(vue@3.3.4)
vue: 3.5.13(typescript@5.2.2)
transitivePeerDependencies:
- '@vue/composition-api'
- vue
- typescript
dev: false
/@zhead/schema@1.0.0-beta.13:
@ -4715,6 +4815,10 @@ packages:
/csstype@3.1.2:
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
/csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
dev: false
/dash-get@1.0.2:
resolution: {integrity: sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==}
dev: false
@ -5004,7 +5108,6 @@ packages:
/entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
dev: true
/errno@0.1.8:
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
@ -6584,10 +6687,6 @@ packages:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
dev: true
/json2csharp@1.0.3:
resolution: {integrity: sha512-hbe5csNMtvYf4neEDvYRWkJf8fgxeheuMAfhG80f2HIpeUMunPvv+4F2fS1PLpXbdzBp6EMQjZiocRvKhtahgg==}
dev: false
/json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
@ -6772,6 +6871,12 @@ packages:
dependencies:
'@jridgewell/sourcemap-codec': 1.4.15
/magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
dev: false
/magic-string@0.30.2:
resolution: {integrity: sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==}
engines: {node: '>=12'}
@ -7005,6 +7110,12 @@ packages:
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
/nanoid@3.3.9:
resolution: {integrity: sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: false
/natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true
@ -7380,6 +7491,10 @@ packages:
/picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
/picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
dev: false
/picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
@ -7470,6 +7585,15 @@ packages:
source-map-js: 1.0.2
dev: true
/postcss@8.5.3:
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.9
picocolors: 1.1.1
source-map-js: 1.2.1
dev: false
/prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@ -8132,6 +8256,11 @@ packages:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
/source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
dev: false
/source-map-support@0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
dependencies:
@ -9158,21 +9287,6 @@ packages:
vue: 3.3.4
dev: false
/vue-demi@0.14.7(vue@3.3.4):
resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
peerDependencies:
'@vue/composition-api': ^1.0.0-rc.1
vue: ^3.0.0-0 || ^2.6.0
peerDependenciesMeta:
'@vue/composition-api':
optional: true
dependencies:
vue: 3.3.4
dev: false
/vue-eslint-parser@9.3.1(eslint@8.47.0):
resolution: {integrity: sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==}
engines: {node: ^14.17.0 || >=16.0.0}
@ -9239,6 +9353,22 @@ packages:
'@vue/server-renderer': 3.3.4(vue@3.3.4)
'@vue/shared': 3.3.4
/vue@3.5.13(typescript@5.2.2):
resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@vue/compiler-dom': 3.5.13
'@vue/compiler-sfc': 3.5.13
'@vue/runtime-dom': 3.5.13
'@vue/server-renderer': 3.5.13(vue@3.5.13)
'@vue/shared': 3.5.13
typescript: 5.2.2
dev: false
/vueuc@0.4.51(vue@3.3.4):
resolution: {integrity: sha512-pLiMChM4f+W8czlIClGvGBYo656lc2Y0/mXFSCydcSmnCR1izlKPGMgiYBGjbY9FDkFG8a2HEVz7t0DNzBWbDw==}
peerDependencies:

View file

@ -1,7 +1,8 @@
import { useRouteQuery } from '@vueuse/router';
import { computed } from 'vue';
import { useStorage } from '@vueuse/core';
export { useQueryParam };
export { useQueryParam, useQueryParamOrStorage };
const transformers = {
number: {
@ -16,6 +17,12 @@ const transformers = {
fromQuery: (value: string) => value.toLowerCase() === 'true',
toQuery: (value: boolean) => (value ? 'true' : 'false'),
},
object: {
fromQuery: (value: string) => {
return JSON.parse(value);
},
toQuery: (value: object) => JSON.stringify(value),
},
};
function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue: T }) {
@ -33,3 +40,27 @@ function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue:
},
});
}
function useQueryParamOrStorage<T>({ name, storageName, defaultValue }: { name: string; storageName: string; defaultValue: T }) {
const type = typeof defaultValue;
const transformer = transformers[type as keyof typeof transformers] ?? transformers.string;
const storageRef = useStorage(storageName, defaultValue);
const proxyDefaultValue = transformer.toQuery(defaultValue as never);
const proxy = useRouteQuery(name, proxyDefaultValue);
const r = ref(defaultValue);
watch(r,
(value) => {
proxy.value = transformer.toQuery(value as never);
storageRef.value = value as never;
},
{ deep: true });
r.value = (proxy.value && proxy.value !== proxyDefaultValue
? transformer.fromQuery(proxy.value) as unknown as T
: storageRef.value as T) as never;
return r;
}

View file

@ -1,9 +1,10 @@
<script setup lang="ts">
import JSON5 from 'json5';
import json2csharp from 'json2csharp';
import json2csharp from './json2csharp';
import type { UseValidationRule } from '@/composable/validation';
import TextareaCopyable from '@/components/TextareaCopyable.vue';
import { withDefaultOnError } from '@/utils/defaults';
import { useQueryParamOrStorage } from '@/composable/queryParams';
const defaultValue = `{
a:"n",
@ -14,9 +15,31 @@ const defaultValue = `{
}
}`;
const jsonInput = ref(defaultValue);
const useNewtonsoft = useStorage('json-to-csharp:newtonsoft', false);
const pascalCase = useQueryParamOrStorage({ name: 'pascalcase', storageName: 'json-c#:pc', defaultValue: true });
const useFields = useQueryParamOrStorage({ name: 'fields', storageName: 'json-c#:fi', defaultValue: false });
const useNullable = useQueryParamOrStorage({ name: 'nullable', storageName: 'json-c#:null', defaultValue: false });
const addJsonProperty = useQueryParamOrStorage({ name: 'pascal', storageName: 'json-c#:jsp', defaultValue: false });
const nullValueHandlingIgnore = useQueryParamOrStorage({ name: 'pascal', storageName: 'json-c#:ign', defaultValue: true });
const addJsonPropertyName = useQueryParamOrStorage({ name: 'pascal', storageName: 'json-c#:jspp', defaultValue: true });
const generateImmutableClasses = useQueryParamOrStorage({ name: 'pascal', storageName: 'json-c#:imm', defaultValue: false });
const useRecordTypes = useQueryParamOrStorage({ name: 'pascal', storageName: 'json-c#:rec', defaultValue: true });
const useReadonlyLists = useQueryParamOrStorage({ name: 'pascal', storageName: 'json-c#:rdl', defaultValue: false });
const rootTypeName = useQueryParamOrStorage({ name: 'root', storageName: 'json-c#:rt', defaultValue: 'Root' });
const csharpOutput = computed(() => withDefaultOnError(
() => json2csharp(JSON.stringify(JSON5.parse(jsonInput.value)), useNewtonsoft.value), ''));
() => json2csharp({
src: JSON5.parse(jsonInput.value),
rootTypeName: rootTypeName.value,
pascalCase: pascalCase.value,
useFields: useFields.value,
useNullable: useNullable.value,
addJsonProperty: addJsonProperty.value,
nullValueHandlingIgnore: nullValueHandlingIgnore.value,
addJsonPropertyName: addJsonPropertyName.value,
generateImmutableClasses: generateImmutableClasses.value,
useRecordTypes: useRecordTypes.value,
useReadonlyLists: useReadonlyLists.value,
}), ''));
const rules: UseValidationRule<string>[] = [
{
validator: (v: string) => v === '' || JSON5.parse(v),
@ -26,25 +49,54 @@ const rules: UseValidationRule<string>[] = [
</script>
<template>
<div>
<c-card title="JSON to C#">
<c-input-text
v-model:value="jsonInput"
multiline
placeholder="Put your json string here..."
rows="20"
rows="10"
label="JSON to C#"
:validation-rules="rules"
raw-text
mb-5
/>
<n-checkbox v-model:checked="useNewtonsoft">
<span title="Use Newtonsoft Annotations">Use Newtonsoft Annotations</span>
<n-space justify="center">
<n-checkbox v-model:checked="pascalCase">
Use Pascal case
</n-checkbox>
<n-checkbox v-model:checked="useRecordTypes">
Use Record types
</n-checkbox>
<n-checkbox v-model:checked="generateImmutableClasses">
Generate Immutable classes
</n-checkbox>
<n-checkbox v-model:checked="useFields">
Use fields
</n-checkbox>
<n-checkbox v-model:checked="addJsonProperty">
Add JsonProperty attributes
</n-checkbox>
<n-checkbox v-model:checked="nullValueHandlingIgnore" :disabled="!addJsonProperty">
Use NullValueHandling.Ignore
</n-checkbox>
<n-checkbox v-model:checked="addJsonPropertyName">
Add JsonPropertyName attributes
</n-checkbox>
<n-checkbox v-model:checked="useNullable">
Use nullable types
</n-checkbox>
<n-checkbox v-model:checked="useReadonlyLists">
Use Readonly lists
</n-checkbox>
</n-space>
<c-input-text v-model:value="rootTypeName" label="Root type name" placeholder="Your root type name..." clearable raw-text mb-5 />
</c-card>
<c-card title="Your C# code">
<c-card title="Your C# code" mt-2>
<TextareaCopyable
:value="csharpOutput"
language="csharp"
/>
</c-card>
</div>
</template>

View file

@ -1,3 +0,0 @@
declare module 'json2csharp'{
export default function(json: string, useNewtonsoftAnnotations?: boolean): string;
}

View file

@ -0,0 +1,617 @@
import { describe, expect, it } from 'vitest';
import { getPrimitiveProp, isDate, json2classes, json2csharp } from './json2csharp';
// Test isDate function
describe('isDate', () => {
it('should return true for valid date strings', () => {
expect(isDate('2024-03-08')).toBe(true);
expect(isDate('2024-03-08T12:34:56Z')).toBe(true);
});
it('should return false for invalid date strings', () => {
expect(isDate('hello')).toBe(false);
expect(isDate('2024-13-08')).toBe(false); // Invalid month
expect(isDate('9999-99-99')).toBe(false); // Invalid format
});
});
// Test getPrimitiveProp function
describe('getPrimitiveProp', () => {
it('should return \'string\' type for non-date strings', () => {
expect(getPrimitiveProp('hello', 'testKey')).toEqual({
type: 'string',
name: 'testKey',
canBeNullable: false,
});
});
it('should return \'DateTime\' type for date strings', () => {
expect(getPrimitiveProp('2024-03-08', 'dateKey')).toEqual({
type: 'DateTime',
name: 'dateKey',
canBeNullable: true,
});
});
it('should return \'int\' for integers', () => {
expect(getPrimitiveProp(42, 'numberKey')).toEqual({
type: 'int',
name: 'numberKey',
canBeNullable: true,
});
});
it('should return \'double\' for floating point numbers', () => {
expect(getPrimitiveProp(3.14, 'floatKey')).toEqual({
type: 'double',
name: 'floatKey',
canBeNullable: true,
});
});
it('should return \'bool\' for boolean values', () => {
expect(getPrimitiveProp(true, 'boolKey')).toEqual({
type: 'bool',
name: 'boolKey',
canBeNullable: true,
});
});
it('should throw an error for unsupported types', () => {
expect(() => getPrimitiveProp({}, 'objKey')).toThrow('Unexpected key \'objKey\' of type object');
});
});
// Test json2classes function
describe('json2classes', () => {
it('should convert JSON to class definitions with custom root', () => {
const json = { name: 'John', age: 30 };
const result = json2classes(json, 'Main');
expect(result).to.deep.eq([
{
key: 'Main',
props: [
{ type: 'string', name: 'name', canBeNullable: false },
{ type: 'int', name: 'age', canBeNullable: true },
],
},
]);
});
it('should handle nested objects', () => {
const json = { user: { name: 'John' } };
const result = json2classes(json);
expect(result).to.deep.eq([
{ key: 'Root', props: [{ type: 'User', name: 'user' }] },
{ key: 'User', props: [{ type: 'string', name: 'name', canBeNullable: false }] },
]);
});
it('should handle arrays of primitives', () => {
const json = { numbers: [1, 2, 3] };
const result = json2classes(json);
expect(result).to.deep.eq([
{ key: 'Root', props: [{ type: 'int', name: 'numbers', isArray: true, canBeNullable: true }] },
]);
});
it('should handle arrays of objects', () => {
const json = { users: [{ name: 'John' }, { name: 'Jane' }] };
const result = json2classes(json);
expect(result).to.deep.eq([
{ key: 'Root', props: [{ type: 'UsersItem', name: 'users', isArray: true }] },
{ key: 'UsersItem', props: [{ type: 'string', name: 'name', canBeNullable: false }] },
]);
});
});
// Test json2csharp function
describe('json2csharp', () => {
const cleanTabs = (s: string) => s.replace(/\t/g, ' ');
it('should convert JSON to C# class with default options', () => {
const json = JSON.stringify({ name: 'John', age: 30 });
const result = cleanTabs(json2csharp({ src: json }));
expect(result).toBe(`using System;
using System.Text.Json;
public class Root
{
[JsonPropertyName(\"name\")]
public string Name { get; set; }
[JsonPropertyName(\"age\")]
public int Age { get; set; }
}`);
});
it('should not change case if pascalCase option is false', () => {
const json = JSON.stringify({ test: { user_name: 'John' } });
const result = cleanTabs(json2csharp({ src: json, pascalCase: false }));
expect(result).toBe(`using System;
using System.Text.Json;
public class Root
{
[JsonPropertyName(\"test\")]
public Test test { get; set; }
}
public class Test
{
[JsonPropertyName(\"user_name\")]
public string user_name { get; set; }
}`);
});
it('should handle when root is an array', () => {
const json = JSON.stringify([{ age: 30 }]);
const result = cleanTabs(json2csharp({ src: json, useNullable: true }));
expect(result).toBe(`using System;
using System.Text.Json;
public class RootItem
{
[JsonPropertyName(\"age\")]
public int? Age { get; set; }
}`);
});
it('should use nullable types if useNullable option is true', () => {
const json = JSON.stringify({ age: 30 });
const result = cleanTabs(json2csharp({ src: json, useNullable: true }));
expect(result).toBe(`using System;
using System.Text.Json;
public class Root
{
[JsonPropertyName(\"age\")]
public int? Age { get; set; }
}`);
});
it('should add JsonProperty attributes if addJsonProperty is true', () => {
const json = JSON.stringify({ name: 'John' });
const result = cleanTabs(json2csharp({ src: json, addJsonProperty: true }));
expect(result).toBe(`using System;
using Newtonsoft.Json;
using System.Text.Json;
public class Root
{
[JsonProperty(\"name\", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName(\"name\")]
public string Name { get; set; }
}`);
});
it('should add JsonPropertyName attributes if addJsonPropertyName is true', () => {
const json = JSON.stringify({ name: 'John' });
const result = cleanTabs(json2csharp({ src: json, addJsonPropertyName: true }));
expect(result).toBe(`using System;
using System.Text.Json;
public class Root
{
[JsonPropertyName(\"name\")]
public string Name { get; set; }
}`);
});
it('should generate immutable classes if generateImmutableClasses is true', () => {
const json = JSON.stringify({ name: 'John' });
const result = cleanTabs(json2csharp({ src: json, generateImmutableClasses: true }));
expect(result).toBe(`using System;
using System.Text.Json;
public class Root
{
public Root(
string name
){
this.Name = name;
}
[JsonPropertyName(\"name\")]
public string Name { get; }
}`);
});
it('should convert JSON with arrays into C# classes', () => {
const json = {
users: [{ name: 'John', age: 30 }, { name: 'Jane', age: 25 }],
};
expect(cleanTabs(json2csharp({ src: json }))).toBe(`using System;
using System.Text.Json;
public class Root
{
[JsonPropertyName(\"users\")]
public UsersItem[] Users { get; set; }
}
public class UsersItem
{
[JsonPropertyName(\"name\")]
public string Name { get; set; }
[JsonPropertyName(\"age\")]
public int Age { get; set; }
}`);
expect(cleanTabs(json2csharp({ src: json, useRecordTypes: true }))).toBe(`using System;
using System.Text.Json;
public record Root(
[property: JsonPropertyName(\"users\")]
UsersItem[] Users
);
public record UsersItem(
[property: JsonPropertyName(\"name\")]
string Name,
[property: JsonPropertyName(\"age\")]
int Age
);`);
expect(cleanTabs(json2csharp({ src: json, useRecordTypes: true, pascalCase: false, useReadonlyLists: true }))).toBe(`using System;
using System.Text.Json;
public record Root(
[property: JsonPropertyName(\"users\")]
IReadonlyList<UsersItem> users
);
public record UsersItem(
[property: JsonPropertyName(\"name\")]
string name,
[property: JsonPropertyName(\"age\")]
int age
);`);
});
it('should generate C# record types when useRecordTypes is true', () => {
const json = JSON.stringify({ name: 'John', age: 30 });
const result = cleanTabs(json2csharp({ src: json, useRecordTypes: true }));
expect(result).toBe(`using System;
using System.Text.Json;
public record Root(
[property: JsonPropertyName(\"name\")]
string Name,
[property: JsonPropertyName(\"age\")]
int Age
);`);
});
it('should generate C# record types with attributes when useRecordTypes/addJsonProperty/addJsonPropertyName are true', () => {
const json = JSON.stringify({ name: 'John', age: 30 });
const result = cleanTabs(json2csharp({
src: json,
useRecordTypes: true,
addJsonProperty: true,
addJsonPropertyName: true,
}));
expect(result).toBe(`using System;
using Newtonsoft.Json;
using System.Text.Json;
public record Root(
[property: JsonProperty(\"name\", NullValueHandling = NullValueHandling.Ignore)]
[property: JsonPropertyName(\"name\")]
string Name,
[property: JsonProperty(\"age\", NullValueHandling = NullValueHandling.Ignore)]
[property: JsonPropertyName(\"age\")]
int Age
);`);
});
it('should handle arrays of primitives', () => {
const json = JSON.stringify({ numbers: [1, 2, 3] });
const result = cleanTabs(json2csharp({ src: json }));
expect(result).toBe(`using System;
using System.Text.Json;
public class Root
{
[JsonPropertyName(\"numbers\")]
public int[] Numbers { get; set; }
}`);
});
it('should handle arrays of objects with PascalCase enabled', () => {
const json = JSON.stringify({
items: [{ item_name: 'A', price: 10.5 }],
});
const result = cleanTabs(json2csharp({
src: json,
pascalCase: true,
addJsonProperty: true,
addJsonPropertyName: true,
}));
expect(result).toBe(`using System;
using Newtonsoft.Json;
using System.Text.Json;
public class Root
{
[JsonProperty(\"items\", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName(\"items\")]
public ItemsItem[] Items { get; set; }
}
public class ItemsItem
{
[JsonProperty(\"item_name\", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName(\"item_name\")]
public string ItemName { get; set; }
[JsonProperty(\"price\", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName(\"price\")]
public double Price { get; set; }
}`);
});
it('should use nullable types for optional fields when useNullable is true', () => {
const json = JSON.stringify({ age: null, price: 10.5 });
const result = cleanTabs(json2csharp({ src: json, useNullable: true }));
expect(result).toBe(`using System;
using System.Text.Json;
public class Root
{
[JsonPropertyName(\"age\")]
public Age Age { get; set; }
[JsonPropertyName(\"price\")]
public double? Price { get; set; }
}
public class Age
{
}`);
});
it('should use fields when useFields is true', () => {
const json = JSON.stringify({ age: 1, price: 10.5 });
const result = cleanTabs(json2csharp({ src: json, useFields: true }));
expect(result).toBe(`using System;
using System.Text.Json;
public class Root
{
[JsonPropertyName(\"age\")]
public int Age;
[JsonPropertyName(\"price\")]
public double Price;
}`);
});
it('should add JsonProperty attributes when addJsonProperty is true', () => {
const json = JSON.stringify({ firstName: 'John', lastName: 'Doe' });
const result = cleanTabs(json2csharp({ src: json, addJsonProperty: true, nullValueHandlingIgnore: false }));
expect(result).toBe(`using System;
using Newtonsoft.Json;
using System.Text.Json;
public class Root
{
[JsonProperty(\"firstName\")]
[JsonPropertyName(\"firstName\")]
public string FirstName { get; set; }
[JsonProperty(\"lastName\")]
[JsonPropertyName(\"lastName\")]
public string LastName { get; set; }
}`);
});
it('should add JsonProperty attributes when addJsonProperty and nullValueHandlingIgnore are true', () => {
const json = JSON.stringify({ firstName: 'John', lastName: 'Doe' });
const result = cleanTabs(json2csharp({ src: json, addJsonProperty: true, nullValueHandlingIgnore: true }));
expect(result).toBe(`using System;
using Newtonsoft.Json;
using System.Text.Json;
public class Root
{
[JsonProperty(\"firstName\", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName(\"firstName\")]
public string FirstName { get; set; }
[JsonProperty(\"lastName\", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName(\"lastName\")]
public string LastName { get; set; }
}`);
});
it('should handle all types', () => {
const json = JSON.stringify({
id: 1, date: '2025-06-09', amount: 1.4, group: null, active: true, email: 'test@example.com',
});
const result = cleanTabs(json2csharp({ src: json }));
expect(result).toBe(`using System;
using System.Text.Json;
public class Root
{
[JsonPropertyName(\"id\")]
public int Id { get; set; }
[JsonPropertyName(\"date\")]
public DateTime Date { get; set; }
[JsonPropertyName(\"amount\")]
public double Amount { get; set; }
[JsonPropertyName(\"group\")]
public Group Group { get; set; }
[JsonPropertyName(\"active\")]
public bool Active { get; set; }
[JsonPropertyName(\"email\")]
public string Email { get; set; }
}
public class Group
{
}`);
});
it('should use readonly lists when useReadonlyLists is true', () => {
const json = JSON.stringify({
items: [{ name: 'Item1' }, { name: 'Item2' }],
});
const result = cleanTabs(json2csharp({ src: json, useReadonlyLists: true }));
expect(result).toBe(`using System;
using System.Text.Json;
public class Root
{
[JsonPropertyName(\"items\")]
public List<ItemsItem> Items { get; } = new List<ItemsItem>;
}
public class ItemsItem
{
[JsonPropertyName(\"name\")]
public string Name { get; set; }
}`);
});
it('should use immutable class properties when generateImmutableClasses is true', () => {
const json = JSON.stringify({ title: 'Book', pages: 300 });
const result = cleanTabs(json2csharp({ src: json, generateImmutableClasses: true }));
expect(result).toBe(`using System;
using System.Text.Json;
public class Root
{
public Root(
string title,
int pages
){
this.Title = title;
this.Pages = pages;
}
[JsonPropertyName(\"title\")]
public string Title { get; }
[JsonPropertyName(\"pages\")]
public int Pages { get; }
}`);
});
it('should generate a combination of options correctly', () => {
const json = JSON.stringify({
people: [
{ first_name: 'John', age: 30 },
{ first_name: 'Jane', age: 25 },
],
});
expect(cleanTabs(json2csharp({
src: json,
pascalCase: true,
useNullable: true,
addJsonProperty: true,
generateImmutableClasses: true,
useRecordTypes: false,
useReadonlyLists: true,
}))).toBe(`using System;
using Newtonsoft.Json;
using System.Text.Json;
public class Root
{
public Root(
List<PeopleItem> people
){
this.People = people;
}
[JsonProperty(\"people\", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName(\"people\")]
public List<PeopleItem> People { get; } = new List<PeopleItem>;
}
public class PeopleItem
{
public PeopleItem(
string firstName,
int? age
){
this.FirstName = firstName;
this.Age = age;
}
[JsonProperty(\"first_name\", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName(\"first_name\")]
public string FirstName { get; }
[JsonProperty(\"age\", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName(\"age\")]
public int? Age { get; }
}`);
expect(cleanTabs(json2csharp({
src: json,
pascalCase: true,
useNullable: true,
addJsonProperty: true,
addJsonPropertyName: true,
generateImmutableClasses: false,
useRecordTypes: false,
useReadonlyLists: true,
}))).toBe(`using System;
using Newtonsoft.Json;
using System.Text.Json;
public class Root
{
[JsonProperty(\"people\", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName(\"people\")]
public List<PeopleItem> People { get; } = new List<PeopleItem>;
}
public class PeopleItem
{
[JsonProperty(\"first_name\", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName(\"first_name\")]
public string FirstName { get; set; }
[JsonProperty(\"age\", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName(\"age\")]
public int? Age { get; set; }
}`);
expect(cleanTabs(json2csharp({
src: json,
pascalCase: true,
useNullable: true,
addJsonProperty: true,
useRecordTypes: true,
useReadonlyLists: true,
}))).toBe(`using System;
using Newtonsoft.Json;
using System.Text.Json;
public record Root(
[property: JsonProperty(\"people\", NullValueHandling = NullValueHandling.Ignore)]
[property: JsonPropertyName(\"people\")]
IReadonlyList<PeopleItem> People
);
public record PeopleItem(
[property: JsonProperty(\"first_name\", NullValueHandling = NullValueHandling.Ignore)]
[property: JsonPropertyName(\"first_name\")]
string FirstName,
[property: JsonProperty(\"age\", NullValueHandling = NullValueHandling.Ignore)]
[property: JsonPropertyName(\"age\")]
int? Age
);`);
});
});

View file

@ -0,0 +1,215 @@
import { camelCase as convertToCamelCase, pascalCase as convertToPascalCase } from 'change-case';
import JSON5 from 'json5';
function normalize(src: string, firstUpper = true) {
// return /^\d$/.test(src.charAt(0))
// ? `N${pascalCase(src)}`
// : pascalCase(src);
return /^\d$/.test(src.charAt(0))
? `N${src}`
: (firstUpper ? src.charAt(0).toUpperCase() + src.slice(1) : src);
}
export function isDate(src: string) {
return /^(\d{4})(-(0[1-9]|1[0-2])(-([12]\d|0[1-9]|3[01]))([T\s]((([01]\d|2[0-3])((:)[0-5]\d))([\:]\d+)?)?(:[0-5]\d([\.]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)$/.test(
src,
);
}
interface Prop { type: string; name: string; isArray?: boolean; canBeNullable?: boolean }
export function getPrimitiveProp(obj: any, key: string): Prop {
const type = typeof obj;
switch (type) {
case 'string':
// eslint-disable-next-line no-case-declarations
const isObjDate = isDate(obj);
return { type: isObjDate ? 'DateTime' : 'string', name: key, canBeNullable: isObjDate };
case 'number':
return { type: Number.isInteger(obj) ? 'int' : 'double', name: key, canBeNullable: true };
case 'boolean':
return { type: 'bool', name: key, canBeNullable: true };
default:
throw new Error(`Unexpected key '${key}' of type ${type}`);
}
}
interface ClassType { key: string; props: Array<Prop> }
function handleObject(
classes: Array<ClassType>,
obj: any,
key: string,
skip: boolean = false) {
const normalizedKey = normalize(key);
let target: ClassType = classes.find(x => x.key === normalizedKey) as never;
if (!target) {
target = { key: normalizedKey, props: [] };
if (!skip) {
classes.push(target);
}
}
if (obj != null) {
Object.keys(obj).forEach((k) => {
const keyName = Number.isNaN(Number(k)) ? k : `${normalizedKey}Item`;
let prop: Prop | null = null;
if (typeof obj[k] == 'object') {
if (Array.isArray(obj[k])) {
if (Array.from(obj[k]).length > 0) {
if (typeof obj[k][0] == 'object') {
handleObject(classes, obj[k], keyName, true);
prop = {
type: normalize(`${keyName}Item`),
name: keyName,
isArray: true,
};
}
else {
prop = getPrimitiveProp(obj[k][0], keyName);
prop.isArray = true;
}
}
}
else {
handleObject(classes, obj[k], keyName);
prop = { type: normalize(keyName), name: keyName };
}
}
else {
prop = getPrimitiveProp(obj[k], keyName);
}
if (prop && !target.props.some(x => x.name === prop?.name)) {
target.props.push(prop);
}
});
}
}
export function json2classes(src: object, rootTypeName: string = 'Root') {
const classes: Array<ClassType> = [];
handleObject(classes, src, rootTypeName);
return classes;
}
export function json2csharp(
{
src,
rootTypeName = 'Root',
pascalCase = true,
nullValueHandlingIgnore = true,
addJsonPropertyName = true,
useReadonlyLists,
useRecordTypes,
addJsonProperty,
generateImmutableClasses,
useFields,
useNullable,
}: {
src: any
rootTypeName?: string
pascalCase?: boolean
useFields?: boolean
useNullable?: boolean
addJsonProperty?: boolean
nullValueHandlingIgnore?: boolean
addJsonPropertyName?: boolean
generateImmutableClasses?: boolean
useRecordTypes?: boolean
useReadonlyLists?: boolean
}) {
const normalizeCase = (s: string) => {
const normalized = normalize(s, pascalCase);
return pascalCase ? convertToPascalCase(normalized) : normalized;
};
const getPropType = (p: Prop) => {
let type = p.type;
if (p.isArray) {
if (useReadonlyLists) {
type = useRecordTypes ? `IReadonlyList<${type}>` : `List<${type}>`;
}
else {
type = `${type}[]`;
}
}
const nullable = useNullable && p.canBeNullable ? '?' : '';
return { type, nullable };
};
const srcObj = typeof src === 'string' ? JSON5.parse(src) : src;
const classes = json2classes(
Array.isArray(srcObj) ? srcObj[0] : srcObj,
Array.isArray(srcObj) ? `${rootTypeName}Item` : rootTypeName);
let result = '';
result += 'using System;\n';
result += addJsonProperty ? 'using Newtonsoft.Json;\n' : '';
result += addJsonPropertyName ? 'using System.Text.Json;\n' : '';
result += '\n';
classes.forEach((c) => {
if (useRecordTypes) {
result += `public record ${normalizeCase(c.key)}(\n`;
}
else {
result += `public class ${normalizeCase(c.key)}\n`;
result += '{\n';
}
if (!useRecordTypes && generateImmutableClasses) {
result += `\tpublic ${normalizeCase(c.key)}(\n`;
c.props.forEach((p) => {
const propType = getPropType(p);
result += `\t\t${propType.type}${propType.nullable} ${convertToCamelCase(p.name)},\n`;
});
result = result.replace(/,\n$/, '\n');
result += '\t){\n';
c.props.forEach((p) => {
result += `\t\tthis.${normalizeCase(p.name)} = ${convertToCamelCase(p.name)};\n`;
});
result += '\t}\n\n';
}
c.props.forEach((p) => {
const scope = useRecordTypes ? 'property: ' : '';
if (addJsonProperty) {
if (nullValueHandlingIgnore) {
result += `\t[${scope}JsonProperty("${p.name}", NullValueHandling = NullValueHandling.Ignore)]\n`;
}
else {
result += `\t[${scope}JsonProperty("${p.name}")]\n`;
}
}
if (addJsonPropertyName) {
result += `\t[${scope}JsonPropertyName("${p.name}")]\n`;
}
const propType = getPropType(p);
if (useRecordTypes) {
result += `\t${propType.type}${propType.nullable} ${normalizeCase(p.name)},\n`;
}
else {
const newList = useReadonlyLists && p.isArray ? ` = new ${propType.type};` : '';
if (useFields) {
result += `\tpublic ${propType.type}${propType.nullable} ${normalizeCase(p.name)}${(newList || ';')}`;
}
else if (generateImmutableClasses || (p.isArray && useReadonlyLists)) {
result += `\tpublic ${propType.type}${propType.nullable} ${normalizeCase(p.name)} { get; }${newList}`;
}
else {
result += `\tpublic ${propType.type}${propType.nullable} ${normalizeCase(p.name)} { get; set; }${newList}`;
}
result += '\n';
}
});
if (useRecordTypes) {
result = result.replace(/,\n$/, '\n');
result += ');\n\n';
}
else {
result += '}\n\n';
}
});
return result.trim();
}
export default json2csharp;