Skip to content

Parser (Zod)

This utility provides data validation and parsing using Zod, a TypeScript-first schema declaration and validation library.

Key features

  • Define data schema as Zod schema, then parse, validate and extract only what you want
  • Built-in envelopes to unwrap and validate popular AWS event sources payloads
  • Extend and customize envelopes to fit your needs
  • Safe parsing option to avoid throwing errors and custom error handling
  • Available for Middy.js middleware and TypeScript method decorators

Getting started

1
npminstall@aws-lambda-powertools/parserzod@~3 

This utility supports Zod v3.x and above.

Define schema

You can define your schema using Zod:

schema.ts
 1 2 3 4 5 6 7 8 9101112131415161718
import{z}from'zod';constorderSchema=z.object({id:z.number().positive(),description:z.string(),items:z.array(z.object({id:z.number().positive(),quantity:z.number(),description:z.string(),})),optionalField:z.string().optional(),});typeOrder=z.infer<typeoforderSchema>;export{orderSchema,typeOrder};

This is a schema for Order object using Zod. You can create complex schemas by using nested objects, arrays, unions, and other types, see Zod documentation for more details.

Parse events

You can parse inbound events using parser decorator, Middy.js middleware, or manually using built-in envelopes and schemas. Both are also able to parse either an object or JSON string as an input.

Warning

The decorator and middleware will replace the event object with the parsed schema if successful. Be cautious when using multiple decorators that expect event to have a specific structure, the order of evaluation for decorators is from bottom to top.

 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728
import{Logger}from'@aws-lambda-powertools/logger';import{parser}from'@aws-lambda-powertools/parser/middleware';importmiddyfrom'@middy/core';import{z}from'zod';constlogger=newLogger();constorderSchema=z.object({id:z.number().positive(),description:z.string(),items:z.array(z.object({id:z.number().positive(),quantity:z.number(),description:z.string(),})),optionalField:z.string().optional(),});exportconsthandler=middy().use(parser({schema:orderSchema})).handler(async(event):Promise<void>=>{for(constitemofevent.items){// item is parsed as OrderItemlogger.info('Processing item',{item});}});
 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435
importtype{LambdaInterface}from'@aws-lambda-powertools/commons/types';import{Logger}from'@aws-lambda-powertools/logger';import{parser}from'@aws-lambda-powertools/parser';importtype{Context}from'aws-lambda';import{z}from'zod';constlogger=newLogger();constorderSchema=z.object({id:z.number().positive(),description:z.string(),items:z.array(z.object({id:z.number().positive(),quantity:z.number(),description:z.string(),})),optionalField:z.string().optional(),});typeOrder=z.infer<typeoforderSchema>;classLambdaimplementsLambdaInterface{@parser({schema:orderSchema})publicasynchandler(event:Order,_context:Context):Promise<void>{// event is now typed as Orderfor(constitemofevent.items){logger.info('Processing item',{item});}}}constmyFunction=newLambda();exportconsthandler=myFunction.handler.bind(myFunction);

Built-in schemas

Parser comes with the following built-in schemas:

Model nameDescription
AlbSchemaLambda Event Source payload for Amazon Application Load Balancer
APIGatewayProxyEventSchemaLambda Event Source payload for Amazon API Gateway
APIGatewayRequestAuthorizerEventSchemaLambda Event Source payload for Amazon API Gateway Request Authorizer
APIGatewayTokenAuthorizerEventSchemaLambda Event Source payload for Amazon API Gateway Token Authorizer
APIGatewayProxyEventV2SchemaLambda Event Source payload for Amazon API Gateway v2 payload
APIGatewayProxyWebsocketEventSchemaLambda Event Source payload for Amazon API Gateway WebSocket events
APIGatewayRequestAuthorizerEventV2SchemaLambda Event Source payload for Amazon API Gateway v2 Authorizer
CloudFormationCustomResourceCreateSchemaLambda Event Source payload for AWS CloudFormation CREATE operation
CloudFormationCustomResourceUpdateSchemaLambda Event Source payload for AWS CloudFormation UPDATE operation
CloudFormationCustomResourceDeleteSchemaLambda Event Source payload for AWS CloudFormation DELETE operation
CloudwatchLogsSchemaLambda Event Source payload for Amazon CloudWatch Logs
PreSignupTriggerSchemaLambda Event Source payload for Amazon Cognito Pre Sign-up trigger
PostConfirmationTriggerSchemaLambda Event Source payload for Amazon Cognito Post Confirmation trigger
PreTokenGenerationTriggerSchemaLambda Event Source payload for Amazon Cognito Pre Token Generation trigger
CustomMessageTriggerSchemaLambda Event Source payload for Amazon Cognito Custom Message trigger
MigrateUserTriggerSchemaLambda Event Source payload for Amazon Cognito User Migration trigger
CustomSMSTriggerSchemaLambda Event Source payload for Amazon Cognito Custom SMS trigger
CustomEmailTriggerSchemaLambda Event Source payload for Amazon Cognito Custom Email trigger
DefineAuthChallengeTriggerSchemaLambda Event Source payload for Amazon Cognito Define Auth Challenge trigger
CreateAuthChallengeTriggerSchemaLambda Event Source payload for Amazon Cognito Create Auth Challenge trigger
VerifyAuthChallengeResponseTriggerSchemaLambda Event Source payload for Amazon Cognito Verify Auth Challenge Response trigger
PreTokenGenerationTriggerSchemaV1Lambda Event Source payload for Amazon Cognito Pre Token Generation trigger v1
PreTokenGenerationTriggerSchemaV2AndV3Lambda Event Source payload for Amazon Cognito Pre Token Generation trigger v2 and v3
DynamoDBStreamSchemaLambda Event Source payload for Amazon DynamoDB Streams
EventBridgeSchemaLambda Event Source payload for Amazon EventBridge
KafkaMskEventSchemaLambda Event Source payload for AWS MSK payload
KafkaSelfManagedEventSchemaLambda Event Source payload for self managed Kafka payload
KinesisDataStreamSchemaLambda Event Source payload for Amazon Kinesis Data Streams
KinesisFirehoseSchemaLambda Event Source payload for Amazon Kinesis Firehose
KinesisDynamoDBStreamSchemaLambda Event Source payload for DynamodbStream record wrapped in Kinesis Data stream
KinesisFirehoseSqsSchemaLambda Event Source payload for SQS messages wrapped in Kinesis Firehose records
LambdaFunctionUrlSchemaLambda Event Source payload for Lambda Function URL payload
S3EventNotificationEventBridgeSchemaLambda Event Source payload for Amazon S3 Event Notification to EventBridge.
S3SchemaLambda Event Source payload for Amazon S3
S3ObjectLambdaEventLambda Event Source payload for Amazon S3 Object Lambda
S3SqsEventNotificationSchemaLambda Event Source payload for S3 event notifications wrapped in SQS event (S3->SQS)
SesSchemaLambda Event Source payload for Amazon Simple Email Service
SnsSchemaLambda Event Source payload for Amazon Simple Notification Service
SqsSchemaLambda Event Source payload for Amazon SQS
TransferFamilySchemaLambda Event Source payload for AWS Transfer Family events
VpcLatticeSchemaLambda Event Source payload for Amazon VPC Lattice
VpcLatticeV2SchemaLambda Event Source payload for Amazon VPC Lattice v2 payload

Extend built-in schemas

You can extend every built-in schema to include your own schema, and yet have all other known fields parsed along the way.

 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728293031323334353637383940
importtype{LambdaInterface}from'@aws-lambda-powertools/commons/types';import{Logger}from'@aws-lambda-powertools/logger';import{parser}from'@aws-lambda-powertools/parser';import{EventBridgeSchema}from'@aws-lambda-powertools/parser/schemas/eventbridge';importtype{Context}from'aws-lambda';import{z}from'zod';constlogger=newLogger();constorderSchema=z.object({id:z.number().positive(),description:z.string(),items:z.array(z.object({id:z.number().positive(),quantity:z.number(),description:z.string(),})),optionalField:z.string().optional(),});constorderEventSchema=EventBridgeSchema.extend({detail:orderSchema,// (1)!});typeOrderEvent=z.infer<typeoforderEventSchema>;classLambdaimplementsLambdaInterface{@parser({schema:orderEventSchema})// (2)!publicasynchandler(event:OrderEvent,_context:Context):Promise<void>{for(constitemofevent.detail.items){// process OrderItemlogger.info('Processing item',{item});// (3)!}}}constmyFunction=newLambda();exportconsthandler=myFunction.handler.bind(myFunction);
  1. Extend built-in EventBridgeSchema with your own detail schema
  2. Pass the extended schema to parser decorator or middy middleware
  3. event is validated including your custom schema and now available in your handler
 1 2 3 4 5 6 7 8 9101112131415161718192021
{"version":"0","id":"6a7e8feb-b491-4cf7-a9f1-bf3703467718","detail-type":"OrderPurchased","source":"OrderService","account":"111122223333","time":"2020-10-22T18:43:48Z","region":"us-west-1","resources":["some_additional"],"detail":{"id":10876546789,"description":"My order","items":[{"id":1015938732,"quantity":1,"description":"item xpto"}]}}

JSON stringified payloads

If you want to extend a schema and transform a JSON stringified payload to an object, you can use helper function JSONStringified:

 1 2 3 4 5 6 7 8 91011121314
import{JSONStringified}from'@aws-lambda-powertools/parser/helpers';import{AlbSchema}from'@aws-lambda-powertools/parser/schemas/alb';import{z}from'zod';constcustomSchema=z.object({name:z.string(),age:z.number(),});constextendedSchema=AlbSchema.extend({body:JSONStringified(customSchema),});typeExtendedAlbEvent=z.infer<typeofextendedSchema>;
  1. Extend built-in AlbSchema using JSONStringified function to transform your payload
 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728
{"requestContext":{"elb":{"targetGroupArn":"arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a"}},"httpMethod":"GET","path":"/lambda","queryStringParameters":{"query":"1234ABCD"},"headers":{"accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","accept-encoding":"gzip","accept-language":"en-US,en;q=0.9","connection":"keep-alive","host":"lambda-alb-123578498.us-east-2.elb.amazonaws.com","upgrade-insecure-requests":"1","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36","x-amzn-trace-id":"Root=1-5c536348-3d683b8b04734faae651f476","x-forwarded-for":"72.12.164.125","x-forwarded-port":"80","x-forwarded-proto":"http","x-imforwards":"20"},"body":"{\"name\":\"Walter\", \"age\": 50}","isBase64Encoded":false}
 1 2 3 4 5 6 7 8 910111213
import{JSONStringified}from'@aws-lambda-powertools/parser/helpers';import{APIGatewayProxyEventV2Schema}from'@aws-lambda-powertools/parser/schemas/api-gatewayv2';import{z}from'zod';constextendedSchema=APIGatewayProxyEventV2Schema.extend({// (1)!body:JSONStringified(z.object({name:z.string(),age:z.number(),})),});typeExtendedAPIGatewayEvent=z.infer<typeofextendedSchema>;
  1. This is compatible also with API Gateway REST API schemas
 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435363738394041
{"version":"2.0","routeKey":"POST /lambda","rawPath":"/lambda","rawQueryString":"","headers":{"accept":"*/*","accept-encoding":"gzip, deflate","authorization":"Bearer foo","content-length":"0","host":"lsw1ro4ipb.execute-api.eu-west-1.amazonaws.com","user-agent":"HTTPie/3.2.2","x-amzn-trace-id":"Root=1-66705bc7-2b4257df30cbee696ef2cf28","x-forwarded-for":"15.248.3.126","x-forwarded-port":"443","x-forwarded-proto":"https"},"requestContext":{"accountId":"123456789012","apiId":"lsw1ro4ipb","authorizer":{"lambda":null},"domainName":"lsw1ro4ipb.execute-api.eu-west-1.amazonaws.com","domainPrefix":"lsw1ro4ipb","http":{"method":"POST","path":"/lambda","protocol":"HTTP/1.1","sourceIp":"15.248.3.126","userAgent":"HTTPie/3.2.2"},"requestId":"ZhNHJhhLjoEEPiw=","routeKey":"POST /lambda","stage":"$default","time":"17/Jun/2024:15:52:39 +0000","timeEpoch":1718639559080},"body":"{\"name\":\"John\",\"age\":42}","isBase64Encoded":false}
 1 2 3 4 5 6 7 8 9101112131415161718192021
import{JSONStringified}from'@aws-lambda-powertools/parser/helpers';import{SqsRecordSchema,SqsSchema,}from'@aws-lambda-powertools/parser/schemas/sqs';import{z}from'zod';constcustomSchema=z.object({name:z.string(),age:z.number(),});constextendedSchema=SqsSchema.extend({Records:z.array(SqsRecordSchema.extend({body:JSONStringified(customSchema),// (1)!})),});typeExtendedSqsEvent=z.infer<typeofextendedSchema>;
 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728293031323334353637383940414243
{"Records":[{"messageId":"059f36b4-87a3-44ab-83d2-661975830a7d","receiptHandle":"AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a...","body":"{\"name\": \"John Doe\", \"age\": 30}","attributes":{"ApproximateReceiveCount":"1","SentTimestamp":"1545082649183","SenderId":"AIDAIENQZJOLO23YVJ4VO","ApproximateFirstReceiveTimestamp":"1545082649185"},"messageAttributes":{"testAttr":{"stringValue":"100","binaryValue":"base64Str","dataType":"Number"}},"md5OfBody":"e4e68fb7bd0e697a0ae8f1bb342846b3","eventSource":"aws:sqs","eventSourceARN":"arn:aws:sqs:us-east-2:123456789012:my-queue","awsRegion":"us-east-2"},{"messageId":"2e1424d4-f796-459a-8184-9c92662be6da","receiptHandle":"AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...","body":"{\"name\": \"foo\", \"age\": 10}","attributes":{"ApproximateReceiveCount":"1","SentTimestamp":"1545082650636","SenderId":"AIDAIENQZJOLO23YVJ4VO","ApproximateFirstReceiveTimestamp":"1545082650649","DeadLetterQueueSourceArn":"arn:aws:sqs:us-east-2:123456789012:my-queue-dead"},"messageAttributes":{},"md5OfBody":"e4e68fb7bd0e697a0ae8f1bb342846b3","eventSource":"aws:sqs","eventSourceARN":"arn:aws:sqs:us-east-2:123456789012:my-queue","awsRegion":"us-east-2"}]}

DynamoDB Stream event parsing

If you want to parse a DynamoDB stream event with unmarshalling, you can use the helper function DynamoDBMarshalled:

 1 2 3 4 5 6 7 8 9101112131415161718192021222324
import{DynamoDBMarshalled}from'@aws-lambda-powertools/parser/helpers/dynamodb';import{DynamoDBStreamChangeRecordBase,DynamoDBStreamRecord,DynamoDBStreamSchema,}from'@aws-lambda-powertools/parser/schemas/dynamodb';import{z}from'zod';constcustomSchema=z.object({id:z.string(),message:z.string(),});constextendedSchema=DynamoDBStreamSchema.extend({Records:z.array(DynamoDBStreamRecord.extend({dynamodb:DynamoDBStreamChangeRecordBase.extend({NewImage:DynamoDBMarshalled(customSchema).optional(),}),})),});typeExtendedDynamoDBStreamEvent=z.infer<typeofextendedSchema>;
 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
{"Records":[{"eventID":"1","eventVersion":"1.0","dynamodb":{"ApproximateCreationDateTime":1693997155.0,"Keys":{"Id":{"N":"101"}},"NewImage":{"Message":{"S":"New item!"},"Id":{"N":"101"}},"StreamViewType":"NEW_AND_OLD_IMAGES","SequenceNumber":"111","SizeBytes":26},"awsRegion":"us-west-2","eventName":"INSERT","eventSourceARN":"eventsource_arn","eventSource":"aws:dynamodb"},{"eventID":"2","eventVersion":"1.0","dynamodb":{"OldImage":{"Message":{"S":"New item!"},"Id":{"N":"101"}},"SequenceNumber":"222","Keys":{"Id":{"N":"101"}},"SizeBytes":59,"NewImage":{"Message":{"S":"This item has changed"},"Id":{"N":"101"}},"StreamViewType":"NEW_AND_OLD_IMAGES"},"awsRegion":"us-west-2","eventName":"MODIFY","eventSourceARN":"source_arn","eventSource":"aws:dynamodb"}]}

Envelopes

When trying to parse your payload you might encounter the following situations:

  • Your actual payload is wrapped around a known structure, for example Lambda Event Sources like EventBridge
  • You're only interested in a portion of the payload, for example parsing the detail of custom events in EventBridge, or body of SQS records
  • You can either solve these situations by creating a schema of these known structures, parsing them, then extracting and parsing a key where your payload is.

This can become difficult quite quickly. Parser simplifies the development through a feature named Envelope. Envelopes can be used via envelope parameter available in middy and decorator. Here's an example of parsing a custom schema in an event coming from EventBridge, where all you want is what's inside the detail key.

 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829
import{Logger}from'@aws-lambda-powertools/logger';import{EventBridgeEnvelope}from'@aws-lambda-powertools/parser/envelopes/eventbridge';import{parser}from'@aws-lambda-powertools/parser/middleware';importmiddyfrom'@middy/core';import{z}from'zod';constlogger=newLogger();constorderSchema=z.object({id:z.number().positive(),description:z.string(),items:z.array(z.object({id:z.number().positive(),quantity:z.number(),description:z.string(),})),optionalField:z.string().optional(),});exportconsthandler=middy().use(parser({schema:orderSchema,envelope:EventBridgeEnvelope})).handler(async(event):Promise<void>=>{for(constitemofevent.items){// item is parsed as OrderItemlogger.info('Processing item',{item});}});
 1 2 3 4 5 6 7 8 9101112131415161718192021222324252627282930313233343536
importtype{LambdaInterface}from'@aws-lambda-powertools/commons/types';import{Logger}from'@aws-lambda-powertools/logger';import{parser}from'@aws-lambda-powertools/parser';import{EventBridgeEnvelope}from'@aws-lambda-powertools/parser/envelopes/eventbridge';importtype{Context}from'aws-lambda';import{z}from'zod';constlogger=newLogger();constorderSchema=z.object({id:z.number().positive(),description:z.string(),items:z.array(z.object({id:z.number().positive(),quantity:z.number(),description:z.string(),})),optionalField:z.string().optional(),});typeOrder=z.infer<typeoforderSchema>;classLambdaimplementsLambdaInterface{@parser({schema:orderSchema,envelope:EventBridgeEnvelope})// (1)!publicasynchandler(event:Order,_context:Context):Promise<void>{// event is now typed as Orderfor(constitemofevent.items){logger.info('Processing item',item);// (2)!}}}constmyFunction=newLambda();exportconsthandler=myFunction.handler.bind(myFunction);
  1. Pass eventBridgeEnvelope to parser decorator
  2. event is parsed and replaced as Order object

The envelopes are functions that take an event and the schema to parse, and return the result of the inner schema. Depending on the envelope it can be something simple like extracting a key. We have also complex envelopes that parse the payload from a string, decode base64, uncompress gzip, etc.

Envelopes vs schema extension

Use envelopes if you want to extract only the inner part of an event payload and don't use the information from the Lambda event. Otherwise, extend built-in schema to parse the whole payload and use the metadata from the Lambda event.

Built-in envelopes

Parser comes with the following built-in envelopes:

Envelope nameBehaviour
apiGatewayEnvelope1. Parses data using APIGatewayProxyEventSchema.
2. Parses body key using your schema and returns it.
apiGatewayV2Envelope1. Parses data using APIGatewayProxyEventV2Schema.
2. Parses body key using your schema and returns it.
cloudWatchEnvelope1. Parses data using CloudwatchLogsSchema which will base64 decode and decompress it.
2. Parses records in message key using your schema and return them in a list.
dynamoDBStreamEnvelope1. Parses data using DynamoDBStreamSchema.
2. Parses records in NewImage and OldImage keys using your schema.
3. Returns a list with a dictionary containing NewImage and OldImage keys
eventBridgeEnvelope1. Parses data using EventBridgeSchema.
2. Parses detail key using your schema and returns it.
kafkaEnvelope1. Parses data using KafkaRecordSchema.
2. Parses value key using your schema and returns it.
kinesisEnvelope1. Parses data using KinesisDataStreamSchema which will base64 decode it.
2. Parses records in Records key using your schema and returns them in a list.
kinesisFirehoseEnvelope1. Parses data using KinesisFirehoseSchema which will base64 decode it.
2. Parses records in Records key using your schema and returns them in a list.
lambdaFunctionUrlEnvelope1. Parses data using LambdaFunctionUrlSchema.
2. Parses body key using your schema and returns it.
snsEnvelope1. Parses data using SnsSchema.
2. Parses records in body key using your schema and return them in a list.
snsSqsEnvelope1. Parses data using SqsSchema.
2. Parses SNS records in body key using SnsNotificationSchema.
3. Parses data in Message key using your schema and return them in a list.
sqsEnvelope1. Parses data using SqsSchema.
2. Parses records in body key using your schema and return them in a list.
vpcLatticeEnvelope1. Parses data using VpcLatticeSchema.
2. Parses value key using your schema and returns it.
vpcLatticeV2Envelope1. Parses data using VpcLatticeSchema.
2. Parses value key using your schema and returns it.

Safe parsing

If you want to parse the event without throwing an error, use the safeParse option. The handler event object will be replaced with ParsedResult<Input?, Oputput?>, for example ParsedResult<SqsEvent, Order>, where SqsEvent is the original event and Order is the parsed schema.

The ParsedResult object will have success, data, or error and originalEvent fields, depending on the outcome. If the parsing is successful, the data field will contain the parsed event, otherwise you can access the error field and the originalEvent to handle the error and recover the original event.

 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728293031323334
import{Logger}from'@aws-lambda-powertools/logger';import{parser}from'@aws-lambda-powertools/parser/middleware';importmiddyfrom'@middy/core';import{z}from'zod';constlogger=newLogger();constorderSchema=z.object({id:z.number().positive(),description:z.string(),items:z.array(z.object({id:z.number().positive(),quantity:z.number(),description:z.string(),})),optionalField:z.string().optional(),});exportconsthandler=middy().use(parser({schema:orderSchema,safeParse:true})// (1)!).handler(async(event):Promise<void>=>{if(event.success){for(constitemofevent.data.items){logger.info('Processing item',{item});// (2)!}}else{logger.error('Error parsing event',{event:event.error});// (3)!logger.error('Original event',{event:event.originalEvent});// (4)!}});
  1. Use safeParse option to parse the event without throwing an error
  2. Use data to access the parsed event when successful
  3. Use error to handle the error message
  4. Use originalEvent to get the original event and recover
 1 2 3 4 5 6 7 8 9101112131415161718192021222324252627282930313233343536373839404142434445464748495051
importtype{LambdaInterface}from'@aws-lambda-powertools/commons/types';import{Logger}from'@aws-lambda-powertools/logger';import{parser}from'@aws-lambda-powertools/parser';import{EventBridgeEnvelope}from'@aws-lambda-powertools/parser/envelopes/eventbridge';importtype{EventBridgeEvent,ParsedResult,}from'@aws-lambda-powertools/parser/types';importtype{Context}from'aws-lambda';import{z}from'zod';constlogger=newLogger();constorderSchema=z.object({id:z.number().positive(),description:z.string(),items:z.array(z.object({id:z.number().positive(),quantity:z.number(),description:z.string(),})),optionalField:z.string().optional(),});typeOrder=z.infer<typeoforderSchema>;classLambdaimplementsLambdaInterface{@parser({schema:orderSchema,envelope:EventBridgeEnvelope,safeParse:true,// (1)!})publicasynchandler(event:ParsedResult<EventBridgeEvent,Order>,_context:Context):Promise<void>{if(event.success){for(constitemofevent.data.items){logger.info('Processing item',{item});// (2)!}}else{logger.error('Failed to parse event',{error:event.error});// (3)!logger.error('Original event is ',{original:event.originalEvent});// (4)!}}}constmyFunction=newLambda();exportconsthandler=myFunction.handler.bind(myFunction);
  1. Use safeParse option to parse the event without throwing an error
  2. Use data to access the parsed event when successful
  3. Use error to handle the error message
  4. Use originalEvent to get the original event and recover

Manual parsing

You can use built-in envelopes and schemas to parse the incoming events manually, without using middy or decorator.

 1 2 3 4 5 6 7 8 9101112131415161718192021222324252627282930313233
import{Logger}from'@aws-lambda-powertools/logger';import{EventBridgeEnvelope}from'@aws-lambda-powertools/parser/envelopes/eventbridge';import{EventBridgeSchema}from'@aws-lambda-powertools/parser/schemas/eventbridge';importtype{EventBridgeEvent}from'@aws-lambda-powertools/parser/types';importtype{Context}from'aws-lambda';import{z}from'zod';constlogger=newLogger();constorderSchema=z.object({id:z.number().positive(),description:z.string(),items:z.array(z.object({id:z.number().positive(),quantity:z.number(),description:z.string(),})),optionalField:z.string().optional(),});typeOrder=z.infer<typeoforderSchema>;exportconsthandler=async(event:EventBridgeEvent,_context:Context):Promise<void>=>{constparsedEvent=EventBridgeSchema.parse(event);// (1)!logger.info('Parsed event',parsedEvent);constorders:Order=EventBridgeEnvelope.parse(event,orderSchema);// (2)!logger.info('Parsed orders',orders);};
  1. Use EventBridgeSchema to parse the event, the details fields will be parsed as a generic record.
  2. Use eventBridgeEnvelope with a combination of orderSchema to get Order object from the details field.
 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435
import{Logger}from'@aws-lambda-powertools/logger';import{EventBridgeEnvelope}from'@aws-lambda-powertools/parser/envelopes/eventbridge';import{EventBridgeSchema}from'@aws-lambda-powertools/parser/schemas/eventbridge';importtype{EventBridgeEvent}from'@aws-lambda-powertools/parser/types';importtype{Context}from'aws-lambda';import{z}from'zod';constlogger=newLogger();constorderSchema=z.object({id:z.number().positive(),description:z.string(),items:z.array(z.object({id:z.number().positive(),quantity:z.number(),description:z.string(),})),optionalField:z.string().optional(),});exportconsthandler=async(event:EventBridgeEvent,_context:Context):Promise<void>=>{constparsedEvent=EventBridgeSchema.safeParse(event);// (1)!parsedEvent.success?logger.info('Event parsed successfully',parsedEvent.data):logger.error('Event parsing failed',parsedEvent.error);constparsedEvenlope=EventBridgeEnvelope.safeParse(event,orderSchema);// (2)!parsedEvenlope.success?logger.info('Event envelope parsed successfully'):logger.error('Event envelope parsing failed',parsedEvenlope.error);};
  1. Use safeParse option to parse the event without throwing an error
  2. safeParse is also available for envelopes

Custom validation

Because Parser uses Zod, you can use all the features of Zod to validate your data. For example, you can use refine to validate a field or a combination of fields:

 1 2 3 4 5 6 7 8 9101112131415161718192021
import{z}from'zod';constorderItemSchema=z.object({id:z.number().positive(),quantity:z.number(),description:z.string(),});exportconstorderSchema=z.object({id:z.number().positive(),description:z.string(),items:z.array(orderItemSchema).refine((items)=>items.length>0,{message:'Order must have at least one item',// (1)!}),optionalField:z.string().optional(),}).refine((order)=>order.id>100&&order.items.length>100,{message:'All orders with more than 100 items must have an id greater than 100',// (2)!});
  1. validate a single field
  2. validate an object with multiple fields

Zod provides a lot of other features and customization, see Zod documentation for more details.

Types

Schema and Type inference

Use z.infer to extract the type of the schema, so you can use types during development and avoid type errors.

 1 2 3 4 5 6 7 8 9101112131415161718192021222324252627282930313233343536
import{Logger}from'@aws-lambda-powertools/logger';import{parser}from'@aws-lambda-powertools/parser/middleware';importmiddyfrom'@middy/core';importtype{Context}from'aws-lambda';import{z}from'zod';constlogger=newLogger();constorderSchema=z.object({id:z.number().positive(),description:z.string(),items:z.array(z.object({id:z.number().positive(),quantity:z.number(),description:z.string(),})),optionalField:z.string().optional(),});typeOrder=z.infer<typeoforderSchema>;// (1)!constlambdaHandler=async(event:Order,// (2)!_context:Context):Promise<void>=>{for(constitemofevent.items){// item is parsed as OrderItemlogger.info('Processing item',{item});// (3)!}};exportconsthandler=middy(lambdaHandler).use(parser({schema:orderSchema}));
  1. Use z.infer to extract the type of the schema, also works for nested schemas
  2. event is of type Order
  3. infer types from deeply nested schemas

Compatibility with @types/aws-lambda

The package @types/aws-lambda is a popular project that contains type definitions for many AWS service event invocations. Powertools parser utility also bring AWS Lambda event types based on the built-in schema definitions.

We recommend to use the types provided by the parser utility. If you encounter any issues or have any feedback, please submit an issue.

Testing your code

When testing your handler with parser decorator you need to use double assertion to bypass TypeScript type checking in your tests. This is useful when you want to test the handler for invalid payloads or when you want to test the error handling. If you are you use middy middleware, you don't need to do this.

 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132
importtype{Context}from'aws-lambda';import{describe,expect,it}from'vitest';import{handler}from'./decorator.js';importtype{Order}from'./schema.js';describe('Test handler',()=>{it('should parse event successfully',async()=>{consttestEvent={id:123,description:'test',items:[{id:1,quantity:1,description:'item1',},],};awaitexpect(handler(testEvent,{}asContext)).resolves.toEqual(123);});it('should throw error if event is invalid',async()=>{consttestEvent={foo:'bar'};awaitexpect(handler(testEventasunknownasOrder,// (1)!{}asContext)).rejects.toThrow();});});
  1. Use double assertion as unknown as X to bypass TypeScript type checking in your tests
 1 2 3 4 5 6 7 8 91011121314151617181920
importtype{LambdaInterface}from'@aws-lambda-powertools/commons/types';import{Logger}from'@aws-lambda-powertools/logger';import{parser}from'@aws-lambda-powertools/parser';importtype{Context}from'aws-lambda';import{typeOrder,orderSchema}from'./schema.js';constlogger=newLogger();classLambdaimplementsLambdaInterface{@parser({schema:orderSchema})publicasynchandler(event:Order,_context:Context):Promise<number>{logger.info('Processing event',{event});// ... business logicreturnevent.id;}}constmyFunction=newLambda();exportconsthandler=myFunction.handler.bind(myFunction);
 1 2 3 4 5 6 7 8 9101112131415161718
import{z}from'zod';constorderSchema=z.object({id:z.number().positive(),description:z.string(),items:z.array(z.object({id:z.number().positive(),quantity:z.number(),description:z.string(),})),optionalField:z.string().optional(),});typeOrder=z.infer<typeoforderSchema>;export{orderSchema,typeOrder};

This also works when using safeParse option.

 1 2 3 4 5 6 7 8 9101112131415161718192021222324252627282930313233343536373839404142434445464748495051
importtype{EventBridgeEvent,ParsedResult,}from'@aws-lambda-powertools/parser/types';importtype{Context}from'aws-lambda';import{describe,expect,it}from'vitest';import{handler}from'./safeParseDecorator.js';importtype{Order}from'./schema.js';describe('Test handler',()=>{it('should parse event successfully',async()=>{consttestEvent={version:'0',id:'6a7e8feb-b491-4cf7-a9f1-bf3703467718','detail-type':'OrderPurchased',source:'OrderService',account:'111122223333',time:'2020-10-22T18:43:48Z',region:'us-west-1',resources:['some_additional'],detail:{id:10876546789,description:'My order',items:[{id:1015938732,quantity:1,description:'item xpto',},],},};awaitexpect(handler(testEventasunknownasParsedResult<EventBridgeEvent,Order>,// (1)!{}asContext)).resolves.toEqual(10876546789);});it('should throw error if event is invalid',async()=>{consttestEvent={foo:'bar'};awaitexpect(handler(testEventasunknownasParsedResult<EventBridgeEvent,Order>,{}asContext)).rejects.toThrow();});});
  1. Use double assertion to pass expected types to the handler
 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435
importtype{LambdaInterface}from'@aws-lambda-powertools/commons/types';import{Logger}from'@aws-lambda-powertools/logger';import{parser}from'@aws-lambda-powertools/parser';import{EventBridgeEnvelope}from'@aws-lambda-powertools/parser/envelopes/eventbridge';importtype{EventBridgeEvent,ParsedResult,}from'@aws-lambda-powertools/parser/types';importtype{Context}from'aws-lambda';import{typeOrder,orderSchema}from'./schema.js';constlogger=newLogger();classLambdaimplementsLambdaInterface{@parser({schema:orderSchema,envelope:EventBridgeEnvelope,safeParse:true,})publicasynchandler(event:ParsedResult<EventBridgeEvent,Order>,_context:Context):Promise<number>{logger.info('Processing event',{event});if(event.success){// ... business logicreturnevent.data.id;}logger.error('Failed to parse event',{event});thrownewError('Failed to parse event');}}constmyFunction=newLambda();exportconsthandler=myFunction.handler.bind(myFunction);
 1 2 3 4 5 6 7 8 9101112131415161718
import{z}from'zod';constorderSchema=z.object({id:z.number().positive(),description:z.string(),items:z.array(z.object({id:z.number().positive(),quantity:z.number(),description:z.string(),})),optionalField:z.string().optional(),});typeOrder=z.infer<typeoforderSchema>;export{orderSchema,typeOrder};
close