---
title: Legacy Hierarchy Building
---

import { ClientTabs, ClientTabItem } from "@components/client-tabs";

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.

:::caution
Legacy hierarchy building is NOT a recommended pattern and should only be used when specifically requested by SDK architects.
:::

## Overview

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

## Property Reconciliation Rules

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

### 1. Property Lifting

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.

<ClientTabs>

```typespec
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.
```

```python
class C:
    c: Optional[str]


class B(C):
    c: Optional[str]
    b: Optional[str]


class A(B):
    c: Optional[str]
    b: Optional[str]
    a: Optional[str]
```

```csharp
public partial class C
{
    public string CProperty { get; }
}

public partial class B : C
{
    public string BProperty { get; }
}

public partial class A : B
{
    public string AProperty { get; }
}
```

```typescript
export interface C {
  c?: string;
}

export interface B extends C {
  b?: string;
}

export interface A extends B {
  a?: string;
}
```

```java
public class C {
    private String c;
}

public class B extends C {
    private String b;
    private String c;
}

public final class A extends B {
    private String a;
    private String b;
    private String c;
}
```

```go
type A struct {
    A *string
    B *string
    C *string
}
```

</ClientTabs>

### 2. Duplicate Dropping

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

<ClientTabs>

```typespec
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.
```

```python
class B:
    prop_b: str


class A(B):
    prop_b: str
    prop_a: str
```

```csharp
public partial class B
{
    public virtual string PropB { get; }
}

public partial class A : B
{
    public override string PropB { get; }
    public string PropA { get; }
}
```

```typescript
export interface B {
  propB: string;
}

export interface A extends B {
  propB: string;
  propA: string;
}
```

```java
public class B {
    private final String propB;
}

public final class A extends B {
    private final String propB;
    private final String propA;
}
```

```go
type A struct {
    // REQUIRED
    PropA *string

    // REQUIRED
    PropB *string
}

type B struct {
    // REQUIRED
    PropB *string
}
```

</ClientTabs>

### 3. Type Compatibility Check

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.

<ClientTabs>

```typespec
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).
```

```python
# A `legacy-hierarchy-building-conflict` warning is emitted at compile time.
class OldBase:
    shared: Optional[str]


class A(OldBase):
    shared: Optional[str]
    a: Optional[str]
```

```csharp
// Warning: legacy-hierarchy-building-conflict (property-type-mismatch for 'shared').
public partial class OldBase
{
    public string Shared { get; }
}

public partial class A : OldBase
{
    public string AProperty { get; }
}
```

```typescript
// Warning: legacy-hierarchy-building-conflict (property-type-mismatch for "shared").
export interface OldBase {
  shared?: string;
}

export interface A extends OldBase {
  a?: string;
}
```

```java
// Warning: legacy-hierarchy-building-conflict (property-type-mismatch for "shared").
public class OldBase {
    private String shared;
}

public final class A extends OldBase {
    private String a;
    private String shared;
}
```

```go
// Warning: legacy-hierarchy-building-conflict (property-type-mismatch for "shared").
type A struct {
    A      *string
    Shared *string
}
```

</ClientTabs>

Compatible types are silently dropped without warnings:

<ClientTabs>

```typespec
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.
```

```python
class OldBase:
    kind: Literal["old"] = "old"
    location: str


class A(OldBase):
    kind: Literal["old"] = "old"
    location: str
    a: Optional[str]
```

```csharp
public partial class OldBase
{
    public string Kind { get; } = "old";
    public string Location { get; }
}

public partial class A : OldBase
{
    public string AProperty { get; }
}
```

```typescript
export interface OldBase {
  kind: "old";
  location: string;
}

export interface A extends OldBase {
  a?: string;
}
```

```java
public class OldBase {
    private final String kind = "old";
    private final String location;
}

public final class A extends OldBase {
    private String a;
}
```

```go
type A struct {
    // REQUIRED
    Kind *string

    // REQUIRED
    Location *string
    A        *string
}
```

</ClientTabs>

### 4. Discriminator Preservation

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

## Common Use Cases

### Multi-Level Discriminated Hierarchy

Use the decorator to create deeper inheritance in discriminated models:

<ClientTabs>

```typespec
@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.
```

```python
class Animal(_Model):
    kind: str
    name: str


class Pet(Animal, discriminator="pet"):
    kind: Literal["pet"]
    trained: bool


class Dog(Pet, discriminator="dog"):
    kind: Literal["dog"]
    breed: str
```

```csharp
public abstract partial class Animal
{
    public string Name { get; }
}

public partial class Pet : Animal
{
    public bool Trained { get; }
}

public partial class Dog : Pet
{
    public string Breed { get; }
}
```

```typescript
export interface Animal {
  kind: string;
  name: string;
}

export interface Pet extends Animal {
  kind: "pet";
  trained: boolean;
}

export interface Dog extends Pet {
  kind: "dog";
  breed: string;
}

export type AnimalUnion = PetUnion | Animal;
export type PetUnion = Dog | Pet;
```

```java
public class Animal {
    private String kind = "Animal";
    private final String name;
}

public class Pet extends Animal {
    private String kind = "pet";
    private final boolean trained;
}

public final class Dog extends Pet {
    private String kind = "dog";
    private final String breed;
}
```

```go
type Animal struct {
    // REQUIRED
    Kind *string

    // REQUIRED
    Name *string
}

type Pet struct {
    // REQUIRED
    Kind *string

    // REQUIRED
    Name *string

    // REQUIRED
    Trained *bool
}

type Dog struct {
    // REQUIRED
    Breed *string

    // REQUIRED
    Kind *string

    // REQUIRED
    Name *string

    // REQUIRED
    Trained *bool
}
```

</ClientTabs>

### Brownfield ARM Resource Rebasing

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

<ClientTabs>

```typespec
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.
```

```python
class Resource(_Model):
    id: Optional[str]
    name: Optional[str]
    type: Optional[str]


class TrackedResource(Resource):
    location: str
    tags: Optional[dict[str, str]]


class Foo(TrackedResource):
    properties: "FooProperties"
    location: Optional[str]
```

```csharp
public partial class Resource
{
    public string Id { get; }
    public string Name { get; }
    public string Type { get; }
}

public partial class TrackedResource : Resource
{
    public string Location { get; }
    public virtual IDictionary<string, string> Tags { get; }
}

public partial class Foo : TrackedResource
{
    public FooProperties Properties { get; }
    public new string Location { get; }
    public override IDictionary<string, string> Tags { get; }
}
```

```typescript
export interface Resource {
  id?: string;
  name?: string;
  type?: string;
}

export interface TrackedResource extends Resource {
  location: string;
  tags?: Record<string, string>;
}

export interface Foo extends TrackedResource {
  properties: FooProperties;
  location?: string;
  tags?: Record<string, string>;
}
```

```java
public class Resource {
    private String id;
    private String name;
    private String type;
}

public class TrackedResource extends Resource {
    private final String location;
    private Map<String, String> tags;
}

public final class Foo extends TrackedResource {
    private final FooProperties properties;
    private String location;
    private Map<String, String> tags;
}
```

```go
type Foo struct {
    // REQUIRED
    Properties *FooProperties
    ID         *string
    Location   *string
    Name       *string
    Tags       map[string]*string
    Type       *string
}
```

</ClientTabs>
