Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Custom Format Serialization Fails with Discriminated Unions but Works with Direct Schema #779

Copy link
Copy link
@123NeNaD

Description

@123NeNaD
Issue body actions

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

5.2.2

Plugin version

6.0.1

Node.js version

22.14.0

Operating system

Windows

Operating system version (i.e. 20.04, 11.3, 10)

10

Description

Bug Description

Custom AJV formats configured in serializerOpts work correctly when used directly in response schemas, but fail when the same schema is used within a discriminated union (TypeBox Type.Union).

Configuration

The custom objectId format is configured in both AJV validation and serialization:

const server: FastifyInstance = fastify({
  ajv: {
    customOptions: {
      coerceTypes: 'array',
      removeAdditional: 'all',
      useDefaults: true,
      formats: {
        objectId: {
          type: 'string',
          validate: (value) => ObjectId.isValid(value),
        },
      },
      keywords: ['transform'],
    },
  },
  serializerOpts: {
    ajv: {
      formats: {
        objectId: {
          type: 'string',
          validate: (value) => ObjectId.isValid(value),
        },
      },
    },
  },
});

Schema Definition

import { Type, Static } from '@sinclair/typebox';
import { ObjectId } from 'mongodb';

const BaseShapeSchema = Type.Object({
  _id: Type.Unsafe<ObjectId>({
    type: 'string',
    format: 'objectId',
  }),
  name: Type.String(),
});

const CircleSchema = Type.Intersect([
  BaseShapeSchema,
  Type.Object({
    type: Type.Literal('circle'),
    radius: Type.Number(),
  }),
]);

const RectangleSchema = Type.Intersect([
  BaseShapeSchema,
  Type.Object({
    type: Type.Literal('rectangle'),
    width: Type.Number(),
    height: Type.Number(),
  }),
]);

// Discriminated union
const ShapeSchema = Type.Union([CircleSchema, RectangleSchema]);

Expected Behavior

Both route configurations should serialize successfully and return the ObjectId as a string.

Actual Behavior

Works: Direct schema usage

server.route({
  method: 'GET',
  url: '/test',
  schema: {
    response: {
      200: Type.Object({
        shape: RectangleSchema, // Direct schema reference
      }),
    },
  },
  handler: async (request, reply) => {
    const rectangle = {
      type: 'rectangle',
      name: 'My Rectangle',
      _id: new ObjectId('507f1f77bcf86cd799439015'),
      width: 20,
      height: 15,
    };
    return reply.status(200).send({ shape: rectangle });
  },
});

Response: ✅ Success

{
  "shape": {
    "name": "My Rectangle",
    "_id": "507f1f77bcf86cd799439015",
    "type": "rectangle",
    "width": 20,
    "height": 15
  }
}

Fails: Discriminated union usage

server.route({
  method: 'GET',
  url: '/test',
  schema: {
    response: {
      200: Type.Object({
        shape: ShapeSchema, // Union schema reference
      }),
    },
  },
  handler: async (request, reply) => {
    const rectangle = {
      _id: new ObjectId('507f1f77bcf86cd799439015'),
      name: 'My Rectangle',
      type: 'rectangle',
      width: 20,
      height: 15,
    };
    return reply.status(200).send({ shape: rectangle });
  },
});

Error: ❌ Failure

TypeError: The value of '#/properties/shape' does not match schema definition.

Analysis

The issue appears to be that when using discriminated unions (Type.Union), the custom format is not being applied correctly by the serializer. The same schema works when referenced directly but fails when wrapped in a union type.

Link to code that reproduces the bug

https://github.com/fastify/fast-json-stringify

Expected Behavior

Both route configurations should serialize successfully and return the ObjectId as a string.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.