smalljson

Transparently decode SmallJson-encoded API responses with a drop-in Dio interceptor.

Pairs with the nylo/smalljson Laravel package: the server shrinks JSON responses with a compact structural encoding (plus optional deflate), and SmallJsonInterceptor restores the original JSON before your app sees it. Your models, decoders and error handling don't change at all.

  • Zero app-code changesresponse.data is plain JSON data, exactly as if the server had never encoded it. Error responses (422 validation errors and friends) are decoded too.
  • Safe by negotiation — the interceptor advertises support with an X-Small-Json request header; servers only encode for clients that ask. Responses are recognised by their application/vnd.smalljson+json content type, so plain-JSON responses (and servers without the package) pass through untouched.
  • All platforms — pure Dart. Mode z (deflate) uses dart:io, so web builds automatically advertise packed-only mode instead.

Getting started

dart pub add smalljson    # or: flutter pub add smalljson
import 'package:dio/dio.dart';
import 'package:smalljson/smalljson.dart';

final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'))
  ..interceptors.add(SmallJsonInterceptor());

final response = await dio.get('/users');
print(response.data); // plain JSON — decoded transparently

That's it. The interceptor adds X-Small-Json: pz to every request, decodes any SmallJson response body in place, and rewrites the response content type back to application/json so downstream code (loggers, caches, model mappers) sees a completely ordinary JSON response.

Using with Nylo

Register the interceptor on your API service:

class ApiService extends NyApiService {
  ApiService({BuildContext? buildContext})
      : super(buildContext, decoders: modelDecoders);

  @override
  String get baseUrl => getEnv('API_BASE_URL');

  @override
  Map<Type, Interceptor> get interceptors => {
        ...super.interceptors,
        SmallJsonInterceptor: SmallJsonInterceptor(),
      };
}

Everything else — network<User>(), decoders, caching — keeps working unchanged, just with smaller payloads on the wire.

Options

SmallJsonInterceptor(
  advertise: true,              // add the request header (default true)
  modes: 'pz',                  // advertised modes; defaults to the platform's best
  rewriteContentType: true,     // report application/json after decoding (default true)
  requestHeader: 'X-Small-Json',// must match the server's smalljson.request_header
  codec: SmallJsonCodec(),      // swap in a configured codec if needed
)

Notes:

  • Requests with ResponseType.bytes or ResponseType.stream are never touched — you asked for raw data, you get raw data.
  • A corrupt envelope surfaces as a DioException (badResponse) whose error is a SmallJsonFormatException, rather than silently handing your app an undecoded envelope.
  • If you call the API from browsers as well, remember to whitelist X-Small-Json in the server's CORS allowed_headers.

Manual use

The codec works without Dio — websockets, cached blobs, isolates:

const codec = SmallJsonCodec();

final data = codec.decode(envelope);      // Map / String / UTF-8 bytes in
final envelope2 = codec.encode(data);     // encode is symmetric (handy for tests)

One platform caveat inherited from JavaScript, not from SmallJson: on web builds, JSON numbers like 1.0 decode as the integer 1 — identical to how plain JSON behaves there.

Testing

dart test                # VM suite (100% line coverage of lib/)
dart test -p vm,chrome   # additionally runs the web-platform tests in Chrome

License

MIT © Anthony Gordon

Libraries

smalljson
Transparently decode SmallJson-encoded API responses with Dio.