序列化响应 #

一、序列化概述 #

Fastify使用fast-json-stringify进行高性能JSON序列化,比JSON.stringify快2-3倍。

1.1 序列化流程 #

text
Handler返回数据
       │
       ▼
┌─────────────────┐
│ preSerialization│  ← 钩子处理
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Schema序列化    │  ← fast-json-stringify
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│    onSend       │  ← 发送前钩子
└────────┬────────┘
         │
         ▼
    发送响应

1.2 性能对比 #

方法 性能
JSON.stringify ~400,000 ops/s
fast-json-stringify ~1,200,000 ops/s

二、Schema序列化 #

2.1 基本序列化 #

javascript
fastify.get('/users/:id', {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          id: { type: 'integer' },
          name: { type: 'string' },
          email: { type: 'string' }
        }
      }
    }
  }
}, async (request, reply) => {
  return {
    id: 1,
    name: 'John',
    email: 'john@example.com',
    password: 'secret'
  }
})

响应(password被过滤):

json
{
  "id": 1,
  "name": "John",
  "email": "john@example.com"
}

2.2 数组序列化 #

javascript
fastify.get('/users', {
  schema: {
    response: {
      200: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            id: { type: 'integer' },
            name: { type: 'string' }
          }
        }
      }
    }
  }
}, async (request, reply) => {
  return [
    { id: 1, name: 'John', password: 'secret' },
    { id: 2, name: 'Jane', password: 'secret' }
  ]
})

2.3 嵌套对象序列化 #

javascript
fastify.get('/users/:id/profile', {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          id: { type: 'integer' },
          name: { type: 'string' },
          profile: {
            type: 'object',
            properties: {
              bio: { type: 'string' },
              avatar: { type: 'string' }
            }
          }
        }
      }
    }
  }
}, handler)

2.4 多状态码序列化 #

javascript
fastify.get('/users/:id', {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          id: { type: 'integer' },
          name: { type: 'string' }
        }
      },
      404: {
        type: 'object',
        properties: {
          error: { type: 'string' },
          message: { type: 'string' }
        }
      }
    }
  }
}, async (request, reply) => {
  const user = await findUser(request.params.id)
  
  if (!user) {
    reply.code(404)
    return { error: 'Not Found', message: 'User not found' }
  }
  
  return user
})

三、共享Schema #

3.1 定义共享Schema #

javascript
fastify.addSchema({
  $id: 'user',
  type: 'object',
  properties: {
    id: { type: 'integer' },
    name: { type: 'string' },
    email: { type: 'string' }
  }
})

fastify.addSchema({
  $id: 'error',
  type: 'object',
  properties: {
    error: { type: 'string' },
    message: { type: 'string' }
  }
})

3.2 使用共享Schema #

javascript
fastify.get('/users/:id', {
  schema: {
    response: {
      200: { $ref: 'user#' },
      404: { $ref: 'error#' }
    }
  }
}, handler)

fastify.get('/users', {
  schema: {
    response: {
      200: {
        type: 'array',
        items: { $ref: 'user#' }
      }
    }
  }
}, handler)

四、自定义序列化器 #

4.1 路由级序列化器 #

javascript
fastify.get('/custom', {
  serializerCompiler: ({ schema, method, url, httpStatus }) => {
    return (data) => JSON.stringify({
      success: true,
      data,
      timestamp: Date.now()
    })
  }
}, async (request, reply) => {
  return { message: 'Hello' }
})

4.2 全局序列化器 #

javascript
fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => {
  return (data) => {
    return JSON.stringify({
      success: true,
      data,
      timestamp: new Date().toISOString()
    })
  }
})

4.3 条件序列化 #

javascript
fastify.get('/conditional', {
  serializerCompiler: ({ schema, method, url, httpStatus }) => {
    return (data) => {
      if (httpStatus === 200) {
        return JSON.stringify({
          success: true,
          data
        })
      }
      return JSON.stringify({
        success: false,
        error: data
      })
    }
  }
}, handler)

五、禁用序列化 #

5.1 路由级禁用 #

javascript
fastify.get('/raw', {
  schema: {
    response: {
      200: {}
    }
  }
}, async (request, reply) => {
  reply.serializer(null)
  return 'Raw string response'
})

5.2 使用reply.serializer #

javascript
fastify.get('/manual', async (request, reply) => {
  reply.serializer(null)
  reply.type('text/plain')
  return 'Plain text response'
})

六、响应包装 #

6.1 统一响应格式 #

javascript
fastify.addHook('preSerialization', async (request, reply, payload) => {
  if (payload && typeof payload === 'object' && !payload.statusCode) {
    return {
      success: true,
      data: payload,
      timestamp: new Date().toISOString(),
      requestId: request.id
    }
  }
  return payload
})

6.2 分页响应包装 #

javascript
fastify.decorateReply('paginate', function (items, total, page, limit) {
  return this.send({
    success: true,
    data: items,
    meta: {
      pagination: {
        page: parseInt(page),
        limit: parseInt(limit),
        total,
        totalPages: Math.ceil(total / limit)
      },
      timestamp: new Date().toISOString()
    }
  })
})

fastify.get('/users', async (request, reply) => {
  const { page = 1, limit = 10 } = request.query
  const users = await getUsers(page, limit)
  const total = await countUsers()
  
  reply.paginate(users, total, page, limit)
})

6.3 错误响应包装 #

javascript
fastify.setErrorHandler((error, request, reply) => {
  const statusCode = error.statusCode || 500
  
  reply.code(statusCode).send({
    success: false,
    error: {
      code: statusCode,
      message: error.message
    },
    timestamp: new Date().toISOString(),
    requestId: request.id
  })
})

七、性能优化 #

7.1 使用Schema #

始终使用Schema进行序列化以获得最佳性能:

javascript
fastify.get('/users/:id', {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          id: { type: 'integer' },
          name: { type: 'string' }
        }
      }
    }
  }
}, handler)

7.2 避免动态Schema #

javascript
const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'integer' },
    name: { type: 'string' }
  }
}

fastify.get('/users/:id', {
  schema: {
    response: {
      200: userSchema
    }
  }
}, handler)

7.3 使用共享Schema #

javascript
fastify.addSchema({
  $id: 'user',
  type: 'object',
  properties: {
    id: { type: 'integer' },
    name: { type: 'string' }
  }
})

fastify.get('/users/:id', {
  schema: {
    response: {
      200: { $ref: 'user#' }
    }
  }
}, handler)

fastify.get('/users', {
  schema: {
    response: {
      200: {
        type: 'array',
        items: { $ref: 'user#' }
      }
    }
  }
}, handler)

八、特殊类型序列化 #

8.1 Date类型 #

javascript
fastify.get('/date', {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          date: { type: 'string', format: 'date-time' }
        }
      }
    }
  }
}, async (request, reply) => {
  return { date: new Date() }
})

8.2 BigInt类型 #

javascript
fastify.get('/bigint', {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          value: { type: 'string' }
        }
      }
    }
  }
}, async (request, reply) => {
  return { value: BigInt(9007199254740991).toString() }
})

8.3 Buffer类型 #

javascript
fastify.get('/buffer', async (request, reply) => {
  const buffer = Buffer.from('Hello')
  reply.type('application/octet-stream')
  return buffer
})

8.4 Stream类型 #

javascript
const fs = require('fs')

fastify.get('/stream', async (request, reply) => {
  const stream = fs.createReadStream('./file.txt')
  reply.type('text/plain')
  return stream
})

九、调试序列化 #

9.1 查看序列化结果 #

javascript
fastify.addHook('preSerialization', async (request, reply, payload) => {
  console.log('Before serialization:', payload)
  return payload
})

fastify.addHook('onSend', async (request, reply, payload) => {
  console.log('After serialization:', payload)
  return payload
})

9.2 序列化错误处理 #

javascript
fastify.addHook('onError', async (request, reply, error) => {
  if (error.message.includes('serialization')) {
    fastify.log.error('Serialization error:', error)
  }
})

十、最佳实践 #

10.1 Schema组织 #

javascript
const schemas = {
  user: {
    $id: 'user',
    type: 'object',
    properties: {
      id: { type: 'integer' },
      name: { type: 'string' },
      email: { type: 'string' }
    }
  },
  userList: {
    $id: 'userList',
    type: 'array',
    items: { $ref: 'user#' }
  },
  error: {
    $id: 'error',
    type: 'object',
    properties: {
      error: { type: 'string' },
      message: { type: 'string' }
    }
  }
}

for (const [name, schema] of Object.entries(schemas)) {
  fastify.addSchema(schema)
}

10.2 响应格式统一 #

javascript
const responseSchema = {
  success: {
    type: 'object',
    properties: {
      success: { type: 'boolean', const: true },
      data: { type: 'object' },
      timestamp: { type: 'string', format: 'date-time' }
    }
  },
  error: {
    type: 'object',
    properties: {
      success: { type: 'boolean', const: false },
      error: { type: 'object' },
      timestamp: { type: 'string', format: 'date-time' }
    }
  }
}

10.3 版本控制 #

javascript
fastify.addSchema({
  $id: 'userV1',
  type: 'object',
  properties: {
    id: { type: 'integer' },
    name: { type: 'string' }
  }
})

fastify.addSchema({
  $id: 'userV2',
  type: 'object',
  properties: {
    id: { type: 'integer' },
    name: { type: 'string' },
    email: { type: 'string' }
  }
})

十一、总结 #

本章我们学习了:

  1. 序列化概述:fast-json-stringify的性能优势
  2. Schema序列化:基本序列化、数组、嵌套对象、多状态码
  3. 共享Schema:定义和使用共享Schema
  4. 自定义序列化器:路由级、全局、条件序列化
  5. 禁用序列化:路由级禁用、手动禁用
  6. 响应包装:统一格式、分页包装、错误包装
  7. 性能优化:使用Schema、避免动态Schema、共享Schema
  8. 特殊类型:Date、BigInt、Buffer、Stream
  9. 调试序列化:查看结果、错误处理
  10. 最佳实践:Schema组织、格式统一、版本控制

接下来让我们学习高级特性!

最后更新:2026-03-28