-
Notifications
You must be signed in to change notification settings - Fork 207
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
TypeScript - anyOf/oneOf arrays do not emit the correct serialization and deserialization information #5353
Comments
@baywet my thoughts in this issue is to reintroduce the wrapper class for the composed types. I don't think there is any other way to deserialize the types without atleast some changes in the |
Here is an example for the changes /* tslint:disable */
/* eslint-disable */
// Generated by Microsoft Kiota
// @ts-ignore
import { type BaseRequestBuilder, type Parsable, type ParsableFactory, type ParseNode, type RequestConfiguration, type RequestInformation, type RequestsMetadata, type SerializationWriter } from '@microsoft/kiota-abstractions';
/**
* Creates a new instance of the appropriate class based on discriminator value
* @param parseNode The parse node to use to read the discriminator value and create the object
* @returns {Success}
*/
// @ts-ignore
export function createSuccessFromDiscriminatorValue(parseNode: ParseNode | null | undefined) : ((instance?: Parsable) => Record<string, (node: ParseNode) => void>) {
return deserializeIntoSuccess;
}
/**
* The deserialization information for the current model
* @returns {Record<string, (node: ParseNode) => void>}
*/
// @ts-ignore
export function deserializeIntoSuccess(success: Success | null | undefined = {} as Success) : Record<string, (node: ParseNode) => void> {
if (!success) return {};
return {
"string": n => { success.string = n.getCollectionOfPrimitiveValues<string>(); },
"successString": n => { success.successString = n.getStringValue(); },
}
}
/**
* Builds and executes requests for operations under /example1
*/
export interface Example1RequestBuilder extends BaseRequestBuilder<Example1RequestBuilder> {
/**
* Test generating error with message property.
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns {Promise<Success>}
*/
post(requestConfiguration?: RequestConfiguration<object> | null | undefined) : Promise<Success | undefined>;
/**
* Test generating error with message property.
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns {RequestInformation}
*/
toPostRequestInformation(requestConfiguration?: RequestConfiguration<object> | null | undefined) : RequestInformation;
}
/**
* Serializes information the current object
* @param writer Serialization writer to use to serialize this model
*/
// @ts-ignore
export function serializeSuccess(writer: SerializationWriter, success: Success | null | undefined = {} as Success) : void {
if (success === undefined || success === null) return;
if(success.string){
writer.writeCollectionOfPrimitiveValues<string>(undefined, success.string);
} else if(success.successString){
writer.writeStringValue(undefined, success.successString);
}
}
/**
* Composed type wrapper for classes {@link string[]}, {@link string}
*/
export interface Success extends Parsable {
/**
* Composed type representation for type {@link string[]}
*/
string?: string[] | null;
/**
* Composed type representation for type {@link string}
*/
successString?: string | null;
}
/**
* Uri template for the request builder.
*/
export const Example1RequestBuilderUriTemplate = "{+baseurl}/example1";
/**
* Metadata for all the requests in the request builder.
*/
export const Example1RequestBuilderRequestsMetadata: RequestsMetadata = {
post: {
uriTemplate: Example1RequestBuilderUriTemplate,
responseBodyContentType: "application/json",
adapterMethodName: "send",
responseBodyFactory: createSuccessFromDiscriminatorValue,
},
};
/* tslint:enable */
/* eslint-enable */ |
The approach to re-introduce the wrapper class is because I cant think of any other way to deserialize the classes that would not result in changing the ParseNode factory. As it is, deserialization of the composed types can be any union of primitives, collections or objects. It would not be possible to evaluate the correct Happy to hear your thoughts on the same |
Just to confirm here @rkodev, Is the evaluation of the correct Assuming the deserializer and serializer functions are correctly generated with the type information from the description, could we possibly
Thoughts?
I believe using the first class features of the language would be the best outcome here if we can get there. |
Should be possible
Should be possible with the
This will need to be handle at library level since we do risk generation of a larger amount of code. The only challenge on this might be how to detect if the ArrayBuffer is a Parsable type X or Parsable Type Y, or a collection of type X. While it might be easier to evaluate if the content of the arraybuffer is a primitive/collection of primitives it might mean to brute force the responses for interfaces? Another issue I found with brute force approach, you might find type X and type Y have an intersecting property i.e |
Won't the generated deserailizer function do all this? All you would need to do is initialize a ParseNode instance with the stream and pass it to the function. Yes? |
Thanks for adding much needed details on this topic.
If we could start unblocking this, we'd probably get to the next logical blocker (which might be the parsenode interface itself?) For the serialization case, I think having the serializeMethod being a triage method should suffice as you've illustrated, and shouldn't require wrapper classes, anything I'm missing here? |
@andrueastman @baywet I have a possible solution for this
We can add an alias for Parsable to the exported type e.g - export type Success = string[] | string;
+ export type Success = string[] | string | Parsable;
/**
* Creates a new instance of the appropriate class based on discriminator value
* @param parseNode The parse node to use to read the discriminator value and create the object
* @returns {string[] | string}
*/
// @ts-ignore
export function createSuccessFromDiscriminatorValue(parseNode: ParseNode | undefined) : ((instance?: Parsable) => Record<string, (node: ParseNode) => void>) {
return deserializeIntoSuccess;
}
/**
* The deserialization information for the current model
* @returns {Record<string, (node: ParseNode) => void>}
*/
// @ts-ignore
-export function deserializeIntoSuccess(success: Partial<string[] | string> | undefined = {}) : Record<string, (node: ParseNode) => void> {
+export function deserializeIntoSuccess(success: Partial<Success> | undefined = {}) : Record<string, (node: ParseNode) => void> {
+ // add a 'default' call if the key is an empty string?
return {
+ "": n => { success = n.getStringValue() ?? n.getCollectionOfPrimitiveValues<string>(); },
}
}
|
This only affects the objects / collection of objects when a type discriminator is not present, in such cases I'm not sure if we can conclusively determine the result unless we generate a wrapper class exclusively for this scenarios? that is IFF we do not have a discriminator on the return type and it involves objects then we can return the wrapper class? Another option is using intersection types when intersecting only objects / only primitives / only collections of objects e.t.c . However this is will not work with an intersection involving types that 'collide' like an array e.t.c My thoughts are we should use the wrapper as a safe fallback when types are not 'compatible' and a discriminator is not provided. |
switching from typescript union types to wrapper types is going to cause a lot of grief to people evolving their OAD:
We should steer clear of any solution that's not consistent through time. I like your Partial/add IParsable by default suggestion here, might be worth exploring. Let us know what your results are. |
@baywet @andrueastman Could you look at this solution . The only remaing bit would be to add a default call to serialize composed type as in this example export function deserializeIntoSuccess(success: Partial<Parsable | string[] | string> | undefined = {}) : Record<string, (node: ParseNode) => void> {
return {
"" : n => { success = n.getStringValue() ?? n.getCollectionOfPrimitiveValues<string>(); },
}
} |
As additional information, GitHub's api (api.github.com) for path |
Follow up of #5348 which revealed multiple bugs in TypeScript composed types generation
Repro
(or change the oneOf by anyOf)
Will emit the following
For the serialization code
Multiple things are wrong here:
for the deserialization code
Multiple things are wrong here:
for the factory
Multiple things are wrong again here:
The text was updated successfully, but these errors were encountered: