Skip to content

Commit

Permalink
fix(object-opt-null): support for optional and nullable for inner obj…
Browse files Browse the repository at this point in the history
… fields
  • Loading branch information
asadali214 committed Jun 27, 2024
1 parent c7afd89 commit 3352fb2
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 28 deletions.
7 changes: 5 additions & 2 deletions packages/schema/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export function validateAndMap<T extends Schema<any, any>>(
);
const validationResult = schema.validateBeforeMap(value, contextCreator);
if (validationResult.length === 0) {
if (isOptionalNullable(schema.type())) {
if (isOptionalNullable(schema.type(), value)) {
return { errors: false, result: value };
}
return { errors: false, result: schema.map(value, contextCreator) };
Expand Down Expand Up @@ -142,14 +142,17 @@ export function validateAndUnmap<T extends Schema<any, any>>(
* @param schema Schema for type
*/
export function validateAndMapXml<T extends Schema<any, any>>(
value: unknown,
value: SchemaMappedType<T>,
schema: T
): ValidationResult<SchemaType<T>> {
const contextCreator = createSchemaContextCreator(
createNewSchemaContext(value, schema.type())
);
const validationResult = schema.validateBeforeMapXml(value, contextCreator);
if (validationResult.length === 0) {
if (isOptionalNullable(schema.type(), value)) {
return { errors: false, result: value };
}
return { errors: false, result: schema.mapXml(value, contextCreator) };
} else {
return { errors: validationResult };
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/src/types/nullable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function nullable<T, S>(
validateBeforeMapXml: (value, ctxt) =>
value === null ? [] : schema.validateBeforeMapXml(value, ctxt),
mapXml: (value, ctxt) =>
value === null ? null : schema.mapXml(value, ctxt),
isNullOrMissing(value) ? null : schema.mapXml(value, ctxt),
unmapXml: (value, ctxt) =>
value === null ? null : schema.unmapXml(value, ctxt),
};
Expand Down
43 changes: 27 additions & 16 deletions packages/schema/src/types/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
} from '../schema';
import { OptionalizeObject } from '../typeUtils';
import {
isOptional,
isOptionalNullable,
literalToString,
objectEntries,
objectKeyEncode,
Expand Down Expand Up @@ -456,24 +458,33 @@ function mapObject<T extends AnyObjectSchema>(

// Map known properties using the schema
for (const key in objectSchema) {
/* istanbul ignore else */
if (Object.prototype.hasOwnProperty.call(objectSchema, key)) {
const element = objectSchema[key];
const propName = element[0];
const propValue = objectValue[propName];
unknownKeys.delete(propName);

// Skip mapping for optional properties to avoid creating properties with value 'undefined'
if (
element[1].type().indexOf('Optional<') !== 0 ||
propValue !== undefined
) {
output[key] = element[1][mappingFn](
propValue,
ctxt.createChild(propName, propValue, element[1])
);
if (!Object.prototype.hasOwnProperty.call(objectSchema, key)) {
continue;
}

const element = objectSchema[key];
const propName = element[0];
const propValue = objectValue[propName];
unknownKeys.delete(propName);

if (isOptionalNullable(element[1].type(), propValue)) {
if (typeof value === 'undefined') {
// Skip mapping to avoid creating properties with value 'undefined'
continue;
}
output[key] = propValue;
continue;
}

if (isOptional(element[1].type(), propValue)) {
// Skip mapping to avoid creating properties with value 'undefined'
continue;
}

output[key] = element[1][mappingFn](
propValue,
ctxt.createChild(propName, propValue, element[1])
);
}

// Copy unknown properties over if additional properties flag is set
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/src/types/optional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function optional<T, S>(
? []
: schema.validateBeforeMapXml(value, ctxt),
mapXml: (value, ctxt) =>
typeof value === 'undefined' ? undefined : schema.mapXml(value, ctxt),
isNullOrMissing(value) ? undefined : schema.mapXml(value, ctxt),
unmapXml: (value, ctxt) =>
typeof value === 'undefined' ? undefined : schema.unmapXml(value, ctxt),
};
Expand Down
11 changes: 8 additions & 3 deletions packages/schema/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,14 @@ export function isNullOrMissing(value: unknown): value is null | undefined {
return value === null || typeof value === 'undefined';
}

export function isOptionalNullable(type: string): boolean {
export function isOptional(type: string, value: unknown): boolean {
return type.startsWith('Optional<') && typeof value === 'undefined';
}

export function isOptionalNullable(type: string, value: unknown): boolean {
return (
type.startsWith('Optional<Nullable<') ||
type.startsWith('Nullable<Optional<')
(type.startsWith('Optional<Nullable<') ||
type.startsWith('Nullable<Optional<')) &&
isNullOrMissing(value)
);
}
37 changes: 32 additions & 5 deletions packages/schema/test/types/object.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {
lazy,
nullable,
number,
object,
optional,
Expand All @@ -14,6 +16,31 @@ describe('Object', () => {
const userSchema = object({
id: ['user_id', string()],
age: ['user_age', number()],
work: ['user_work', optional(lazy(() => workSchema))],
});

const workSchema = object({
id: ['work_id', optional(nullable(string()))],
});

it('should map valid object with lazy loaded sub object', () => {
const input = {
user_id: 'John Smith',
user_age: 50,
user_work: {
work_id: null,
},
};
const output = validateAndMap(input, userSchema);
const expected: SchemaType<typeof userSchema> = {
id: 'John Smith',
age: 50,
work: {
id: null,
},
};
expect(output.errors).toBeFalsy();
expect((output as any).result).toStrictEqual(expected);
});

it('should map valid object', () => {
Expand Down Expand Up @@ -69,13 +96,13 @@ describe('Object', () => {
"branch": Array [
"not an object",
],
"message": "Expected value to be of type 'Object<{id,age}>' but found 'string'.
"message": "Expected value to be of type 'Object<{id,age,work}>' but found 'string'.
Given value: \\"not an object\\"
Type: 'string'
Expected type: 'Object<{id,age}>'",
Expected type: 'Object<{id,age,work}>'",
"path": Array [],
"type": "Object<{id,age}>",
"type": "Object<{id,age,work}>",
"value": "not an object",
},
]
Expand Down Expand Up @@ -135,9 +162,9 @@ describe('Object', () => {
Given value: {\\"user_id\\":\\"John Smith\\"}
Type: 'object'
Expected type: 'Object<{id,age}>'",
Expected type: 'Object<{id,age,work}>'",
"path": Array [],
"type": "Object<{id,age}>",
"type": "Object<{id,age,work}>",
"value": Object {
"user_id": "John Smith",
},
Expand Down

0 comments on commit 3352fb2

Please sign in to comment.