← Back to Blog

JSON in Microservices: Serialization, Schemas, and Contract Testing

April 26, 2026 3 min read By CodeTidy Team

The JSON Conundrum: How to Keep Your Microservices in Sync

We've all been there - stuck in a never-ending cycle of debugging, trying to figure out why our microservices aren't communicating with each other as expected. One of the most common culprits? JSON serialization and schema mismatches.

Table of Contents

  • The Importance of JSON Schemas in Microservices
  • Using JSON Schema for Contracts
  • Pact Testing: Ensuring API Contract Compliance
  • Schema Registries: The Key to Backward Compatibility
  • Event Schemas: A Special Case
  • Key Takeaways
  • FAQ

The Importance of JSON Schemas in Microservices

When building microservices, we often rely on JSON to exchange data between services. However, without a clear schema, we risk introducing errors and inconsistencies that can be difficult to track down. JSON schemas provide a way to define the structure of our JSON data, ensuring that all services are on the same page.

Let's take a simple example in Node.js:

// user.schema.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "User",
  "type": "object",
  "properties": {
    "id": {"type": "integer"},
    "name": {"type": "string"}
  },
  "required": ["id", "name"]
}

Using JSON Schema for Contracts

JSON schemas can also serve as API contracts, defining the expected input and output data for each service. By using a schema to define our API contracts, we can ensure that all services are compatible and reduce the risk of errors.

For example, let's say we have a users service that exposes an endpoint to retrieve a user by ID:

// users.service.js
const express = require('express');
const app = express();
const userSchema = require('./user.schema.json');

app.get('/users/:id', (req, res) => {
  const userId = req.params.id;
  const user = // retrieve user from database
  if (!userSchema.validate(user)) {
    res.status(400).send('Invalid user data');
  } else {
    res.json(user);
  }
});

Pact Testing: Ensuring API Contract Compliance

But how do we ensure that our services are actually complying with the API contracts we've defined? That's where Pact testing comes in. Pact is a testing framework that allows us to define the expected interactions between services and verify that they are meeting those expectations.

Here's an example of a Pact test in Ruby:

# users_pact_test.rb
require 'pact/consumer/rspec'

Pact.service_consumer 'Users Service' do
  has_pact_with 'Users API' do
    expects_method :get, '/users/:id'
      .with_query('id' => 1)
      .will_respond_with status: 200, body: { id: 1, name: 'John Doe' }
  end
end

Schema Registries: The Key to Backward Compatibility

As our services evolve, our schemas will inevitably change. But how do we ensure that these changes don't break our existing services? That's where schema registries come in. A schema registry is a centralized store of all our schemas, allowing us to manage different versions and ensure backward compatibility.

For example, let's say we have a schema registry that stores all our schemas:

// schema.registry.json
{
  "schemas": {
    "user": {
      "versions": [
        {
          "version": 1,
          "schema": // user.schema.json v1
        },
        {
          "version": 2,
          "schema": // user.schema.json v2
        }
      ]
    }
  }
}

Event Schemas: A Special Case

Event schemas are a special case, as they often involve complex data structures and relationships. When working with event schemas, it's essential to consider the specific requirements of our event-driven architecture.

For example, let's say we have an event schema for a user-created event:

// user-created.event.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "User Created",
  "type": "object",
  "properties": {
    "userId": {"type": "integer"},
    "eventName": {"type": "string"}
  },
  "required": ["userId", "eventName"]
}

Key Takeaways

  • Use JSON schemas to define the structure of your JSON data and ensure consistency across services.
  • Use JSON schemas as API contracts to define expected input and output data for each service.
  • Use Pact testing to verify that services are complying with API contracts.
  • Use schema registries to manage different versions of your schemas and ensure backward compatibility.
  • Consider the specific requirements of event-driven architectures when working with event schemas.

FAQ

Q: What is the difference between a JSON schema and an API contract?

A JSON schema defines the structure of your JSON data, while an API contract defines the expected input and output data for each service.

Q: How do I choose the right schema registry for my project?

When choosing a schema registry, consider factors such as scalability, ease of use, and integration with your existing toolchain.

Q: Can I use JSON schemas with other data formats, such as XML or Avro?

While JSON schemas are specifically designed for JSON data, similar concepts and tools exist for other data formats, such as XML Schema and Avro schema.

AI agent tools available. The CodeTidy MCP Server gives Claude, Cursor, and other AI agents access to 60+ developer tools. One command: npx @codetidy/mcp