Skip to content

Commit e8faf8d

Browse files
committed
feat: Add YAML to JSON converter and enhance JSON to YAML converter
- Updated JSON to YAML converter with property case transformation options. - Introduced YAML to JSON converter with similar property case transformation features. - Enhanced UI to support converter options in the conversion view. - Updated styles for better UI consistency and responsiveness. - Updated sitemap to include new converter routes. - Bumped version number to 2.4 in the sidebar.
1 parent 12fa2a0 commit e8faf8d

16 files changed

Lines changed: 3187 additions & 2201 deletions

package-lock.json

Lines changed: 2387 additions & 1956 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 38 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "utilplex",
3-
"version": "2.3.0",
3+
"version": "2.4.0",
44
"scripts": {
55
"ng": "ng",
66
"start": "ng serve",
@@ -13,64 +13,63 @@
1313
},
1414
"private": true,
1515
"dependencies": {
16-
"@acrodata/code-editor": "^0.5.1",
17-
"@angular/animations": "^20.3.10",
18-
"@angular/common": "^20.3.10",
19-
"@angular/compiler": "^20.3.10",
20-
"@angular/core": "^20.3.10",
21-
"@angular/forms": "^20.3.10",
22-
"@angular/platform-browser": "^20.3.10",
23-
"@angular/platform-browser-dynamic": "^20.3.10",
24-
"@angular/platform-server": "^20.3.10",
25-
"@angular/router": "^20.3.10",
26-
"@angular/ssr": "^20.3.9",
27-
"@codemirror/commands": "^6.10.0",
16+
"@acrodata/code-editor": "^0.6.0",
17+
"@angular/animations": "^21.1.1",
18+
"@angular/common": "^21.1.1",
19+
"@angular/compiler": "^21.1.1",
20+
"@angular/core": "^21.1.1",
21+
"@angular/forms": "^21.1.1",
22+
"@angular/platform-browser": "^21.1.1",
23+
"@angular/platform-browser-dynamic": "^21.1.1",
24+
"@angular/platform-server": "^21.1.1",
25+
"@angular/router": "^21.1.1",
26+
"@angular/ssr": "^21.1.1",
27+
"@codemirror/commands": "^6.10.1",
2828
"@codemirror/lang-css": "^6.3.1",
2929
"@codemirror/lang-javascript": "^6.2.4",
3030
"@codemirror/lang-json": "^6.0.2",
3131
"@codemirror/lang-sql": "^6.10.0",
3232
"@codemirror/lang-xml": "^6.1.0",
3333
"@codemirror/lang-yaml": "^6.1.2",
34-
"@codemirror/language": "^6.11.3",
35-
"@codemirror/state": "^6.5.2",
36-
"@codemirror/view": "^6.38.6",
34+
"@codemirror/language": "^6.12.1",
35+
"@codemirror/state": "^6.5.4",
36+
"@codemirror/view": "^6.39.11",
3737
"@lezer/highlight": "^1.2.3",
38-
"@ng-select/ng-select": "^20.7.0",
39-
"@stedi/prettier-plugin-jsonata": "^2.1.6",
38+
"@ng-select/ng-select": "^21.2.0",
39+
"@stedi/prettier-plugin-jsonata": "^2.1.8",
4040
"codemirror": "^6.0.2",
41-
"express": "~5.1.0",
42-
"js-yaml": "^4.1.0",
43-
"prettier": "^3.6.2",
41+
"express": "~5.2.1",
42+
"js-yaml": "^4.1.1",
43+
"prettier": "^3.8.1",
4444
"rxjs": "~7.8.2",
45-
"sql-formatter": "^15.6.10",
45+
"sql-formatter": "^15.7.0",
4646
"tslib": "^2.8.1",
47-
"zone.js": "~0.15.1"
47+
"zone.js": "~0.16.0"
4848
},
4949
"devDependencies": {
50-
"@angular-eslint/builder": "20.5.1",
51-
"@angular-eslint/schematics": "20.5.1",
52-
"@angular-eslint/template-parser": "20.5.1",
53-
"@angular/build": "^20.3.9",
54-
"@angular/cli": "~20.3.9",
55-
"@angular/compiler-cli": "^20.3.10",
56-
"@types/express": "~5.0.5",
57-
"@types/jasmine": "~5.1.12",
50+
"@angular-eslint/builder": "21.1.0",
51+
"@angular-eslint/schematics": "21.1.0",
52+
"@angular-eslint/template-parser": "21.1.0",
53+
"@angular/build": "^21.1.1",
54+
"@angular/cli": "~21.1.1",
55+
"@angular/compiler-cli": "^21.1.1",
56+
"@types/express": "~5.0.6",
57+
"@types/jasmine": "~6.0.0",
5858
"@types/js-yaml": "^4.0.9",
59-
"@types/node": "^24.10.0",
60-
"angular-eslint": "^20.5.1",
61-
"eslint": "^9.39.1",
59+
"@types/node": "^25.0.10",
60+
"angular-eslint": "^21.1.0",
61+
"eslint": "^9.39.2",
6262
"eslint-config-prettier": "^10.1.8",
63-
"eslint-plugin-prettier": "^5.5.4",
64-
"jasmine-core": "~5.12.1",
63+
"eslint-plugin-prettier": "^5.5.5",
64+
"jasmine-core": "~6.0.1",
6565
"karma": "~6.4.4",
6666
"karma-chrome-launcher": "~3.2.0",
6767
"karma-coverage": "~2.2.1",
6868
"karma-jasmine": "~5.1.0",
69-
"karma-jasmine-html-reporter": "~2.1.0",
70-
"prettier": "^3.6.2",
69+
"prettier": "^3.8.1",
7170
"prettier-eslint": "^16.4.2",
7271
"style-loader": "^4.0.0",
7372
"typescript": "~5.9.3",
74-
"typescript-eslint": "^8.46.3"
73+
"typescript-eslint": "^8.53.1"
7574
}
7675
}

src/app/_services/route.service.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,22 @@ export class RouteService {
101101
routes: [
102102
{
103103
name: 'Json To Yaml',
104-
title: 'Json To Yaml',
104+
title: 'JSON to YAML Converter',
105105
url: '/convert/json-yaml',
106-
description: 'Convert JSON data to YAML format with ease, preserving structure and readability.',
106+
description:
107+
'Convert JSON data to YAML format with property case transformation options including camelCase, snake_case, and more.',
107108
loadComponent: () =>
108109
import('../converters/c-json-yaml/c-json-yaml.component').then((mod) => mod.CJsonYamlComponent),
109110
},
111+
{
112+
name: 'Yaml To Json',
113+
title: 'YAML to JSON Converter',
114+
url: '/convert/yaml-json',
115+
description:
116+
'Convert YAML data to JSON format with property case transformation options including camelCase, snake_case, and more.',
117+
loadComponent: () =>
118+
import('../converters/c-yaml-json/c-yaml-json.component').then((mod) => mod.CYamlJsonComponent),
119+
},
110120
],
111121
},
112122
{
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
import { Observable } from 'rxjs';
2+
import { signal, WritableSignal } from '@angular/core';
3+
4+
export interface ConverterOption {
5+
key: string;
6+
label: string;
7+
type: 'select' | 'checkbox';
8+
options?: { value: string; label: string }[];
9+
value: WritableSignal<string | boolean>;
10+
}
211

312
export abstract class ConverterServiceBase {
413
abstract title: string;
514
abstract languageFrom: string;
615
abstract languageTo: string;
716
abstract routeName: string;
817
abstract convert(input: string): Observable<string>;
18+
19+
/** Optional configuration options for this converter */
20+
converterOptions: ConverterOption[] = [];
921
}
Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,88 @@
1-
import { Injectable } from '@angular/core';
1+
import { Injectable, signal } from '@angular/core';
22

33
import * as yaml from 'js-yaml';
44
import { Observable, of } from 'rxjs';
5-
import { ConverterServiceBase } from './converter.service';
5+
import { ConverterOption, ConverterServiceBase } from './converter.service';
6+
7+
export type PropertyCaseFormat = 'original' | 'camelCase' | 'PascalCase' | 'snake_case' | 'kebab-case' | 'CONSTANT_CASE';
68

79
@Injectable()
810
export class JsonToYamlConverter extends ConverterServiceBase {
911
override title = 'Json to Yaml';
1012
override languageFrom = 'json';
1113
override languageTo = 'yaml';
1214
override routeName = 'Json To Yaml';
15+
16+
private propertyCaseFormat = signal<string>('original');
17+
18+
override converterOptions: ConverterOption[] = [
19+
{
20+
key: 'propertyCase',
21+
label: 'Property Case',
22+
type: 'select',
23+
options: [
24+
{ value: 'original', label: 'Original' },
25+
{ value: 'camelCase', label: 'camelCase' },
26+
{ value: 'PascalCase', label: 'PascalCase' },
27+
{ value: 'snake_case', label: 'snake_case' },
28+
{ value: 'kebab-case', label: 'kebab-case' },
29+
{ value: 'CONSTANT_CASE', label: 'CONSTANT_CASE' },
30+
],
31+
value: this.propertyCaseFormat,
32+
},
33+
];
34+
1335
override convert(input: string): Observable<string> {
1436
if (!input) return of('');
15-
const json = JSON.parse(input);
37+
let json = JSON.parse(input);
38+
const caseFormat = this.propertyCaseFormat() as PropertyCaseFormat;
39+
if (caseFormat !== 'original') {
40+
json = this.transformKeys(json, caseFormat);
41+
}
1642
return of(yaml.dump(json));
1743
}
44+
45+
private transformKeys(obj: unknown, format: PropertyCaseFormat): unknown {
46+
if (Array.isArray(obj)) {
47+
return obj.map((item) => this.transformKeys(item, format));
48+
}
49+
if (obj !== null && typeof obj === 'object') {
50+
const result: Record<string, unknown> = {};
51+
for (const [key, value] of Object.entries(obj)) {
52+
const newKey = this.convertCase(key, format);
53+
result[newKey] = this.transformKeys(value, format);
54+
}
55+
return result;
56+
}
57+
return obj;
58+
}
59+
60+
private convertCase(str: string, format: PropertyCaseFormat): string {
61+
// Split by common separators (camelCase, PascalCase, snake_case, kebab-case, spaces)
62+
const words = str
63+
.replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase/PascalCase split
64+
.replace(/[_\-\s]+/g, ' ') // normalize separators
65+
.toLowerCase()
66+
.split(' ')
67+
.filter((w) => w.length > 0);
68+
69+
switch (format) {
70+
case 'camelCase':
71+
return words.map((w, i) => (i === 0 ? w : this.capitalize(w))).join('');
72+
case 'PascalCase':
73+
return words.map((w) => this.capitalize(w)).join('');
74+
case 'snake_case':
75+
return words.join('_');
76+
case 'kebab-case':
77+
return words.join('-');
78+
case 'CONSTANT_CASE':
79+
return words.join('_').toUpperCase();
80+
default:
81+
return str;
82+
}
83+
}
84+
85+
private capitalize(str: string): string {
86+
return str.charAt(0).toUpperCase() + str.slice(1);
87+
}
1888
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Injectable, signal } from '@angular/core';
2+
3+
import * as yaml from 'js-yaml';
4+
import { Observable, of } from 'rxjs';
5+
import { ConverterOption, ConverterServiceBase } from './converter.service';
6+
7+
export type PropertyCaseFormat = 'original' | 'camelCase' | 'PascalCase' | 'snake_case' | 'kebab-case' | 'CONSTANT_CASE';
8+
9+
@Injectable()
10+
export class YamlToJsonConverter extends ConverterServiceBase {
11+
override title = 'Yaml to Json';
12+
override languageFrom = 'yaml';
13+
override languageTo = 'json';
14+
override routeName = 'Yaml To Json';
15+
16+
private propertyCaseFormat = signal<string>('original');
17+
18+
override converterOptions: ConverterOption[] = [
19+
{
20+
key: 'propertyCase',
21+
label: 'Property Case',
22+
type: 'select',
23+
options: [
24+
{ value: 'original', label: 'Original' },
25+
{ value: 'camelCase', label: 'camelCase' },
26+
{ value: 'PascalCase', label: 'PascalCase' },
27+
{ value: 'snake_case', label: 'snake_case' },
28+
{ value: 'kebab-case', label: 'kebab-case' },
29+
{ value: 'CONSTANT_CASE', label: 'CONSTANT_CASE' },
30+
],
31+
value: this.propertyCaseFormat,
32+
},
33+
];
34+
35+
override convert(input: string): Observable<string> {
36+
if (!input) return of('');
37+
let parsed = yaml.load(input);
38+
const caseFormat = this.propertyCaseFormat() as PropertyCaseFormat;
39+
if (caseFormat !== 'original') {
40+
parsed = this.transformKeys(parsed, caseFormat);
41+
}
42+
return of(JSON.stringify(parsed, null, 2));
43+
}
44+
45+
private transformKeys(obj: unknown, format: PropertyCaseFormat): unknown {
46+
if (Array.isArray(obj)) {
47+
return obj.map((item) => this.transformKeys(item, format));
48+
}
49+
if (obj !== null && typeof obj === 'object') {
50+
const result: Record<string, unknown> = {};
51+
for (const [key, value] of Object.entries(obj)) {
52+
const newKey = this.convertCase(key, format);
53+
result[newKey] = this.transformKeys(value, format);
54+
}
55+
return result;
56+
}
57+
return obj;
58+
}
59+
60+
private convertCase(str: string, format: PropertyCaseFormat): string {
61+
const words = str
62+
.replace(/([a-z])([A-Z])/g, '$1 $2')
63+
.replace(/[_\-\s]+/g, ' ')
64+
.toLowerCase()
65+
.split(' ')
66+
.filter((w) => w.length > 0);
67+
68+
switch (format) {
69+
case 'camelCase':
70+
return words.map((w, i) => (i === 0 ? w : this.capitalize(w))).join('');
71+
case 'PascalCase':
72+
return words.map((w) => this.capitalize(w)).join('');
73+
case 'snake_case':
74+
return words.join('_');
75+
case 'kebab-case':
76+
return words.join('-');
77+
case 'CONSTANT_CASE':
78+
return words.join('_').toUpperCase();
79+
default:
80+
return str;
81+
}
82+
}
83+
84+
private capitalize(str: string): string {
85+
return str.charAt(0).toUpperCase() + str.slice(1);
86+
}
87+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<app-convert-view></app-convert-view>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/* No component-specific styles needed */
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { CommonModule } from '@angular/common';
2+
import { Component } from '@angular/core';
3+
import { FormsModule } from '@angular/forms';
4+
import { ConverterServiceBase } from '../_services/converter.service';
5+
import { YamlToJsonConverter } from '../_services/yaml-to-json.service';
6+
import { ConvertViewComponent } from '../convert-view/convert-view.component';
7+
8+
@Component({
9+
selector: 'app-c-yaml-json',
10+
templateUrl: './c-yaml-json.component.html',
11+
styleUrls: ['./c-yaml-json.component.scss'],
12+
providers: [{ provide: ConverterServiceBase, useClass: YamlToJsonConverter }],
13+
imports: [CommonModule, FormsModule, ConvertViewComponent],
14+
host: { class: 'host-flex-container' }
15+
})
16+
export class CYamlJsonComponent {}

0 commit comments

Comments
 (0)