Skip to content

JsonResponse

Viames Marino edited this page Aug 24, 2026 · 4 revisions

Pair framework: JsonResponse

Pair\Http\JsonResponse is the explicit JSON response object for Pair v4 controllers.

Use it when an action should return a ResponseInterface instead of sending JSON immediately through legacy helpers.

Constructor

new JsonResponse(mixed $payload, int $httpCode = 200, array $headers = [])

Arguments:

  • payload: JSON payload to emit. Arrays, objects, read models, scalar values, and null are accepted.
  • httpCode: HTTP status code.
  • headers: optional additional headers keyed by header name.

Example:

use Pair\Http\JsonResponse;

return new JsonResponse(['saved' => true], 201);

With explicit cache headers:

return new JsonResponse(['id' => 7], 200, [
	'ETag' => '"abc123"',
	'Cache-Control' => 'public, max-age=300',
]);

Read-model payloads are converted to arrays before JSON encoding.

Read-model example:

use Pair\Data\ArraySerializableData;
use Pair\Data\ReadModel;
use Pair\Http\JsonResponse;

/**
 * Public health payload for an API endpoint.
 */
final readonly class HealthReadModel implements ReadModel {

	use ArraySerializableData;

	/**
	 * Build the health payload.
	 */
	public function __construct(
		public bool $ok,
		public string $version
	) {}

	/**
	 * Export the public health payload.
	 *
	 * @return	array<string, mixed>
	 */
	public function toArray(): array {

		return [
			'ok' => $this->ok,
			'version' => $this->version,
		];

	}

}

return new JsonResponse(new HealthReadModel(true, '4.0.0'), 200);

Behavior

  • send() emits Content-Type: application/json and then any additional headers. Passing a Content-Type header replaces the default, which is how Problem Details responses use application/problem+json.
  • Empty payloads keep the historical promotion to HTTP 204.
  • send() does not terminate execution by itself; the v4 dispatcher sends the response and then finishes the request.
  • When APP_DEBUG=true and observability debug headers are enabled, send() also emits Pair correlation and trace timing headers.
  • Legacy ApiResponse::respond(), error(), success(), and paginated() still terminate after sending to preserve their old contract.

Standard envelopes

For Pair v4 web controllers, prefer the helper methods on Pair\Web\Controller when the endpoint should follow the shared JSON conventions:

return $this->dataResponse([
	'updatedRows' => 12,
], 'Operation completed.');

This emits successful responses with domain data under data and non-domain metadata under meta:

{
	"data": {
		"updatedRows": 12
	},
	"meta": {
		"message": "Operation completed."
	}
}

For HTTP errors, use Problem Details:

return $this->problemResponse(
	'urn:pair:problem:invalid-request',
	'Invalid request',
	422,
	'The submitted request is missing a required field.',
	'INVALID_REQUEST'
);

This returns application/problem+json with the RFC 9457 base members plus optional Pair extension members such as code or errors.

Scalar Payloads

Scalar payloads are supported for bridge cases such as idempotency replay:

return new JsonResponse('stored', 200);

Normal public API endpoints should still prefer explicit array/object/read-model payloads.

See also: API, ApiResponse, ApiErrorResponse, ReadModel, ResponseInterface, HttpCache, Observability, EmptyResponse, TextResponse.

Clone this wiki locally