Enhance polymorphic deserialization to handle multi-level inheritance
Currently the deserializer cannot instantiate types that are not direct descendants of the declared return type. Instead it will default to the base type.
This was a performance compromise because in Microsoft Graph all types derive from a base type "entity" and if any operation returned an entity it would be expensive to search the entire tree of types looking for the correct type.
One solution to consider in the future is to create a global mapping table that is aware that Graph has globally unique discriminators so that it can reduce the duplication of mapping tables. We would then move the type mappings for the types into request builders and property deserializers.
@darrelmiller should we move this one to v3?
I would be keen to see this fixed in a way which doesn't add additional hidden requirements on a schema - if we have a nested hierarchy of types with discriminators at each level the currently generated code is so very close to working out of the box.
Perhaps a switch if the main internal use of the generated code is for an API where every type is a subclass of the same base? "useGlobalDiscriminatorTable" or something? That way schemas which can tolerate the performance impact of recursive descent can avoid the need for undocumented globally unique discriminators?
Thanks for your interest in this. We unlikely to add a switch for that aspect, it's too specific. We more likely to ship this as a breaking change (major version required). This is because for a same type, multiple discriminator information can be present depending on whether it's part of an inheritance structure, union type, or a specific endpoint has restrictions. (e.g. a rollingVehicules endpoint that'd return car, bus, train but not plane or boat)
Thanks for your interest in this. We unlikely to add a switch for that aspect, it's too specific. We more likely to ship this as a breaking change (major version required). This is because for a same type, multiple discriminator information can be present depending on whether it's part of an inheritance structure, union type, or a specific endpoint has restrictions. (e.g. a rollingVehicules endpoint that'd return car, bus, train but not plane or boat)
That's also a reasonable fix, the impression I got from the comments above is that it was deliberately not done initially because the design of the Graph API was such that it would add a reasonable overhead because every single type would need to traverse t the same object hierarchy.
You are correct, that's the initial design and limitations trade-off we made. Effectively, at the time, every way we looked at it, we ended up with either bad performance or circular dependencies. We then decided to take on this limitation in order to ship something that worked in time. And so far this decision has worked well both for Microsoft Graph and other APIs. In the case of Microsoft Graph, we have a couple of edge cases (notably education scenarios as far as I can remember), but no broad impact to the customers.
I'm being blocked by this limitation, although I'm not entirely sure it's the same issue.
Here's my scenario, in simplified form:
public abstract class Building;
public class Residence : Building;
public sealed class Mansion : Residence;
The way the API works, is that:
/buildingsreturns aBuilding[], whose instances can beResidenceorMansion/residencesreturns aResidence[], whose instances can beResidenceorMansion/mansionsreturns aMansion[], whose instances areMansion
In openapi.json, the discriminator and its mappings are defined in the Building component schema. The derived types are defined as allOf: [ ref-baseType, inline-own-properties ].
The CreateFromDiscriminatorValue in the generated Building class contains the mappings for the three types, but Residence and Mansion have no mappings. It seems to me that:
Building.CreateFromDiscriminatorValueshould switch overBuilding/Residence/Mansion(already the case)Residence.CreateFromDiscriminatorValueshould switch overResidence/Mansion(currently alwaysResidence)Mansion.CreateFromDiscriminatorValueshould returnMansion(already the case)
The problem here is that Mansion instances returned from /residences are deserialized as Residence instances, which makes it impossible to access the properties defined on Mansion (after upcast). This makes the endpoint effectively useless. Is there a workaround?
Note that the discriminator names/values are not globally unique in the API, there exist multiple (unrelated) type hierarchies.