Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assertions and/or parsers should respect the default tag #930

Open
Abion47 opened this issue Jan 18, 2024 · 2 comments
Open

Assertions and/or parsers should respect the default tag #930

Abion47 opened this issue Jan 18, 2024 · 2 comments
Labels
enhancement New feature or request good first issue Good for newcomers question Further information is requested

Comments

@Abion47
Copy link

Abion47 commented Jan 18, 2024

Feature Request

When creating my data models, I make ample use of tags to make them easily serializable into a JSON schema format. Another feature I use are the validation and parsing functions, but I have noticed that they don't seem to respect when a field has the Default tag.

I would've expected that when I run a validator on an object or a parse on a valid JSON string that omitted a field marked with the Default tag, the validator/parser would self-insert the specified default value for that field. This is especially true for parser functions, where the common use case is to validate user input and I would expect omitted fields with default values to be automatically populated.

I'm not sure if the desired approach should be to mark the field with the default value as optional or not, so I will include expected behavior for both approaches:

Example (Required Field)

type Foo = {
  bar: boolean & tags.Default<true>
}

typia.assert<Foo>({});
typia.validate<Foo>({});
typia.json.isParse<Foo>('{}');
typia.json.assertParse<Foo>('{}');
typia.json.validateParse<Foo>('{}');

Current Behavior

// <throws Error>
// { success: false, errors: [ ... ], data: undefined }
// null
// <throws Error>
// { success: false, errors: [ ... ], data: undefined }

Expected Behavior

// { bar: true }
// { success: true, errors: [], data: { bar: true } }
// { bar: true }
// { bar: true }
// { success: true, errors: [], data: { bar: true } }

Example (Optional Field)

type Foo = {
  bar?: boolean & tags.Default<true>
}

typia.assert<Foo>({});
typia.validate<Foo>({});
typia.json.isParse<Foo>('{}');
typia.json.assertParse<Foo>('{}');
typia.json.validateParse<Foo>('{}');

Current Behavior

// {}
// { success: true, errors: [], data: {} }
// {}
// {}
// { success: true, errors: [], data: {} }

Expected Behavior

// { bar: true }
// { success: true, errors: [], data: { bar: true } }
// { bar: true }
// { bar: true }
// { success: true, errors: [], data: { bar: true } }

// Ideally if possible, the returned type of `(data.)bar` would also be made non-nullable, though 
// that might also be non-desirable for type transparency reasons.)

Alternative Solution: applyDefaults Function

If for whatever reason it is undesired or overly complicated to bundle this functionality into the existing parsing and validating functions, an alternative solution would be a new function that analyzes the missing optional fields of an object and supplies any default values from the defined tags:

Definition

export type DefaultedFields<T> = /* T with all fields of T that have `Default` tags made non-nullable */;

export function applyDefaults<T>(input: T): DefaultedFields<T>;

// Convenience functions, equivalent to calling `applyDefaults<T>(func<T>(input))`

export function assertApplyDefaults<T>(input: T): DefaultedFields<T>;
export function assertEqualsApplyDefaults<T>(input: T): DefaultedFields<T>;
export function validateApplyDefaults<T>(input: T): IValidation<DefaultedFields<T>>;
export function validateEqualsApplyDefaults<T>(input: T): IValidation<DefaultedFields<T>>;

export namespace json {
  export function isParseApplyDefaults<T>(input: string): Primitive<DefaultedFields<T>> | null;
  export function assertParseApplyDefaults<T>(input: string): Primitive<DefaultedFields<T>>;
  export function validateParseApplyDefaults<T>(input: string): IValidation<Primitive<DefaultedFields<T>>>;
}

Usage

type Foo = {
  bar?: boolean & tags.Default<true>
}

typia.applyDefaults<Foo>({});
// { bar: true } as { bar: boolean }

typia.assertApplyDefaults<Foo>({});
// { bar: true } as { bar: boolean }

typia.assertEqualsApplyDefaults<Foo>({});
// { bar: true } as { bar: boolean }

typia.validateApplyDefaults<Foo>({});
// { success: true, errors: [], data: { bar: true } as { bar: boolean } }

typia.validateEqualsApplyDefaults<Foo>({});
// { success: true, errors: [], data: { bar: true } as { bar: boolean } }

typia.json.isParseApplyDefaults<Foo>('{}');
// { bar: true } as { bar: boolean }

typia.json.assertParseApplyDefaults<Foo>('{}');
// { bar: true } as { bar: boolean }

typia.json.validateParseApplyDefaults<Foo>('{}');
// { success: true, errors: [], data: { bar: true } as { bar: boolean } }
@samchon samchon added enhancement New feature or request good first issue Good for newcomers labels Jan 19, 2024
@samchon
Copy link
Owner

samchon commented Jan 19, 2024

I've designed the tags.Default<T> for only swagger documentation, but it seems good idea.

However, I am not sure that the function of assigning default values is necessary only in parse, and the names of functions, such as validateParseApplyDefaults<T>(), become too long and complicated.

@samchon samchon added the question Further information is requested label Jan 19, 2024
@Abion47
Copy link
Author

Abion47 commented Jan 23, 2024

I wasn't sure about the convenience functions and only really included them for completeness. I would imagine that this feature would either work implicitly for everything that supports in (in which case those functions are redundant) or would be an optional addition (in which case those functions are extraneous). In either case, they are definitely riding the edge of being too wordy to be practically useful.

As far as for where the default field population would occur, my thinking would be that it would work everywhere that validates an input and returns a confirmed object of the type. This would mean assert, validate, and the parse family of functions. The http functions and protobuf.decode almost certainly should populate default fields as well considering their purpose, while clone and prune it probably shouldn't as it would fall outside of their intended use case. As for random, any field with a default value should never be null/undefined, and instead should randomly flip between a randomly populated value and the default value.

The biggest exception would be is, whose role is merely to look at an object and return a boolean indicating whether or not that object already satisfies the type. In this case, mutating the object itself with default values would be an undesired side-effect. (What this means for isParse is less clear, but ultimately I think it shouldn't mutate the object either to maintain feature parity with is.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants