Testing JSON processing with Jackson and Gson in Java

JSON sits between many parts of a modern Java system: REST controllers, message brokers, configuration files, payment gateways and browser clients. A test that checks only whether a string can be parsed misses important behaviour, including missing fields, unknown properties, date formats and numeric precision. Learn more about Oscardavidsanchez.com.

Jackson and Gson are both mature JSON libraries, yet they make different decisions about defaults, annotations and object mapping. Those differences can create defects when a team changes libraries, upgrades dependencies or shares fixtures between services.

For Australian teams, these details often appear in practical places: a Sydney service consuming a Melbourne partner’s API, a Brisbane system handling Australian Eastern time, or an EOFY reporting process receiving large batches overnight. Reliable tests should prove the contract and the business meaning of the data, rather than merely confirm that deserialisation completed.

Why JSON tests need clear intent

A JSON test should state what behaviour matters. If the requirement is that an API preserves a customer’s postcode as text, the test should verify "postcode": "0200" rather than relying on an integer field. If the requirement is that unknown response fields are ignored for forward compatibility, that deserves a separate test from ordinary round-trip serialisation.

There are three useful layers. A parser test checks valid and malformed JSON. A mapping test checks conversion between JSON and Java objects. A contract test checks the shape and semantics exchanged by two systems. Keeping these purposes distinct makes failures easier to diagnose and prevents a broad integration test from hiding a small mapper defect.

Avoid asserting the entire JSON string unless property order and formatting are part of the contract. Jackson may write fields in a different order after a refactor, while Gson may omit null values. Parsing the output into a tree or inspecting selected properties usually produces a more stable assertion.

For REST endpoints, combine semantic checks with schema validation where appropriate. A response can be valid JSON and still contain the wrong currency, an invalid date, or a missing field required by a downstream service.

Setting up Jackson for predictable tests

Jackson’s ObjectMapper is highly configurable, which is powerful but can make tests inconsistent if every class creates its own mapper. Prefer a shared, explicitly configured mapper in production and use the same configuration in unit and integration tests. Register modules such as JavaTimeModule when domain objects use LocalDate, Instant or OffsetDateTime.

A useful test fixture might contain a name, an Australian postcode, a decimal amount and a timestamp. Verify both directions: Java to JSON and JSON back to Java. For monetary values, assert the value and scale according to the domain rule. A BigDecimal representing 10.50 may need different treatment from a floating-point number representing approximately the same amount.

Unknown-property behaviour should be deliberate. Strict deserialisation can expose an incompatible API change immediately, while tolerant deserialisation can help a consumer survive additive fields. Test the selected policy explicitly, using a fixture with an extra property and an assertion about whether parsing succeeds.

Dates deserve special attention. A timestamp from Perth, Adelaide or Sydney can cross daylight-saving boundaries, and LocalDateTime carries no offset. If the business event represents an absolute moment, prefer an offset-aware type and assert the expected instant. Tests should include the transition between AEDT and AEST rather than assuming every Australian date behaves identically.

Understanding Gson’s mapping behaviour

Gson is straightforward for many POJOs and is often convenient in small services or Android-related code. Its defaults, however, should not be treated as interchangeable with Jackson’s. Gson commonly serialises fields directly, can omit nulls, and uses annotations such as @SerializedName to map a Java property to a wire name.

Test the wire names explicitly. A Java field called customerId might need to appear as customer_id, and a rename can silently break a partner integration if the fixture checks only that an object was created. Include a test for both serialisation and deserialisation so that an accidental one-way mapping does not survive review.

Custom TypeAdapter implementations are valuable for unusual formats, but they need focused tests. Cover valid values, null values, malformed values and boundary values. A date adapter that accepts 2024-01-31 should have a clear response to 31/01/2024, especially when data comes from systems using different regional conventions.

Gson’s handling of generic collections also benefits from dedicated tests. Use TypeToken when reading lists or maps and verify empty collections, absent properties and null elements. A test that covers only one populated list can miss a production failure when an Australian supplier returns an empty array during a quiet trading period.

Making assertions useful and durable

JSON assertions should be precise without becoming coupled to irrelevant formatting. With Jackson, JsonNode allows tests to inspect paths, types and values. JSONAssert, JsonPath or equivalent tools can provide expressive comparisons when a response contains nested objects. The important principle is to assert the contract’s meaningful parts.

For example, an API test can check that customer.address.postcode is a string, total is a decimal-compatible value, and items contains two records. It can ignore a generated request identifier if that identifier is not part of the behaviour under test. This balance keeps tests stable while still catching a breaking response.

Property-based or parameterised tests are especially effective for edge cases. Generate empty strings, long names, Unicode characters, missing fields, duplicate array entries and numbers near accepted limits. Include escaped characters such as apostrophes and line breaks; customer names and address notes are not limited to simple ASCII.

Keep fixture files readable and versioned beside the tests. A small fixture can explain a contract better than a long chain of builder calls, while builders remain useful for generating variations. If fixtures are shared across teams, record whether they represent a request, a response, an event or an invalid example.

A practical review checklist

For mapping and contract tests, check:

For test maintainability, check:

Testing failures, asynchronous flows and performance

Malformed JSON should be tested as a normal input category, not an afterthought. Include truncated documents, invalid escape sequences, duplicate keys if relevant, wrong top-level types and unexpected encoding. Assert the exception type or domain error, but avoid tying the test to a fragile vendor-specific message unless that message is exposed to users.

Asynchronous systems add another layer. A JSON event placed on Kafka, RabbitMQ or a cloud queue may be serialised by one process and consumed later by another version. Test the published payload, the consumer’s mapping and the acknowledgement or dead-letter behaviour. Make the test deterministic by controlling clocks, retries and message ordering.

For large payloads, add performance tests that measure more than average speed. Track allocation, heap pressure and throughput for realistic arrays. Jackson’s streaming API can help when a service handles large exports, while Gson’s tree model may be perfectly adequate for small messages. The right choice depends on payload size, latency targets and operational limits.

A Melbourne retailer processing a heavy EOFY export has a different profile from a small internal API in Canberra. Measure with representative data, including the largest expected order and realistic Unicode text. Performance tests should run separately from fast unit tests but remain part of the delivery pipeline or scheduled quality checks.

Choosing a reliable test strategy

A useful test suite combines library-specific checks with library-neutral contract checks. First define the JSON that external systems must receive and accept. Then test Jackson or Gson configuration against that contract. This prevents the implementation library from becoming the accidental definition of the API.

When migrating between libraries, run both mappers against the same corpus of fixtures. Compare field names, null handling, date output, numeric representation and unknown-property behaviour. Differences that look harmless in a unit test can affect signatures, cache keys or downstream validation.

Concern Jackson Gson Testing focus
Configuration Highly configurable mapper and modules Simple defaults with custom adapters Verify shared configuration
Java time Usually needs registered time module Often needs explicit adapters Test offsets and formats
Unknown fields Can be strict or tolerant Commonly tolerant Define compatibility policy
Field naming Annotations and naming strategies @SerializedName and field rules Assert exact wire names
Large payloads Strong streaming support Tree and stream options available Measure memory and throughput
Numeric values Flexible types and coercion settings Common coercion behaviour Protect money and identifiers

The most valuable tests are small, explicit and representative. A focused mapper test can catch a renamed property before a full Sydney-to-Perth integration environment is available, while a contract test confirms that the deployed service still speaks the agreed language.

The key thing to remember is that JSON processing is a contract, not just a parsing task: test names, types, dates, failure behaviour and business meaning with the same care you give the Java code around them.