0

JSON response helper

Beginner5 min read·fw-04-006

Concept

JSON response helper is a convenience layer for the most common API response pattern. Instead of manually constructing a Response with JSON body and content-type header every time, a helper encapsulates it. It also handles PHP encoding options, error handling, and status code conventions.

What a JSON helper adds:

  • Sets Content-Type: application/json; charset=UTF-8 automatically.
  • Handles json_encode() errors gracefully.
  • Provides semantic status helpers: Response::ok(), Response::created(), Response::unprocessable().
  • Can standardize response envelope structure: {data: ..., meta: ...} or {error: ..., code: ...}.

json_encode() options:

  • JSON_UNESCAPED_UNICODE: Don't escape multibyte characters (é, ü, etc.) to \uXXXX.
  • JSON_UNESCAPED_SLASHES: Don't escape / to \/. Cleaner URLs in JSON.
  • JSON_THROW_ON_ERROR: Throw JsonException on failure instead of returning false.
  • JSON_PRETTY_PRINT: Human-readable output (dev only — larger payload).

API envelope pattern: Wrapping all responses in a consistent structure:

json
{"data": {...}, "meta": {"page": 1}}
{"errors": [{"field": "email", "message": "Required"}]}

Helps API clients handle responses uniformly.

JSONP (rare today): Response::jsonp($callback, $data) — wraps JSON in a callback function for cross-origin requests in old browsers. Not needed with modern CORS.

Code Example

php
<?php
namespace Framework\Http;

class JsonResponse extends Response
{
    public function __construct(mixed $data, int $status = 200, array $extraHeaders = [])
    {
        $body = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);

        parent::__construct(
            $body,
            $status,
            array_merge(['Content-Type' => 'application/json; charset=UTF-8'], $extraHeaders)
        );
    }

    // Semantic helpers
    public static function ok(mixed $data, array $meta = []): static
    {
        return new static(empty($meta) ? $data : ['data' => $data, 'meta' => $meta]);
    }

    public static function created(mixed $data): static
    {
        return new static(['data' => $data], 201);
    }

    public static function noContent(): static
    {
        return new static(null, 204);
    }

    public static function error(string $message, int $status = 400, array $errors = []): static
    {
        $body = ['error' => $message];
        if (!empty($errors)) $body['errors'] = $errors;
        return new static($body, $status);
    }

    public static function validationError(array $errors): static
    {
        return static::error('Validation failed', 422, $errors);
    }

    public static function unauthorized(string $message = 'Unauthenticated'): static
    {
        return static::error($message, 401);
    }

    public static function forbidden(string $message = 'Forbidden'): static
    {
        return static::error($message, 403);
    }

    public static function notFound(string $message = 'Not found'): static
    {
        return static::error($message, 404);
    }
}

// Usage in controllers/handlers
class UserController
{
    public function index(): JsonResponse
    {
        $users = User::all();
        return JsonResponse::ok($users->toArray(), ['total' => $users->count()]);
    }

    public function show(int $id): JsonResponse
    {
        $user = User::find($id);
        if (!$user) return JsonResponse::notFound('User not found');
        return JsonResponse::ok($user->toArray());
    }

    public function store(Request $request): JsonResponse
    {
        $user = User::create($request->all());
        return JsonResponse::created($user->toArray());
    }
}