Skip to content

[typescript-fetch] oneOf models now consider primitives when converting #21464

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

Merged
merged 5 commits into from
Jul 24, 2025

Conversation

DavidGrath
Copy link
Contributor

PR checklist

  • Read the contribution guidelines.
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • File the PR against the correct branch: master (upcoming 7.x.0 minor release - breaking changes with fallbacks), 8.0.x (breaking changes without fallbacks)
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

The output looks like this:

import type { MyNumericValue } from './MyNumericValue';
import {
    instanceOfMyNumericValue,
    MyNumericValueFromJSON,
    MyNumericValueFromJSONTyped,
    MyNumericValueToJSON,
} from './MyNumericValue';

/**
 * @type MyCustomSpeed
 * A value that can be a number or a specific string.
 * @export
 */
export type MyCustomSpeed = Array<number> | Array<string> | Date | MyNumericValue | number | string;

export function MyCustomSpeedFromJSON(json: any): MyCustomSpeed {
    return MyCustomSpeedFromJSONTyped(json, false);
}

export function MyCustomSpeedFromJSONTyped(json: any, ignoreDiscriminator: boolean): MyCustomSpeed {
    if (json == null) {
        return json;
    }
    if (typeof json !== 'object') {
        return json;
    }
    if (instanceOfMyNumericValue(json)) {
        return MyNumericValueFromJSONTyped(json, true);
    }
    if(!(isNaN(new Date(json).getTime()))) {
        return json == null ? undefined : (new Date(json));
    }
    if(!(isNaN(new Date(json).getTime()))) {
        return json == null ? undefined : (new Date(json));
    }
    if(typeof json === 'string' && (json === 'fixed-value-a' || json === 'fixed-value-b' || json === 'fixed-value-c')) {
        return json;
    }
    if(typeof json === 'number' && (json === 10 || json === 20 || json === 30)) {
        return json;
    }
    if (Array.isArray(json)) {
        if (json.every(item => typeof item === 'number')) {
            return json;
        }
     }
    if (Array.isArray(json)) {
        if (json.every(item => typeof item === 'string')) {
            return json;
        }
     }
    return {} as any;
}

export function MyCustomSpeedToJSON(json: any): any {
    return MyCustomSpeedToJSONTyped(json, false);
}

export function MyCustomSpeedToJSONTyped(value?: MyCustomSpeed | null, ignoreDiscriminator: boolean = false): any {
    if (value == null) {
        return value;
    }
    if (typeof value !== 'object') {
        return value;
    }
    if (instanceOfMyNumericValue(value)) {
        return MyNumericValueToJSON(value as MyNumericValue);
    }
    if(value instanceof Date) {
        return ((value).toISOString().substring(0,10));
     }
    if(value instanceof Date) {
        return value == null ? undefined : ((value).toISOString());
    }
    if(typeof value === 'string' && (value === 'fixed-value-a' || value === 'fixed-value-b' || value === 'fixed-value-c')) {
        return value;
    }
    if(typeof value === 'number' && (value === 10 || value === 20 || value === 30)) {
        return value;
    }
    if (Array.isArray(value)) {
        if (value.every(item => typeof item === 'number') {
            return value;
        }
    }
    if (Array.isArray(value)) {
        if (value.every(item => typeof item === 'string') {
            return value;
        }
    }
    return {};
}

@joscha @macjohnny , please help review for TypeScript

@macjohnny
Copy link
Member

@DavidGrath can you resolve the merge conflict?

@DavidGrath
Copy link
Contributor Author

@macjohnny, yes, please give me till early Saturday or sooner

@DavidGrath
Copy link
Contributor Author

@macjohnny, resolved

@macjohnny macjohnny merged commit f1a0935 into OpenAPITools:master Jul 24, 2025
15 checks passed
@DavidGrath
Copy link
Contributor Author

@macjohnny thank you

@macjohnny
Copy link
Member

@DavidGrath thank you for the fix!

btpnlsl pushed a commit to btpnlsl/openapi-generator that referenced this pull request Jul 27, 2025
macjohnny pushed a commit that referenced this pull request Jul 28, 2025
…ipt-fetch oneOf logic #21057 #21464 (#21638)

* Add unit tests for current oneOf logic #21057 #21464

* Remove comment from issue_21259.yaml

---------

Co-authored-by: Chris Gual <cgual@omnidian.com>
@btpnlsl
Copy link
Contributor

btpnlsl commented Jul 28, 2025

@DavidGrath there was one other thing I noticed about the serialization / deserialization code which I thought I would check with you about. Does it make sense to distinguish between isDateType & isDateTimeType in a oneOf template when both data types are being coerced into Date?

In the sample OAS you provided for the bug two of the oneOf schemas are date/date-time related:

        - type: string
          format: date
        - type: string
          format: date-time

Which results in this code in the deserialization method (which is fine but the second if will never be true)

    // is getTime supposed to distinguish between `isDateType` & `isDateTimeType`?
    if (!(isNaN(new Date(json).getTime()))) {
        return json == null ? undefined : (new Date(json));
    }
    if (!(isNaN(new Date(json).getTime()))) {
        return json == null ? undefined : (new Date(json));
    }

But the serialization method might cause an issue:

    if (value instanceof Date) {
        return ((value).toISOString().substring(0,10));
    }
    if (value instanceof Date) {
        return value == null ? undefined : ((value).toISOString());
    }

Since the isDateType check will always come first, the serialization code will always truncate values to remove the time portion, even if initial representation came from a DateTime.

If we reverse the checks for isDateType & isDateTimeType in the template I think the method will default to serialize with the more precise format if both isDateType & isDateTimeType are present, but use the truncated format if only isDateTime is present.

Does that seem reasonable to you?

@wing328 wing328 added this to the 7.15.0 milestone Aug 9, 2025
@wing328 wing328 changed the title [typescript-fetch] oneOf models now consider primitives when converting. Issue #21259 [typescript-fetch] oneOf models now consider primitives when converting Aug 9, 2025
@DavidGrath
Copy link
Contributor Author

@btpnlsl , sorry for the delayed response. You're right. I mainly copied and modified the date and time logic from somewhere else and so I didn't think too deeply about the need to distinguish between the two or the need for extra precision. I think if I make another PR for a separate issue, I can include this improvement as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants