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

Clarify: cast is not required in order to see properties of derived types. #532

Merged
merged 1 commit into from
May 16, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
88 changes: 60 additions & 28 deletions graph/patterns/subtypes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Microsoft Graph API Design Pattern

*A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This lets us model collections of resources that have slightly different metadata and behavior.*
*A frequent pattern in Microsoft Graph is to have a small type hierarchy, a base type with a few subtypes. This lets us model collections of resources that have slightly different properties and behavior.*

## Problem

Expand All @@ -27,9 +27,9 @@ You can consider related patterns such as [facets](./facets.md) and [flat bag of
## Issues and considerations

When introducing a new subtype to the hierarchy, developers need to ensure that
the new subtype doesn't change the semantic of the type hierarchy with its implicit constraints.
the new subtype doesn't change the semantic of the type hierarchy or collections of the specified base type with implicit constraints.

To retrieve properties specific for a derived type, an API request URL might need to include casting to the derived type. If the type hierarchy is very deep, then the resulting URL might become very long and not easily readable.
To reference properties specific to a derived type, an API request URL might need to include a segment casting to the derived type. If the type hierarchy is very deep, then the resulting URL might become very long and not easily readable.

There are a few considerations to take into account when new subtypes are introduced:

Expand All @@ -52,65 +52,99 @@ and groups stored in Azure Active Directory. Because any directoryObject object
</Key>
<Property Name="id" Type="Edm.String" Nullable="false" />
</EntityType>
<EntityType *Name*="directoryObject" *BaseType*="graph.entity" />
<Property *Name*="deletedDateTime" *Type*="Edm.DateTimeOffset" />
<EntityType Name="directoryObject" BaseType="graph.entity" />
<Property Name="deletedDateTime" Type="Edm.DateTimeOffset" />
<EntityType/>
```

Groups and users are derived types and modeled as follows:

```XML
 <EntityType Name="group" BaseType="graph.directoryObject" />
        <Property Name="accessType" Type="graph.groupAccessType"
<EntityType Name="group" BaseType="graph.directoryObject" />
<Property Name="description" Type="Edm.String" />
...
</EntityType>
<EntityType Name="user" BaseType="graph.directoryObject">
        <Property Name="aboutMe" Type="Edm.String" />
<Property Name="jobTitle" Type="Edm.String" />
...
</EntityType>
```

An API request to get members of a group returns a heterogeneous collection of
users and groups where each element can be a user or a group, and has an
additional property @odata.type for a variant subtype:
additional `@odata.type` property that specifies the subtype:

```
GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members?$select=id,displayName
GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members

Response payload shortened for readability:
Response payload shortened for readability. The deletedDateTime property from the base type is a non-default property and is only returned if explicitly requested.

{
"@odata.context":
"https://graph.microsoft.com/v1.0/\$metadata\#directoryObjects",
    "value": [
        {          
"https://graph.microsoft.com/v1.0/$metadata#directoryObjects",
"value": [
{
"@odata.type": "#microsoft.graph.user",
"id": "37ca648a-a007-4eef-81d7-1127d9be34e8",
"displayName": "John Cob"
"jobTitle": "CEO",
...
},
{
"@odata.type": "#microsoft.graph.group",
"id": "45f25951-d04f-4c44-b9b0-2a79e915658d",
"displayName": "Department 456"
"description": "Microsoft Graph API Reviewers",
...
},
...
    ]
]
}
```

Addressing a property of the subtype, for example, in `$filter` or `$select`, requires prefixing the property with the fully-qualified name of the subtype (or type derived from the subtype) on which it is defined. To filter on the `jobTitle` for the user type, you need to qualify the property with `microsoft.graph.user`.

The following query returns all groups that are members of group a94a666e-0367-412e-b96e-54d28b73b2db, as well as users that are members and whose jobTitle is CEO.

```
GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members?$filter=microsoft.graph.user/jobTitle eq 'CEO'

Response payload shortened for readability:

{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#directoryObjects",
"value": [
{
"@odata.type": "#microsoft.graph.user",
"id": "37ca648a-a007-4eef-81d7-1127d9be34e8",
"jobTitle": "CEO",
...
},
{
"@odata.type": "#microsoft.graph.group",
"id": "45f25951-d04f-4c44-b9b0-2a79e915658d",
"description": "Microsoft Graph API Reviewers",
...
},
...
]
}
```

An API request for a subtype-specific property requires type casting to the subtype; that is, to retrieve the jobTitle property enabled for the user type, you need to cast from the directoryObject collection items to the `microsoft.graph.group` derived type.
An entire collection can be cast to a particular subtype by appending the fully-qualified subtype name to the URL. Doing so filters the collection to members of (or derived from) that particular subtype, and makes the properties of that subtype available without casting. In this case, the `@odata.type` attribute is not returns for records of the specified subtype because the `@odata.context` indicates that the entire collection is consists of the particular subtype. Types derived from that subtype do still have the `@odata.type` attribute.

The following query returns only users that are members of group a94a666e-0367-412e-b96e-54d28b73b2db and whose jobTitle is CEO.

```
GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members/microsoft.graph.user?$select=displayName,jobTitle
GET https://graph.microsoft.com/v1.0/groups/a94a666e-0367-412e-b96e-54d28b73b2db/members/microsoft.graph.user?$filter=jobTitle eq 'CEO'

Response payload shortened for readability:

{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(displayName,jobTitle)",
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users",
"value": [
{
"displayName": "John Cob",
"jobTitle": "RESEARCHER II"
"id": "37ca648a-a007-4eef-81d7-1127d9be34e8",
"jobTitle": "CEO",
...
},
...
]
Expand All @@ -124,9 +158,7 @@ POST https://graph.microsoft.com/v1.0/directoryObjects

{
"@odata.type": "#microsoft.graph.group",
"displayName": "Library Assist",
"mailEnabled": false,
"mailNickname": "library",
"securityEnabled": true
"description": "Microsoft Graph API Reviewers",
...
}
```