SfSkills Domains Docs GitHub

Home / apex / apex-json-serialization

apex-json-serialization

Use when serializing Apex objects to JSON strings or deserializing JSON responses into Apex types — especially for callout payloads, integration parsing, and controlling null field output. Trigger keywords: 'suppress null fields JSON Apex', 'deserialize JSON into Apex class', 'JSON parse unknown shape', 'TypeException JSON deserialize', 'JSONGenerator streaming'. NOT for shaping a wrapper class for LWC — use apex/apex-wrapper-class-patterns. NOT for REST endpoint response shaping — use apex/apex-rest-services.

Open the skill folder on GitHub


Apex JSON Serialization

Use this skill when converting Apex objects to JSON strings for outbound callouts or parsing JSON responses from external APIs into Apex types. Covers the full JSON class family: JSON.serialize/deserialize, JSON.deserializeUntyped, JSONGenerator, and JSONParser.


Before Starting

Gather this context before working on anything in this domain:


Core Concepts

JSON.serialize and suppressApexObjectNulls

JSON.serialize(obj) converts any Apex object to a JSON string. By default it includes all fields, including those with null values. Pass true as the second argument — JSON.serialize(obj, true) — to suppress null fields recursively across the entire object graph. LLMs routinely omit this argument, producing bloated payloads that fail strict JSON schema validation on the receiving end.

suppressApexObjectNulls applies recursively to nested custom objects: if an outer object contains a non-null nested object that has null fields, those inner nulls are also suppressed. Static fields are not serialized by JSON.serialize — only public instance fields.

JSON.deserialize and TypeException

JSON.deserialize(jsonString, Type.class) deserializes into a typed Apex object. Extra JSON fields that don't match the Apex type are silently ignored. However, a type mismatch on a field that does exist (e.g., JSON has a String where Apex expects an Integer) throws System.TypeException at runtime — it is not silently tolerated. Always wrap external-data deserialization in try/catch for TypeException.

JSON.deserializeStrict(jsonString, Type.class) (API v45+) rejects JSON with extra fields — use when schema adherence must be enforced.

JSON.deserializeUntyped

JSON.deserializeUntyped(jsonString) returns Object, which at runtime is Map<String, Object> for JSON objects, List<Object> for arrays, or a primitive. You must cast explicitly:

Map<String, Object> root = (Map<String, Object>) JSON.deserializeUntyped(response);
List<Object> items = (List<Object>) root.get('items');

Use this when the JSON shape is not known at compile time or varies between responses.

JSONGenerator and JSONParser

JSONGenerator writes JSON token by token — use for very large payloads or precise control over field ordering. Pattern: JSON.createGenerator(prettyPrint) → writeStartObject/writeFieldName/writeString → getAsString().

JSONParser reads JSON token by token using nextToken() returning JSONToken enum values. parser.readValueAs(SomeClass.class) deserializes from the current parser position — useful for large heterogeneous arrays without loading the full structure into heap.


Common Patterns

Outbound callout payload with null suppression

When to use: Sending a structured Apex object as a POST body to a REST API that rejects null fields.

How it works:

public class OrderPayload {
    public String orderId;
    public Decimal amount;
    public String couponCode; // may be null
}
OrderPayload p = new OrderPayload();
p.orderId = '12345';
p.amount = 99.95;
// couponCode stays null — omitted from output
String body = JSON.serialize(p, true); // {"orderId":"12345","amount":99.95}

Why not the alternative: JSON.serialize(p) without the flag produces {"orderId":"12345","amount":99.95,"couponCode":null}, which fails strict schema validation.

Deserializing a typed response safely

When to use: Parsing a known-shape JSON response from an HTTP callout.

How it works:

HttpResponse res = http.send(req);
try {
    MyResponseWrapper wrapper = (MyResponseWrapper) JSON.deserialize(
        res.getBody(), MyResponseWrapper.class
    );
} catch (JSONException e) {
    // malformed JSON
} catch (System.TypeException e) {
    // shape mismatch — log and surface to caller
}

Decision Guidance

SituationRecommended ApproachReason
JSON shape matches Apex classJSON.deserialize(str, MyClass.class)Typed, safe, readable
JSON shape unknown or variesJSON.deserializeUntyped(str) + castFlexible, no compile-time type needed
Need to suppress null fieldsJSON.serialize(obj, true)Omits nulls recursively
Very large payload or streaming writeJSONGeneratorControls memory and token order
Large heterogeneous JSON arrayJSONParser + readValueAs()Avoids loading full structure into heap
Strict schema enforcement on inputJSON.deserializeStrict()Rejects extra fields

  1. Determine direction: serialize (Apex → JSON) or deserialize (JSON → Apex).
  2. For serialization: create or reuse an Apex wrapper; decide if null suppression is needed; use JSON.serialize(obj, suppressNulls).
  3. For typed deserialization: define Apex class matching JSON shape; wrap JSON.deserialize() in try/catch for TypeException.
  4. For untyped deserialization: use JSON.deserializeUntyped() and navigate Map<String,Object> / List<Object> with explicit casts.
  5. For streaming large payloads: use JSONGenerator for writes, JSONParser for reads.
  6. Validate heap impact: estimate payload size; payloads over 1 MB in sync context warrant chunking or async processing.
  7. Test error paths: include a test passing unexpected JSON to confirm TypeException is caught without leaking stack traces.

Review Checklist


Salesforce-Specific Gotchas

  1. suppressApexObjectNulls is recursive — it suppresses nulls in nested custom objects, not just the top level. If you expect a nested object to include null fields for schema reasons, this flag silently drops them.
  2. TypeException on type mismatch, NOT on extra fields — JSON.deserialize silently ignores extra JSON fields, but throws TypeException if a matching field has the wrong data type. Developers expect the extra-field tolerance to extend to type mismatches too.
  3. Static fields excluded from serialization — only public instance fields are serialized. Static, transient, and @TestVisible-only fields are excluded silently.

Output Artifacts

ArtifactDescription
Apex wrapper classTyped class matching JSON shape for JSON.deserialize
Serialized JSON stringBody string for HTTP callout or storage
check_apex_json_serialization.pyValidator confirming required method coverage

Package files