Skip to content

Commit 7cbde4f

Browse files
authored
Merge pull request #474 from streamich/json-type-map
JSON Type "map" type
2 parents e8421d6 + 1102d58 commit 7cbde4f

File tree

15 files changed

+399
-13
lines changed

15 files changed

+399
-13
lines changed

src/json-schema/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export interface JsonSchemaObject extends JsonSchemaGenericKeywords {
3636
};
3737
required?: string[];
3838
additionalProperties?: boolean | JsonSchemaNode;
39+
patternProperties?: {
40+
[key: string]: JsonSchemaNode;
41+
};
3942
const?: object;
4043
}
4144

src/json-type/codegen/binary/__tests__/testBinaryCodegen.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,40 @@ export const testBinaryCodegen = (transcode: (system: TypeSystem, type: Type, va
369369
});
370370
});
371371

372+
describe('"map" type', () => {
373+
test('can encode empty map', () => {
374+
const system = new TypeSystem();
375+
const t = system.t;
376+
const type = t.map;
377+
const value: {} = {};
378+
expect(transcode(system, type, value)).toStrictEqual(value);
379+
});
380+
381+
test('can encode empty map with one key', () => {
382+
const system = new TypeSystem();
383+
const t = system.t;
384+
const type = t.map;
385+
const value: {} = {a: 'asdf'};
386+
expect(transcode(system, type, value)).toStrictEqual(value);
387+
});
388+
389+
test('can encode typed map with two keys', () => {
390+
const system = new TypeSystem();
391+
const t = system.t;
392+
const type = t.Map(t.bool);
393+
const value: {} = {x: true, y: false};
394+
expect(transcode(system, type, value)).toStrictEqual(value);
395+
});
396+
397+
test('can encode nested maps', () => {
398+
const system = new TypeSystem();
399+
const t = system.t;
400+
const type = t.Map(t.Map(t.bool));
401+
const value: {} = {a: {x: true, y: false}};
402+
expect(transcode(system, type, value)).toStrictEqual(value);
403+
});
404+
});
405+
372406
describe('"ref" type', () => {
373407
test('can encode a simple reference', () => {
374408
const system = new TypeSystem();

src/json-type/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,38 @@ describe('object', () => {
155155
});
156156
});
157157

158+
describe('map', () => {
159+
test('empty', () => {
160+
const system = new TypeSystem();
161+
const type = system.t.map;
162+
const estimator = type.compileCapacityEstimator({});
163+
expect(estimator(123)).toBe(maxEncodingCapacity({}));
164+
});
165+
166+
test('with one field', () => {
167+
const system = new TypeSystem();
168+
const type = system.t.Map(system.t.bool);
169+
const estimator = type.compileCapacityEstimator({});
170+
expect(estimator({foo: true})).toBe(maxEncodingCapacity({foo: true}));
171+
});
172+
173+
test('three number fields', () => {
174+
const system = new TypeSystem();
175+
const type = system.t.Map(system.t.num);
176+
const estimator = type.compileCapacityEstimator({});
177+
const data = {foo: 1, bar: 2, baz: 3};
178+
expect(estimator(data)).toBe(maxEncodingCapacity(data));
179+
});
180+
181+
test('nested maps', () => {
182+
const system = new TypeSystem();
183+
const type = system.t.Map(system.t.Map(system.t.str));
184+
const estimator = type.compileCapacityEstimator({});
185+
const data = {foo: {bar: 'baz'}, baz: {bar: 'foo'}};
186+
expect(estimator(data)).toBe(maxEncodingCapacity(data));
187+
});
188+
});
189+
158190
describe('ref', () => {
159191
test('two hops', () => {
160192
const system = new TypeSystem();

src/json-type/codegen/json/__tests__/json.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,28 @@ describe('"obj" type', () => {
121121
});
122122
});
123123

124+
describe('"map" type', () => {
125+
test('serializes a map', () => {
126+
const type = s.Map(s.num);
127+
exec(type, {a: 1, b: 2, c: 3});
128+
});
129+
130+
test('serializes empty map', () => {
131+
const type = s.Map(s.num);
132+
exec(type, {});
133+
});
134+
135+
test('serializes a map with a single key', () => {
136+
const type = s.Map(s.num);
137+
exec(type, {'0': 0});
138+
});
139+
140+
test('serializes a map in a map', () => {
141+
const type = s.Map(s.Map(s.bool));
142+
exec(type, {a: {b: true}});
143+
});
144+
});
145+
124146
describe('general', () => {
125147
test('serializes according to schema a POJO object', () => {
126148
const type = s.Object({

src/json-type/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export enum ValidationError {
66
ARR,
77
TUP,
88
OBJ,
9+
MAP,
910
KEY,
1011
KEYS,
1112
BIN,
@@ -32,6 +33,7 @@ export const ValidationErrorMessage = {
3233
[ValidationError.ARR]: 'Not an array.',
3334
[ValidationError.TUP]: 'Not a tuple.',
3435
[ValidationError.OBJ]: 'Not an object.',
36+
[ValidationError.MAP]: 'Not a map.',
3537
[ValidationError.KEY]: 'Missing key.',
3638
[ValidationError.KEYS]: 'Too many or missing object keys.',
3739
[ValidationError.BIN]: 'Not a binary.',

src/json-type/schema/__tests__/type.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ test('can generate any type', () => {
1414
s.prop('address', address),
1515
s.prop('timeCreated', s.Number()),
1616
s.prop('tags', s.Array(s.Or(s.Number(), s.String()))),
17-
s.prop('elements', s.Map(s.str))
17+
s.prop('elements', s.Map(s.str)),
1818
);
1919

2020
expect(userType).toMatchObject({

src/json-type/type/TypeBuilder.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ export class TypeBuilder {
4646
return this.Object();
4747
}
4848

49+
get map() {
50+
return this.Map(this.any);
51+
}
52+
4953
get fn() {
5054
return this.Function(this.any, this.any);
5155
}
@@ -129,6 +133,12 @@ export class TypeBuilder {
129133
return field;
130134
}
131135

136+
public Map<T extends Type>(type: T, options?: schema.Optional<schema.MapSchema>) {
137+
const map = new classes.MapType<T>(type, options);
138+
map.system = this.system;
139+
return map;
140+
}
141+
132142
public Or<F extends Type[]>(...types: F) {
133143
const or = new classes.OrType<F>(types);
134144
or.system = this.system;
@@ -176,6 +186,8 @@ export class TypeBuilder {
176186
),
177187
).options(node);
178188
}
189+
case 'map':
190+
return this.Map(this.import(node.type), node);
179191
case 'const':
180192
return this.Const(node.value).options(node);
181193
case 'or':

src/json-type/type/__tests__/fixtures.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ export const everyType = t.Object(
1717
t.prop('emptyArray', t.arr.options({max: 0})),
1818
t.prop('oneItemArray', t.arr.options({min: 1, max: 1})),
1919
t.prop('objWithArray', t.Object(t.propOpt('arr', t.arr), t.propOpt('arr2', t.arr))),
20+
t.prop('emptyMap', t.map),
21+
t.prop('mapWithOneNumField', t.Map(t.num)),
22+
t.prop('mapOfStr', t.Map(t.str)),
2023
);
2124

2225
export const everyTypeValue: TypeOf<SchemaOf<typeof everyType>> = {
@@ -37,4 +40,12 @@ export const everyTypeValue: TypeOf<SchemaOf<typeof everyType>> = {
3740
objWithArray: {
3841
arr: [1, 2, 3],
3942
},
43+
emptyMap: {},
44+
mapWithOneNumField: {
45+
a: 1,
46+
},
47+
mapOfStr: {
48+
a: 'a',
49+
b: 'b',
50+
},
4051
};

src/json-type/type/__tests__/getJsonSchema.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ test('can print a type', () => {
3636
)
3737
.options({format: 'cbor'}),
3838
),
39+
t.prop('map', t.Map(t.str)),
3940
)
4041
.options({unknownFields: true});
4142
// console.log(JSON.stringify(type.toJsonSchema(), null, 2));
@@ -83,6 +84,14 @@ test('can print a type', () => {
8384
"id": {
8485
"type": "string",
8586
},
87+
"map": {
88+
"patternProperties": {
89+
".*": {
90+
"type": "string",
91+
},
92+
},
93+
"type": "object",
94+
},
8695
"numberProperty": {
8796
"exclusiveMinimum": 3.14,
8897
"type": "number",
@@ -178,6 +187,7 @@ test('can print a type', () => {
178187
"unionProperty",
179188
"operation",
180189
"binaryOperation",
190+
"map",
181191
],
182192
"type": "object",
183193
}

src/json-type/type/__tests__/random.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,23 @@ test('generates random JSON', () => {
1313
t.prop('name', t.str),
1414
t.prop('tags', t.Array(t.str)),
1515
t.propOpt('scores', t.Array(t.num)),
16+
t.prop('refs', t.Map(t.str)),
1617
);
1718
const json = type.random();
1819
expect(json).toMatchInlineSnapshot(`
1920
{
2021
"id": "",
2122
"name": "1",
23+
"refs": {
24+
"259<@CGK": "UY\\\`c",
25+
";>BEILPT": "^beimp",
26+
"HKORVY]\`": "korvy}#",
27+
"LOSWZ^ae": "pswz #'*",
28+
"_cfjmqtx": "гггггг诶诶诶诶",
29+
"nquy|"%)": "4",
30+
"w{ $'+/2": "=@",
31+
"гггг诶诶诶诶": "MQTX",
32+
},
2233
"tags": [
2334
"@CG",
2435
"QUY\\\`",

0 commit comments

Comments
 (0)