Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

Objective-C JSON Formatting for Legacy iOS Applications

If you maintain an older iOS app that still ships Objective-C, JSON work usually comes down to one Foundation class: NSJSONSerialization. It is still Apple's built-in way to turn Foundation objects into JSON and parse JSON back into NSDictionary, NSArray, NSString, NSNumber, and NSNull.

For a real legacy codebase, the hard part is rarely "how do I call the API?" It is avoiding invalid payloads, handling null safely, understanding which formatting options exist on the OS versions you still support, and quickly checking whether a broken response is malformed JSON or just unexpected data. That is where a JSON formatter is useful before you even touch application code.

What Still Matters in Legacy Objective-C

NSJSONSerialization is still the right default for formatting and parsing JSON in Objective-C. Apple's current documentation also notes that the class is thread-safe on iOS 7 and later, so modernized legacy apps do not need a third-party formatter just to serialize normal API payloads.

  • Use +isValidJSONObject: before serializing anything that may contain custom model objects, dates, sets, or computed numeric values.
  • Remember that JSON null becomes [NSNull null], not nil.
  • JSON numbers map to NSNumber, including booleans.
  • By default, Apple expects a top-level array or dictionary when writing JSON. Scalar values are a special case and should be handled deliberately with fragment options.

Serialize Objective-C Objects Into Readable JSON

When you are logging a request body, creating a fixture, or saving local state, a good serializer path does three things: validates the object first, enables readable formatting only when it helps, and keeps newer options behind availability checks.

Safer Serialization Example

NSDictionary *payload = @{
    @"name": @"Alice",
    @"age": @(30),
    @"active": @(YES),
    @"roles": @[@"admin", @"editor"],
    @"manager": [NSNull null]
};

if (![NSJSONSerialization isValidJSONObject:payload]) {
    NSLog(@"Payload contains a non-JSON type.");
    return;
}

NSJSONWritingOptions options = 0;
options |= NSJSONWritingPrettyPrinted;

if (@available(iOS 11.0, *)) {
    options |= NSJSONWritingSortedKeys;
}

if (@available(iOS 13.0, *)) {
    options |= NSJSONWritingWithoutEscapingSlashes;
}

NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:payload
                                                   options:options
                                                     error:&error];

if (jsonData == nil) {
    NSLog(@"Serialization failed: %@", error.localizedDescription);
    return;
}

NSString *jsonString = [[NSString alloc] initWithData:jsonData
                                             encoding:NSUTF8StringEncoding];
NSLog(@"%@", jsonString);

Why This Pattern Holds Up

  • isValidJSONObject: catches unsupported types before you get a runtime failure.
  • NSJSONWritingPrettyPrinted is best for logs, fixtures, and local debugging. For network traffic, use 0 unless humans need to read the payload.
  • NSJSONWritingSortedKeys is available on iOS 11 and later and is useful when you want stable, diff-friendly output in tests or support logs.
  • NSJSONWritingWithoutEscapingSlashes is available on iOS 13 and later. It improves readability for URLs, but it is cosmetic rather than semantic.
  • JSON does not allow NaN or infinity values. If floating-point calculations can produce them, sanitize those numbers before serializing.

Parse API Responses Without Common Legacy Bugs

Deserialization looks simple until you hit responses with nullable fields, unexpected top-level values, or code that assumes every response is a dictionary. The safe approach is to parse into id, confirm the root type, then unwrap any NSNull values before using them.

Defensive Parsing Example

NSData *responseData = /* data from NSURLSession */;

NSJSONReadingOptions options = 0;
BOOL expectsScalarJSON = NO;

if (expectsScalarJSON) {
    options |= NSJSONReadingFragmentsAllowed;
}

NSError *error = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:responseData
                                                options:options
                                                  error:&error];

if (jsonObject == nil) {
    NSLog(@"Parse failed: %@", error.localizedDescription);
    return;
}

if (![jsonObject isKindOfClass:[NSDictionary class]]) {
    NSLog(@"Expected a dictionary but got: %@", [jsonObject class]);
    return;
}

NSDictionary *response = (NSDictionary *)jsonObject;
id emailValue = response[@"email"];
NSString *email = (emailValue == [NSNull null]) ? nil : emailValue;

NSLog(@"Parsed email: %@", email);

Current Parsing Notes Worth Knowing

  • Prefer NSJSONReadingFragmentsAllowed for top-level scalar JSON. Older code often uses NSJSONReadingAllowFragments, which Apple now marks as deprecated.
  • NSJSONReadingMutableContainers is only useful if you truly need mutable arrays or dictionaries immediately after parsing. In most maintenance work, immutable results plus targeted copies are cleaner.
  • NSJSONReadingMutableLeaves is rarely worth using in practice.
  • NSJSONReadingJSON5Allowed exists on iOS 15 and later, but it is a niche tool for human-edited local content. Do not quietly enable it for normal API traffic if you expect strict JSON from a backend.

Compatibility and Interoperability Caveats

Apple's API behavior and the JSON standard are close, but not identical in defaults. RFC 8259 says a JSON text can be any serialized value, while NSJSONSerialization still treats top-level fragments as opt-in behavior. That difference matters when an API returns a bare string, number, or boolean.

  • If your app still supports iOS 10 or earlier, guard NSJSONWritingSortedKeys with @available.
  • If your app still supports iOS 12 or earlier, do the same for NSJSONWritingWithoutEscapingSlashes.
  • If you maintain a mixed fleet of old app versions, keep your JSON generator conservative so responses remain easy to compare across builds and devices.
  • Output key sorting helps humans and test diffs, but JSON object key order should not be treated as business logic.

Common Breakages in Legacy Apps

  • Custom model objects in payloads: Convert them to dictionaries before calling dataWithJSONObject:options:error:.
  • Dates and URLs: Serialize them explicitly as ISO 8601 strings or other agreed wire formats instead of passing NSDate or NSURL directly.
  • Blind dictionary casts: Some APIs return arrays at the root. Check the type before subscripting.
  • UI freezes on large payloads: Pretty-printing or parsing big JSON on the main thread is still expensive. Move heavy JSON work off the UI path.
  • Assuming malformed data is valid JSON: Before changing parsing code, drop the payload into an offline formatter to verify whether the issue is syntax, structure, or unexpected nulls.

When a JSON Formatter Helps More Than More Code

For legacy iOS maintenance, a formatter is often the fastest first step. Paste the raw response fromNSURLSession, a proxy capture, or an app log and confirm three things before editing Objective-C: whether the payload is valid JSON, what the real root type is, and which fields are actually null.

That workflow is especially useful when you are debugging production-like data, building test fixtures, or comparing server responses without sending potentially sensitive payloads through another web service.

Bottom Line

Objective-C JSON formatting in 2026 is still mostly about using NSJSONSerialization carefully, not replacing it. Validate objects before writing, guard newer formatting options by OS version, treat NSNull as a first-class case, and use an offline formatter to inspect real payloads before you change legacy parsing code.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool