JSON Parsing in Mobile Apps
Mobile apps are API-heavy. Every screen loads data from a backend and every form submission sends data back. In Swift (iOS/macOS) and Dart (Flutter/Android), this means writing model classes that map JSON responses to native objects.
Swift — Codable Protocol
Swift uses the Codable protocol (combining Encodable and Decodable) for JSON serialization. The standard library's JSONDecoder handles all the conversion.
Given this API response:
{
"user_id": 42,
"display_name": "Ravi Mehta",
"is_premium": true,
"created_at": "2024-01-15T10:30:00Z"
}Write this Swift struct:
import Foundation
struct UserProfile: Codable {
let userId: Int
let displayName: String
let isPremium: Bool
let createdAt: String
enum CodingKeys: String, CodingKey {
case userId = "user_id"
case displayName = "display_name"
case isPremium = "is_premium"
case createdAt = "created_at"
}
}CodingKeys maps JSON snake_case keys to Swift camelCase properties. The enum only needs cases where the names differ.
Decode from URLSession:
let (data, _) = try await URLSession.shared.data(from: url)
let profile = try JSONDecoder().decode(UserProfile.self, from: data)
print(profile.displayName) // Ravi MehtaOr use keyDecodingStrategy to skip the CodingKeys enum entirely:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let profile = try decoder.decode(UserProfile.self, from: data)When to use each approach:
- CodingKeys: precise control, rename individual fields, skip optional fields
- convertFromSnakeCase: simple bulk rename, less code, but all or nothing
Swift Optional Fields
If an API field might be absent, make it optional:
struct UserProfile: Codable {
let userId: Int
let displayName: String
let avatarUrl: String? // Optional — won't throw if missing
}Dart — fromJson / toJson
Dart does not have a built-in serialization protocol. Instead, you write factory constructors and methods manually (or use code generation with json_serializable).
class UserProfile {
final int userId;
final String displayName;
final bool isPremium;
UserProfile({
required this.userId,
required this.displayName,
required this.isPremium,
});
factory UserProfile.fromJson(Map<String, dynamic> json) => UserProfile(
userId: json['user_id'] as int,
displayName: json['display_name'] as String,
isPremium: json['is_premium'] as bool,
);
Map<String, dynamic> toJson() => {
'user_id': userId,
'display_name': displayName,
'is_premium': isPremium,
};
}Parse an HTTP response in Flutter:
import 'dart:convert';
import 'package:http/http.dart' as http;
final response = await http.get(Uri.parse('https://api.example.com/profile'));
final profile = UserProfile.fromJson(jsonDecode(response.body));
print(profile.displayName);Dart Nested Objects
For nested objects, call fromJson recursively:
factory UserProfile.fromJson(Map<String, dynamic> json) => UserProfile(
userId: json['user_id'],
displayName: json['display_name'],
address: Address.fromJson(json['address']),
);Null Safety in Dart
For optional fields, use nullable types:
final String? avatarUrl; // nullable
avatarUrl: json['avatar_url'] as String?,Generate Boilerplate Automatically
Use JSONKit's tools to generate these classes from any JSON response: - Swift Codable structs: /json-to-swift - Dart fromJson/toJson classes: /json-to-dart
Paste your API response and get the complete class hierarchy — including all nested types and CodingKeys — in seconds.