# Runtime type for subclasses

This recipe is useful when you need to revive fields where several subclasses can appear.

An example of this is JSON generated using [Gson's `TypeAdapter`](https://google.github.io/gson/apidocs/com/google/gson/TypeAdapter.html). In fact, this example is based on [Gson's RuntimeTypeAdapterFactory](https://github.com/google/gson/blob/gson-parent-2.8.0/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java#L36)

We will use [`ajvMetadata`](/advanced/json_validation.md#majvmetadata-and-json-schema) to demonstrate how to get Modélico to generate complex JSON schemas. This is also an example of a schema with circular references.

```javascript
class Geometer extends M.Base {
  static innerTypes () {
    return Object.freeze({
      name: string({minLength: 1}),
      // Shape has multiple subclasses
      favouriteShape: _(Shape)
    })
  }
}
```

The `Shape` in the inner types can be either a `Diamond` or a `Circle`. Let's look at their definitions:

```javascript
const ShapeType = M.Enum.fromArray(['CIRCLE', 'DIAMOND'])

// We are going to use this metadata in several places, so by reusing it, we
// not only save unnecessary processing, but our generated JSON schema will
// also be more compact.
const greaterThanZero = number({
  exclusiveMinimum: 0
})

const reviver = (k, v) => {
  if (k !== '') {
    return v
  }

  switch (v.type) {
    case ShapeType.CIRCLE().toJSON():
      return new Circle(v)
    case ShapeType.DIAMOND().toJSON():
      return new Diamond(v)
    default:
      throw TypeError('Unsupported or missing shape type in the Shape reviver.')
  }
}

class Shape extends M.Base {
  toJSON () {
    const fields = M.fields(this)
    let type

    // the generated JSON must contain runtime type information for Modélico
    // to be able to revive it later
    switch (this[M.symbols.typeSymbol]()) {
      case Circle:
        type = ShapeType.CIRCLE()
        break
      case Diamond:
        type = ShapeType.DIAMOND()
        break
      default:
        throw TypeError('Unsupported Shape in the toJSON method.')
    }

    return Object.freeze(Object.assign({type}, fields))
  }

  static innerTypes () {
    return Object.freeze({
      // notice the self-reference here and how the subclasses are
      // going to extend Shape's inner types
      relatedShape: maybe(_(Shape))
    })
  }

  static metadata () {
    // We are going to use meta so that M.getSchema can give us a more
    // robust JSON schema. If you don't need that, you could return the
    // baseMetadata directly.
    const baseMetadata = Object.assign({}, base(Shape), {reviver})

    return meta(baseMetadata, {}, {}, () => ({
      anyOf: [
        Circle,
        Diamond
      ].map(x => M.getSchema(base(x), false))
    }))
  }
}
```

```javascript
class Circle extends Shape {
  constructor (props) {
    super(Circle, props)
  }

  area () {
    return Math.PI * this.radius() ** 2
  }

  static innerTypes () {
    return Object.freeze(Object.assign({}, super.innerTypes(), {
      radius: greaterThanZero
    }))
  }
}
```

```javascript
class Diamond extends Shape {
  constructor (props) {
    super(Diamond, props)
  }

  area () {
    return this.width() * this.height() / 2
  }

  static innerTypes () {
    return Object.freeze(Object.assign({}, super.innerTypes(), {
      width: greaterThanZero
      height: greaterThanZero
    }))
  }
}
```

With that, we can revive `Diamond`s

```javascript
const geometer1 = M.fromJS(Geometer, {
  name: 'Audrey',
  favouriteShape: {
    type: 'DIAMOND',
    width: 8,
    height: 7
  }
})

geometer1.favouriteShape().area() // => 28
```

as well as `Circle`s:

```javascript
const geometer2 = M.fromJS(Geometer, {
  name: 'Javier',
  favouriteShape: {
    type: 'CIRCLE',
    radius: 3
  }
})

geometer2.favouriteShape().area() // => Math.PI * 3 ** 2
```

Serialisation also works for `Diamond`s and `Circle`s

```javascript
geometer1.toJS()
geometer2.toJS()
```

respectively yield:

```javascript
{
  name: 'Audrey',
  favouriteShape: {
    type: 'DIAMOND',
    relatedShape: null,
    width: 8,
    height: 7
  }
}
```

```javascript
{
  name: 'Javier',
  favouriteShape: {
    type: 'CIRCLE',
    relatedShape: null,
    radius: 3
  }
}
```

Now remember how we have been using `ajvMetadata` and even enhanced the Shape metadata to account for its subtypes. This is going to allow us to get a very detailed schema that would not be easy to write by hand.

*Note: definitions are sequentially named to avoid collisions. In the example below, the definition numbers have gaps because some of them got reserved in case they'd be reused. Short sub-schemas (less than 2 keys and not arrays) are always inlined.*

```javascript
M.getSchema(_(Geometer))
```

yields:

```javascript
{
  type: 'object',
  properties: {
    name: {
      $ref: '#/definitions/2'
    },
    favouriteShape: {
      $ref: '#/definitions/3'
    }
  },
  required: ['name', 'favouriteShape'],
  definitions: {
    '2': {
      type: 'string',
      minLength: 1
    },
    '3': {
      anyOf: [
        {
          $ref: '#/definitions/4'
        },
        {
          $ref: '#/definitions/7'
        }
      ]
    },
    '4': {
      type: 'object',
      properties: {
        relatedShape: {
          $ref: '#/definitions/5'
        },
        radius: {
          $ref: '#/definitions/6'
        }
      },
      required: ['radius']
    },
    '5': {
      anyOf: [
        {
          type: 'null'
        },
        {
          $ref: '#/definitions/3'
        }
      ]
    },
    '6': {
      type: 'number',
      exclusiveMinimum: 0
    },
    '7': {
      type: 'object',
      properties: {
        relatedShape: {
          $ref: '#/definitions/5'
        },
        width: {
          $ref: '#/definitions/6'
        },
        height: {
          $ref: '#/definitions/6'
        }
      },
      required: ['width', 'height']
    }
  }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://modelico.javiercejudo.com/recipes/runtime_type_for_subclasses.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
