Data Formats and Serialization (JSON-focused): Rules, Date/Number/Null Handling, and Standardizing API Responses



Data Formats and Serialization (JSON-focused): Rules, Date/Number/Null Handling, and Standardizing API Responses

JSON is the default data format for most modern APIs, but “it works” is not the same as “it scales.” Inconsistent field names, ambiguous date formats, floating-point surprises, and unclear null behavior create bugs that are hard to trace—especially as multiple clients (web, mobile, partners) integrate with your API.

This post explains the core JSON rules, how to treat dates, numbers, and null values safely, and how to enforce consistent request/response schemas. At the end, you will standardize your API responses into a predictable JSON shape you can reuse across endpoints.


Table of Contents


Key Points (Quick Rules)

  • Always send JSON with Content-Type: application/json and a consistent top-level response structure.
  • Use a single naming convention (commonly camelCase) for fields across all requests and responses.
  • Represent dates as ISO 8601 strings (e.g., 2026-01-02T09:30:00Z), not localized formats.
  • Be careful with JSON numbers: avoid floating-point for money; consider minor units (cents) or strings for high precision.
  • Define rules for null vs missing fields; document them and keep them consistent.
  • Standardize errors: include an error code, human message, and optional field-level details.


What “Serialization” Means in Practice

Serialization is converting in-memory data structures (objects, arrays, types) into a format you can send over the network (a JSON string). Deserialization converts that JSON back into objects on the client or server.

Most bugs happen at boundaries:

  • Different languages represent types differently (e.g., JavaScript has one Number, but databases may have DECIMAL, BIGINT).
  • Dates are not a native JSON type; you must choose a representation.
  • null is valid JSON, but teams disagree on what it “means” unless you define it.

The goal is to choose conventions that minimize ambiguity and remain stable as your API grows.


JSON Rules You Must Know

JSON has a small set of types: object, array, string, number, boolean, and null. That simplicity is powerful, but it means some common concepts are not built-in (dates, decimals, binary data). The core rules:

  • Object keys are strings and must be quoted (e.g., {"id": 1}).
  • Strings are Unicode; escape quotes and control characters properly.
  • Numbers have no explicit integer/float distinction in JSON text; the consuming language decides how to parse it.
  • Do not rely on key ordering inside objects; clients should treat objects as unordered maps.

A good API treats JSON as a contract, not an implementation detail. That means defining a schema and sticking to it.


Dates and Times in JSON

Because JSON has no date type, you must pick a representation. The safest and most interoperable option is an ISO 8601 string in UTC, usually with Z (Zulu time):

  • Recommended: "createdAt": "2026-01-02T09:30:00Z"
  • Avoid: "01/02/2026 18:30" (ambiguous across locales)

If you need a date without time (e.g., a birthday), use YYYY-MM-DD:

  • "birthDate": "1995-10-31"

If your system needs time zones (for scheduling, local business hours), store timestamps in UTC and send a separate time zone field when relevant:

  • "startsAt": "2026-01-02T09:30:00Z"
  • "timeZone": "Asia/Seoul"


Numbers: Precision, Currency, IDs

JSON numbers look simple, but they can become tricky in real systems. Many clients (especially JavaScript) parse numbers into IEEE 754 floating-point, which can introduce precision errors:

  • Example issue: 0.1 + 0.2 may not equal exactly 0.3 in floating-point systems.

Currency

For money, avoid floats. Two common safe approaches:

  • Minor units (recommended for many APIs): store as integers (cents).
    "amount": 1299, "currency": "USD" means $12.99
  • String decimal: preserve precision as a string.
    "amount": "12.99", "currency": "USD"

Large IDs

Very large integers (e.g., database IDs) can exceed safe integer ranges in some clients. To avoid client-side rounding:

  • Use string IDs: "id": "9223372036854775807"
  • Or use prefixed IDs: "id": "ord_12345"

Rules of Thumb

  • Use JSON numbers for counts, sizes, and values that fit safely in common clients.
  • Use strings for high-precision decimals (rates) or extremely large integers (IDs) if clients might lose precision.


Null vs Missing vs Empty

Many schema inconsistencies come from unclear handling of null. Define what each state means:

  • Missing field: “not provided” or “not applicable in this response.”
  • null: “explicitly unknown” or “intentionally cleared.”
  • Empty string (""): “known to be blank.”
  • Empty array ([]): “known to have zero items.”

A practical beginner-friendly policy:

  • Use missing fields for optional data not included in the response shape.
  • Use null only when you need to explicitly say “no value,” or when the client can send null to clear a value (document this).
  • Prefer empty arrays over null arrays for list fields (e.g., "items": [] instead of "items": null).


Keeping Request/Response Schemas Consistent

Consistency is what makes an API easy for beginners. Here are the most valuable rules to enforce:

1) Keep Field Names Consistent

  • Pick camelCase (common in JSON) or snake_case, then stick with it everywhere.
  • Do not mix created_at in one endpoint and createdAt in another.

2) Keep Types Stable

A field should not change type across endpoints or versions. If totalAmount is an integer minor unit in one place, it should not become a string in another without a versioned migration plan.

3) Use Reusable Models

If you return an Order object from multiple endpoints, keep it structurally consistent. If some endpoints need less data, either:

  • Use partial objects but keep the same meaning for included fields, or
  • Return a “summary” model intentionally (e.g., OrderSummary) and document it.

4) Be Predictable About Lists and Pagination

List endpoints should consistently return:

  • A list field (e.g., items) that is always an array
  • A meta section for pagination information


Practice: Standardizing Response JSON

The fastest way to make your API predictable is to standardize the top-level response shape. A beginner-friendly and widely-used pattern is an “envelope” with success, data, error, and meta.

Standard Envelope (Recommended)

  • success: boolean
  • data: the payload (object, array, or null)
  • error: null or an object (code/message/details)
  • meta: optional metadata (pagination, requestId, timing)

Now apply this to three common scenarios:

  • Detail response: one entity (e.g., an order).
  • List response: multiple entities with pagination.
  • Error response: validation error with field-level details.

Design Targets (Your Exercise)

  1. Pick a consistent date format (ISO 8601 UTC) and apply it across all entities.
  2. Pick a money format (minor units integer or decimal string) and apply it everywhere.
  3. Decide rules for null (when allowed, when avoided) and document them in the schema.
  4. Ensure every endpoint returns the same envelope shape, even on errors.


Code Example: Response Wrapper + Error Format

The following JSON examples demonstrate standardized success and error responses. You can copy these shapes into your API documentation or OpenAPI/JSON Schema definitions.

{
  "success": true,
  "data": {
    "orderId": "ord_12345",
    "status": "paid",
    "totalAmount": 1299,
    "currency": "USD",
    "createdAt": "2026-01-02T09:30:00Z"
  },
  "error": null,
  "meta": {
    "requestId": "req_8f1c2a",
    "serverTime": "2026-01-02T09:30:01Z"
  }
}

/* List response with pagination */
{
  "success": true,
  "data": {
    "items": [
      {
        "orderId": "ord_12345",
        "status": "paid",
        "totalAmount": 1299,
        "currency": "USD",
        "createdAt": "2026-01-02T09:30:00Z"
      }
    ]
  },
  "error": null,
  "meta": {
    "page": 1,
    "size": 20,
    "totalItems": 1,
    "totalPages": 1,
    "sort": "-createdAt"
  }
}

/* Validation error response */
{
  "success": false,
  "data": null,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "One or more fields are invalid.",
    "details": [
      { "field": "email", "reason": "Invalid email format." },
      { "field": "birthDate", "reason": "Expected YYYY-MM-DD." }
    ]
  },
  "meta": {
    "requestId": "req_91b7de"
  }
}

Notice how the envelope does not change shape between success and failure. Clients can always check success, then read data or error. This drastically reduces “special-case parsing” on the frontend.


Reference Table: Recommended Conventions

Area Recommendation Why It Helps Example
Field naming Use one style consistently (e.g., camelCase) Reduces mapping and confusion across endpoints createdAt, totalAmount
Dates ISO 8601 UTC strings Avoids locale ambiguity and time zone bugs 2026-01-02T09:30:00Z
Money Integer minor units or decimal string Avoids floating-point precision errors 1299 + USD
Null handling Define meaning; prefer empty arrays for lists Prevents client-side special cases "items": []
Response shape Use a stable envelope (success, data, error, meta) Simplifies clients and improves observability requestId, pagination in meta
Errors Standardize code, message, optional details Enables reliable UI handling and debugging VALIDATION_ERROR


Step-by-Step Workflow

  1. Write down your core entities and their fields (e.g., User, Order, Shipment).
  2. Choose conventions: naming style, date format, money format, null policy.
  3. Define a standard response envelope and error structure.
  4. Create reusable helpers on the server (e.g., ok(data, meta), fail(code, message, details)).
  5. Document request/response schemas (JSON Schema or OpenAPI) and keep them version-controlled.
  6. Add contract tests that validate example responses match the schema.
  7. Roll out endpoint by endpoint, keeping backward compatibility or versioning when needed.


Extra Considerations

  • Boolean flags: keep them boolean (true/false), not "Y"/"N". If your database uses Y/N, convert at the API layer.
  • Enums: document allowed string values (e.g., status can be pending, paid, canceled).
  • Backward compatibility: adding fields is usually safe; renaming/removing fields should be versioned or handled with a migration plan.
  • Consistency across services: if you have multiple microservices, define shared conventions (date, money, error codes) to avoid integration friction.
  • Debugging: including requestId in meta helps connect client errors to server logs quickly.

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

Reactions

댓글 쓰기

0 댓글