Skip to content

Legacy Hierarchy Building

This document explains how to use the @Azure.ClientGenerator.Core.Legacy.hierarchyBuilding decorator to change the base type of a model in generated client SDKs.

The @hierarchyBuilding decorator changes the base type (parent class) of a model in the generated SDK type graph. It does not validate property presence at decoration time. Instead, property reconciliation happens during SDK type graph building.

Use @hierarchyBuilding when you need to:

  • Rebase a model onto a different ancestor in the inheritance chain (e.g., skip intermediate models)
  • Rebase a model onto an unrelated base that shares common properties (e.g., brownfield ARM resources onto TrackedResource)
  • Create multi-level discriminated hierarchies in generated SDKs

When the decorator is applied, the following rules govern how properties are reconciled:

Properties from removed intermediate ancestors (between the target’s original parent and the new base) are “lifted” onto the rebased model as its own properties.

model C {
c?: string;
}
model B extends C {
b?: string;
}
@Azure.ClientGenerator.Core.Legacy.hierarchyBuilding(C)
model A extends B {
a?: string;
}
// SDK result: A extends C. A.properties = { a, b }. C.properties = { c }.
// 'b' was lifted from removed intermediate B.

Properties whose names are supplied by the new base chain are dropped from the model (they are inherited instead).

model B {
propB: string;
}
model A {
...B;
propA: string;
}
@@Azure.ClientGenerator.Core.Legacy.hierarchyBuilding(A, B);
// SDK result: A extends B. A.properties = { propA }. B.properties = { propB }.
// propB was A's own property via spread, but is now supplied by new base → dropped.

When dropping a property, if the type on the dropped property is incompatible with the same-named property on the new base chain, a legacy-hierarchy-building-conflict warning with property-type-mismatch message is emitted. The property is still dropped.

model C {
shared?: int32;
}
model OldBase {
shared?: string; // different type than C.shared
}
@Azure.ClientGenerator.Core.Legacy.hierarchyBuilding(C)
model A extends OldBase {
a?: string;
}
// SDK result: A extends C. A.properties = { a }.
// Warning emitted: 'shared' type mismatch (string vs int32).

Compatible types are silently dropped without warnings:

scalar azureLocation extends string;
model C {
kind: string;
location: string;
}
model OldBase {
kind: "old";
location: azureLocation;
}
@Azure.ClientGenerator.Core.Legacy.hierarchyBuilding(C)
model A extends OldBase {
a?: string;
}
// SDK result: A extends C. A.properties = { a }.
// No warning: literal "old" is assignable to string, azureLocation is assignable to string.

Discriminator properties are never dropped, even if the new base has a same-named property.

Use the decorator to create deeper inheritance in discriminated models:

@discriminator("kind")
model Animal {
kind: string;
name: string;
}
alias PetContent = {
trained: boolean;
};
model Pet extends Animal {
kind: "pet";
...PetContent;
}
alias DogContent = {
breed: string;
};
@Azure.ClientGenerator.Core.Legacy.hierarchyBuilding(Pet)
model Dog extends Animal {
kind: "dog";
...PetContent;
...DogContent;
}
// SDK result: Dog extends Pet. Dog.properties = { kind, breed }.
// 'trained' is supplied by Pet → dropped. 'kind' is a discriminator → preserved.

A common pattern for Azure resources that need to be rebased onto TrackedResource:

model Resource {
id?: string;
name?: string;
type?: string;
}
model TrackedResource extends Resource {
location: string;
tags?: Record<string>;
}
model Foo extends Resource {
properties: FooProperties;
location?: string;
tags?: Record<string>;
}
@@Azure.ClientGenerator.Core.Legacy.hierarchyBuilding(Foo, TrackedResource);
// SDK result: Foo extends TrackedResource. Foo.properties = { properties }.
// location and tags are supplied by TrackedResource → dropped.
// id, name, type are supplied by Resource (in TrackedResource's chain) → dropped.