
In this one I thought I'd talk a little bit about a serverless JSON transformation technique I've found incredibly useful in recent years, especially when time is of the essence. Serverless computing has revolutionized how developers deploy code to the cloud, offering on-demand execution without the need to manage infrastructure. Two leading platforms in this space are AWS Lambda and Azure Functions. In this article, I'll implement the exact same functionality—a JSON transformer service—on both platforms, comparing their approaches, development experiences, and key differences.
The example function will accept a JSON payload via HTTP POST, transform it to a different schema, add derived data and return the transformed JSON.
The Sample Function
For our comparison, we'll build a function that transforms product data. Our input will be a simple e-commerce product JSON, and we'll convert it to a warehouse-friendly format with additional tax and shipping calculations.
Input JSON Instance
{
"productId": "ABC123",
"name": "Wireless Headphones",
"price": 79.99,
"category": "Electronics",
"weight": 0.5,
"dimensions": {
"length": 7.5,
"width": 6.0,
"height": 3.2
}
}
Output JSON Instance
This is what we should get back from the function!
{
"sku": "ABC123",
"productName": "Wireless Headphones",
"pricing": {
"basePrice": 79.99,
"taxAmount": 6.40,
"totalPrice": 86.39
},
"shipping": {
"weightKg": 0.5,
"volumeCm3": 144,
"shippingClass": "standard"
},
"category": "Electronics",
"processed": "2023-09-15T14:30:00Z"
}
Implementation in AWS Lambda
Setting Up the Lambda Function
- Create a new Lambda function:
- Log into the AWS Management Console
- Navigate to the Lambda service
- Click "Create function"
- Choose Author from Scratch
- Name your function (e.g. ProductTransformer)
- Choose Python 3.9 as the runtime
- Click "Create Function"
- Define the Function Code:
import json import datetime import math def lambda_handler(event, context): try: # Parse incoming JSON from the request body input_product = json.loads(event['body']) # Calculate derived values tax_rate = 0.08 tax_amount = round(input_product['price'] * tax_rate, 2) total_price = round(input_product['price'] + tax_amount, 2) # Calculate volume for shipping length = input_product['dimensions']['length'] width = input_product['dimensions']['width'] height = input_product['dimensions']['height'] volume_cm3 = round(length * width * height) # Determine shipping class based on weight and volume shipping_class = "standard" if input_product['weight'] > 2 or volume_cm3 > 5000: shipping_class = "heavy" elif input_product['weight'] < 0.2 and volume_cm3 < 100: shipping_class = "light" # Construct transformed output transformed_product = { "sku": input_product['productId'], "productName": input_product['name'], "pricing": { "basePrice": input_product['price'], "taxAmount": tax_amount, "totalPrice": total_price }, "shipping": { "weightKg": input_product['weight'], "volumeCm3": volume_cm3, "shippingClass": shipping_class }, "category": input_product['category'], "processed": datetime.datetime.now().isoformat() } # Return successful response return { "statusCode": 200, "headers": { "Content-Type": "application/json" }, "body": json.dumps(transformed_product) } except Exception as e: # Return error response return { "statusCode": 400, "headers": { "Content-Type": "application/json" }, "body": json.dumps({"error": "Invalid input JSON", "details": str(e)}) }
- Configure API Gateway:
- Create a new API Gateway trigger
- Set up a REST API with a POST method
- Configure the integration to pass the entire request through
- Deploy the API to a stage (e.g. prod)
Implementation in Azure Functions
Setting up the Azure Function
- Create a new Function App:
- Log into the Azure Portal
- Navigate to "Function App"
- Click "Add" to create a new Function App
- Select your subscription and resource group
- Name your Function App (e.g., "ProductTransformerApp")
- Choose Python as the runtime stack (Python 3.9)
- Select your region and hosting plan (Consumption plan for serverless)
- Click "Review + create" followed by "Create"
- Create an HTTP Trigger Function:
- In your Function App, click "Functions" then "Add"
- Choose "HTTP trigger" template
- Name your function (e.g., "ProductTransformer")
- Set Authorization level to "Function" or "Anonymous" (for testing)
- Click "Create"
- Define the function code (in `__init__.py`):
import json import logging import datetime import azure.functions as func def main(req: func.HttpRequest) -> func.HttpResponse: logging.info('Python HTTP trigger function processed a request.') try: # Get input product from request body req_body = req.get_json() input_product = req_body # Calculate derived values tax_rate = 0.08 tax_amount = round(input_product['price'] * tax_rate, 2) total_price = round(input_product['price'] + tax_amount, 2) # Calculate volume for shipping length = input_product['dimensions']['length'] width = input_product['dimensions']['width'] height = input_product['dimensions']['height'] volume_cm3 = round(length * width * height) # Determine shipping class based on weight and volume shipping_class = "standard" if input_product['weight'] > 2 or volume_cm3 > 5000: shipping_class = "heavy" elif input_product['weight'] < 0.2 and volume_cm3 < 100: shipping_class = "light" # Construct transformed output transformed_product = { "sku": input_product['productId'], "productName": input_product['name'], "pricing": { "basePrice": input_product['price'], "taxAmount": tax_amount, "totalPrice": total_price }, "shipping": { "weightKg": input_product['weight'], "volumeCm3": volume_cm3, "shippingClass": shipping_class }, "category": input_product['category'], "processed": datetime.datetime.now().isoformat() } # Return successful response return func.HttpResponse( body=json.dumps(transformed_product), mimetype="application/json", status_code=200 ) except Exception as e: # Return error response error_response = {"error": "Invalid input JSON", "details": str(e)} return func.HttpResponse( body=json.dumps(error_response), mimetype="application/json", status_code=400 )
- Configure function.json:
{ "scriptFile": "__init__.py", "bindings": [ { "authLevel": "anonymous", "type": "httpTrigger", "direction": "in", "name": "req", "methods": [ "post" ] }, { "type": "http", "direction": "out", "name": "$return" } ] }
Key Differences and Comparison
Development Experience
When developing serverless applications, AWS Lambda and Azure Functions offer distinctly different developer experiences. AWS Lambda takes a minimalist approach centered around a single handler function that processes events and a context object. This simplicity makes Lambda functions easy to understand, though you'll need tools like AWS SAM or a serverless plugin for local testing. For deployment, AWS offers several options ranging from direct uploads through the console to infrastructure-as-code approaches using CloudFormation or the Serverless Framework. Lambda handles virtual environments behind the scenes, requiring only a requirements.txt file to specify dependencies. Python functions in Lambda experience reasonably fast cold starts, typically initializing within hundreds of milliseconds.
In contrast, Azure Functions employs a more structured approach with a function app containing multiple functions, each with its own dedicated configuration files. Microsoft provides comprehensive local development support through the Azure Functions Core Tools, which offers a more intuitive local testing experience specifically optimized for Python. Deployment options are similarly diverse, with particularly strong integration with Visual Studio Code through dedicated extensions, as well as support for Azure CLI, GitHub Actions, and traditional ZIP deployments. Like Lambda, Azure Functions uses requirements.txt for dependency management. Cold start performance for Python functions is generally comparable between the two platforms, with both services optimizing their Python runtimes for quick initialization.
HTTP Integration and Request Handling
The approaches to HTTP integration reveal perhaps the starkest contrast between the two platforms. AWS Lambda requires explicit integration with API Gateway to handle HTTP requests. This separation of concerns provides flexibility but necessitates additional configuration. Within your Lambda function, you'll need to manually structure response objects with explicit statusCode, headers, and body properties. Similarly, parsing incoming JSON requires manual handling with json.loads(), and you'll work directly with the raw event structure containing the HTTP request data.
Azure Functions simplifies HTTP integration considerably through built-in HTTP bindings. The platform provides purpose-built func.HttpRequest and func.HttpResponse objects that abstract away much of the boilerplate code. Instead of manually parsing JSON, you can use convenient methods like req.get_json() to extract and parse the request body. Creating responses is equally streamlined with the func.HttpResponse constructor, which handles content type and status code settings elegantly. This higher-level abstraction allows developers to focus more on business logic and less on HTTP protocol details.
Python-Specific Features
Both platforms offer robust Python support, though with some noteworthy differences. AWS Lambda provides slightly broader Python version compatibility, supporting versions 3.7 through 3.11. Lambda's layers feature allows for sharing dependencies across multiple functions, reducing duplication and deployment size. When deploying, Lambda includes the virtual environment in the deployment package, and developers typically install dependencies to a local directory that's bundled with the function code.
Azure Functions supports Python versions 3.7 through 3.10, offering nearly equivalent language support. Where Azure particularly shines is in its native support for asynchronous Python functions, aligning well with Python's async/await syntax for non-blocking operations. Both platforms utilize requirements.txt for dependency management, but Azure Functions integrates particularly well with Python's standard logging module, making it straightforward to implement comprehensive logging throughout your application.
Monitoring and Observability
For monitoring and troubleshooting, each platform leverages its respective cloud ecosystem. AWS Lambda integrates with CloudWatch Logs for log aggregation and analysis, with straightforward integration for Python's logging module. For distributed tracing, AWS X-Ray provides insights into request flow across multiple AWS services, helping identify bottlenecks and errors in complex serverless architectures.
Azure Functions offers comprehensive monitoring through Application Insights, which provides more out-of-the-box analytics capabilities without additional configuration. The platform's native integration with Python's logging module makes instrumentation simple and familiar for Python developers. The Azure Portal also provides visual performance monitoring tools that offer intuitive insights into function execution, memory usage, and error rates, all accessible through an interactive dashboard experience.
Local Development Experience
Lambda:
# Install AWS SAM CLI
pip install aws-sam-cli
# Create a template.yaml for local testing
# Create a requirements.txt file for dependencies
# Run locally
sam local start-api
Azure Functions:
# Install Azure Functions Core Tools
pip install azure-functions-core-tools
# Create a new project
func init ProductTransformerProject --python
cd ProductTransformerProject
# Create a new function
func new --name ProductTransformer --template "HTTP trigger"
# Create virtual environment and install dependencies
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -r requirements.txt
# Run locally
func start
Conclusion
Both AWS Lambda and Azure Functions provide robust platforms for implementing serverless JSON transformation services in Python. The core transformation logic remains almost identical between the two platforms, but the surrounding infrastructure, configuration patterns, and integration methods differ.
AWS Lambda offers a simpler, more function-centric approach with its single-file handler model, making it easy to understand and deploy basic functions. It integrates seamlessly with the broader AWS ecosystem but requires more configuration for HTTP endpoints via API Gateway.
Azure Functions provides a more structured application model with better built-in HTTP request/response handling. Its Python support is mature, with excellent local development tools and integrated logging, benefiting teams already working with Microsoft technologies.
For our JSON transformer example, the Python implementation is clean and efficient on both platforms. Your choice should ultimately depend on your existing cloud provider relationships, team expertise, and specific requirements around integration with other services.
The serverless approach on either platform dramatically simplifies infrastructure management, allowing you to focus on building your transformation logic rather than managing servers—exactly what serverless computing promises. My experience over the last few years has shown me that serverless is indeed one of those rare things in life that deliver exactly what they promise - I am a serious convert.