Skip to content

Commit d8e9585

Browse files
LucasJAlbaaitboudad
authored andcommitted
feat(json-schema): adds allOf support (#1683)
1 parent ce3759d commit d8e9585

File tree

6 files changed

+213
-0
lines changed

6 files changed

+213
-0
lines changed

demo/src/app/examples/advanced/json-schema/app.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export class AppComponent {
2323
'numbers',
2424
'references',
2525
'schema_dependencies',
26+
'allOf',
2627
];
2728

2829
constructor(
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"schema": {
3+
"title": "Extending",
4+
"definitions": {
5+
"address": {
6+
"type": "object",
7+
"properties": {
8+
"street_address": {
9+
"title": "Address",
10+
"type": "string"
11+
},
12+
"city": {
13+
"title": "City",
14+
"type": "string"
15+
},
16+
"state": {
17+
"title": "State",
18+
"type": "string"
19+
}
20+
},
21+
"required": [
22+
"street_address",
23+
"city",
24+
"state"
25+
]
26+
}
27+
},
28+
"type": "object",
29+
"properties": {
30+
"billing_address": {
31+
"title": "Billing Address",
32+
"$ref": "#/definitions/address"
33+
},
34+
"shipping_address": {
35+
"allOf": [
36+
{
37+
"title": "Shipping Address",
38+
"$ref": "#/definitions/address"
39+
},
40+
{
41+
"properties": {
42+
"type": {
43+
"title": "Type",
44+
"enum": [
45+
"residential",
46+
"business"
47+
]
48+
}
49+
},
50+
"required": [
51+
"type"
52+
]
53+
}
54+
]
55+
}
56+
}
57+
}
58+
}

demo/src/app/examples/advanced/json-schema/config.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { AppComponent } from './app.component';
2929
{ file: 'assets/json-schema/numbers.json', content: require('!!highlight-loader?raw=true&lang=typescript!./assets/numbers_json'), filecontent: require('!!raw-loader!./assets/numbers_json') },
3030
{ file: 'assets/json-schema/references.json', content: require('!!highlight-loader?raw=true&lang=typescript!./assets/references_json'), filecontent: require('!!raw-loader!./assets/references_json') },
3131
{ file: 'assets/json-schema/schema_dependencies.json', content: require('!!highlight-loader?raw=true&lang=typescript!./assets/schema_dependencies_json'), filecontent: require('!!raw-loader!./assets/schema_dependencies_json') },
32+
{ file: 'assets/json-schema/allOf.json', content: require('!!highlight-loader?raw=true&lang=typescript!./assets/allOf_json'), filecontent: require('!!raw-loader!./assets/allOf_json') },
3233
],
3334
}],
3435
},
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"schema": {
3+
"title": "Extending",
4+
"definitions": {
5+
"address": {
6+
"type": "object",
7+
"properties": {
8+
"street_address": {
9+
"title": "Address",
10+
"type": "string"
11+
},
12+
"city": {
13+
"title": "City",
14+
"type": "string"
15+
},
16+
"state": {
17+
"title": "State",
18+
"type": "string"
19+
}
20+
},
21+
"required": [
22+
"street_address",
23+
"city",
24+
"state"
25+
]
26+
}
27+
},
28+
"type": "object",
29+
"properties": {
30+
"billing_address": {
31+
"title": "Billing Address",
32+
"$ref": "#/definitions/address"
33+
},
34+
"shipping_address": {
35+
"allOf": [
36+
{
37+
"title": "Shipping Address",
38+
"$ref": "#/definitions/address"
39+
},
40+
{
41+
"properties": {
42+
"type": {
43+
"title": "Housing type",
44+
"enum": [
45+
"Residential",
46+
"Business"
47+
]
48+
}
49+
},
50+
"required": [
51+
"type"
52+
]
53+
}
54+
]
55+
}
56+
}
57+
}
58+
}

src/core/json-schema/src/formly-json-schema.service.spec.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,82 @@ describe('Service: FormlyJsonschema', () => {
546546

547547
});
548548
});
549+
550+
// https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.7
551+
describe('Schema allOf support', () => {
552+
it('should merge allOf array into single object ', () => {
553+
const schema: JSONSchema7 = {
554+
'definitions': {
555+
'address': {
556+
'type': 'object',
557+
'properties': {
558+
'street_address': { 'type': 'string' },
559+
'city': { 'type': 'string' },
560+
'state': { 'type': 'string' },
561+
},
562+
'required': ['street_address', 'city', 'state'],
563+
},
564+
},
565+
'type': 'object',
566+
'properties': {
567+
'billing_address': {
568+
allOf: [
569+
{'$ref': '#/definitions/address'},
570+
{ 'properties': {
571+
'type': { 'enum': [ 'residential', 'business' ] },
572+
},
573+
},
574+
],
575+
},
576+
},
577+
};
578+
const config = formlyJsonschema.toFieldConfig(schema);
579+
expect(config.fieldGroup[0].fieldGroup[0].key).toEqual('type');
580+
expect(config.fieldGroup[0].fieldGroup[0].type).toEqual('enum');
581+
});
582+
583+
it('should handle nested allOf ', () => {
584+
const schema: JSONSchema7 = {
585+
'definitions': {
586+
'baseAddress': {
587+
'type': 'object',
588+
'properties': {
589+
'street_address': { 'type': 'string' },
590+
'city': { 'type': 'string' },
591+
'state': { 'type': 'string' },
592+
},
593+
'required': ['street_address', 'city', 'state'],
594+
},
595+
'mailingAddress': {
596+
allOf: [
597+
{'$ref': '#/definitions/baseAddress'},
598+
{ 'properties': {
599+
'country': { 'type': 'string' },
600+
},
601+
},
602+
],
603+
},
604+
},
605+
'type': 'object',
606+
'properties': {
607+
'billing_address': {
608+
allOf: [
609+
{'$ref': '#/definitions/mailingAddress'},
610+
{ 'properties': {
611+
'type': { 'enum': [ 'residential', 'business' ] },
612+
},
613+
},
614+
],
615+
},
616+
},
617+
};
618+
const config = formlyJsonschema.toFieldConfig(schema);
619+
expect(config.fieldGroup[0].fieldGroup[0].key).toEqual('type');
620+
expect(config.fieldGroup[0].fieldGroup[0].type).toEqual('enum');
621+
expect(config.fieldGroup[0].fieldGroup[1].key).toEqual('country');
622+
expect(config.fieldGroup[0].fieldGroup[1].type).toEqual('string');
623+
});
624+
});
549625
// TODO: discuss support of writeOnly. Note: this may not be needed.
550626
// TODO: discuss support of examples. By spec, default can be used in its place.
551627
// https://json-schema.org/latest/json-schema-validation.html#rfc.section.10

src/core/json-schema/src/formly-json-schema.service.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export class FormlyJsonschema {
3232
schema = this.resolveDefinition(schema, options);
3333
}
3434

35+
if (schema.allOf) {
36+
schema = this.resolveAllOf(schema, options);
37+
}
38+
3539
let field: FormlyFieldConfig = {
3640
type: this.guessType(schema),
3741
defaultValue: schema.default,
@@ -141,6 +145,21 @@ export class FormlyJsonschema {
141145
return options.map ? options.map(field, schema) : field;
142146
}
143147

148+
private resolveAllOf(schema: JSONSchema7, options: IOptions) {
149+
if (!schema.allOf.length) {
150+
throw Error(`allOf array can not be empty ${schema.allOf}.`);
151+
}
152+
return schema.allOf.reduce((prev: JSONSchema7, curr: JSONSchema7) => {
153+
if (curr.$ref) {
154+
curr = this.resolveDefinition(curr, options);
155+
}
156+
if (curr.allOf) {
157+
curr = this.resolveAllOf(curr, options);
158+
}
159+
return reverseDeepMerge(curr, prev);
160+
}, {});
161+
}
162+
144163
private resolveDefinition(schema: JSONSchema7, options: IOptions): JSONSchema7 {
145164
const [uri, pointer] = schema.$ref.split('#/');
146165
if (uri) {

0 commit comments

Comments
 (0)