Lesson 3.5: JSON and Data Serialization

JSON (JavaScript Object Notation) has become the universal language of web communication. Every time you use a mobile app, browse a modern website, or interact with an API, you're likely exchanging JSON data. Despite its name suggesting JavaScript origins, JSON is now the standard format for transferring data between different systems, programming languages, and platforms.

Think about the last time you used a weather app, checked social media, or made an online purchase. Behind the scenes, your device was sending and receiving JSON data—user profiles, product information, weather forecasts, and transaction details all flowing as structured JSON messages. Understanding JSON means understanding how modern web applications communicate.

Data serialization is the process of converting complex data structures into formats that can be stored in files, sent over networks, or shared between different systems. JSON is just one serialization format, but it's become dominant because it's human-readable, lightweight, and supported by virtually every programming language.

For PHP developers, JSON skills are essential because you'll constantly exchange data with JavaScript frontends, mobile applications, external APIs, and other web services. Whether you're building REST APIs, processing AJAX requests, or integrating with third-party services, JSON will be your primary data format.

Understanding JSON Structure

JSON organizes data using a simple syntax that closely resembles PHP arrays and objects. This similarity makes JSON intuitive for PHP developers, but understanding the subtle differences prevents common mistakes and ensures reliable data exchange.

JSON supports six basic data types: strings, numbers, booleans, null, objects (like associative arrays), and arrays (like indexed arrays). These types map naturally to PHP data types, making conversion straightforward.

<?php
// PHP array that we'll convert to JSON
$userData = [
    'name' => 'Alice Johnson',
    'age' => 28,
    'email' => '[email protected]',
    'active' => true,
    'balance' => 1250.75,
    'last_login' => null,
    'preferences' => [
        'theme' => 'dark',
        'notifications' => true,
        'language' => 'en'
    ],
    'recent_orders' => [
        ['id' => 1001, 'total' => 45.99],
        ['id' => 1002, 'total' => 128.50]
    ]
];

// Convert PHP array to JSON string
$jsonString = json_encode($userData);
echo "JSON Output:\n$jsonString\n";
?>

The resulting JSON is human-readable and follows a predictable structure where objects use curly braces {} and arrays use square brackets []. Keys are always strings in double quotes, and values follow JSON formatting rules.

Here's how different PHP data types convert to JSON:

<?php
$examples = [
    'string' => 'Hello World',
    'integer' => 42,
    'float' => 3.14159,
    'boolean_true' => true,
    'boolean_false' => false,
    'null_value' => null,
    'indexed_array' => [1, 2, 3, 4],
    'empty_array' => [],
    'empty_object' => new stdClass()
];

echo "PHP to JSON conversions:\n";
foreach ($examples as $type => $value) {
    $json = json_encode($value);
    echo "$type: $json\n";
}
?>

Converting Between PHP and JSON

The json_encode() and json_decode() functions handle conversion between PHP data structures and JSON strings. Understanding their options and behavior helps you handle edge cases and optimize performance.

Encoding PHP Data to JSON

<?php
$productCatalog = [
    [
        'id' => 1,
        'name' => 'Wireless Headphones',
        'price' => 79.99,
        'categories' => ['Electronics', 'Audio'],
        'in_stock' => true
    ],
    [
        'id' => 2,
        'name' => 'Coffee Mug',
        'price' => 12.50,
        'categories' => ['Kitchen', 'Drinkware'],
        'in_stock' => false
    ]
];

// Basic JSON encoding
$basicJson = json_encode($productCatalog);

// Pretty-printed JSON (easier to read)
$prettyJson = json_encode($productCatalog, JSON_PRETTY_PRINT);

// Unescaped Unicode (for international text)
$unicodeJson = json_encode($productCatalog, JSON_UNESCAPED_UNICODE);

echo "Basic JSON:\n$basicJson\n\n";
echo "Pretty JSON:\n$prettyJson\n";
?>

The JSON_PRETTY_PRINT flag formats JSON with indentation and line breaks, making it much easier to read during development. This is invaluable for debugging and API documentation.

Decoding JSON to PHP

<?php
$jsonData = '{
    "user_id": 123,
    "username": "alice_j",
    "profile": {
        "first_name": "Alice",
        "last_name": "Johnson",
        "age": 28
    },
    "settings": ["email_notifications", "dark_theme"]
}';

// Decode to associative array (most common)
$userData = json_decode($jsonData, true);
echo "Username: " . $userData['username'] . "\n";
echo "First name: " . $userData['profile']['first_name'] . "\n";

// Decode to object (alternative approach)
$userObject = json_decode($jsonData);
echo "Username: " . $userObject->username . "\n";
echo "First name: " . $userObject->profile->first_name . "\n";
?>

The second parameter of json_decode() determines the return type. true returns associative arrays (usually preferred), while false returns objects with properties accessible using arrow notation.

Error Handling and Validation

JSON operations can fail for various reasons—invalid syntax, encoding issues, or data types that don't translate cleanly. Proper error handling ensures your applications respond gracefully to these situations.

<?php
function safeJsonEncode($data) {
    $json = json_encode($data);

    if (json_last_error() !== JSON_ERROR_NONE) {
        $error = json_last_error_msg();
        throw new Exception("JSON encoding failed: $error");
    }

    return $json;
}

function safeJsonDecode($json, $assoc = true) {
    $data = json_decode($json, $assoc);

    if (json_last_error() !== JSON_ERROR_NONE) {
        $error = json_last_error_msg();
        throw new Exception("JSON decoding failed: $error");
    }

    return $data;
}

// Testing error handling
try {
    $validData = ['name' => 'John', 'age' => 30];
    $json = safeJsonEncode($validData);
    echo "Encoding successful: $json\n";

    $decoded = safeJsonDecode($json);
    echo "Decoding successful: " . $decoded['name'] . "\n";

} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}
?>

Common JSON errors include syntax errors in JSON strings, UTF-8 encoding issues, and attempts to encode resources or objects that can't be serialized:

<?php
// Examples of problematic data
$problematicData = [
    'file_handle' => fopen('php://temp', 'r'), // Resources can't be encoded
    'recursive' => null // We'll make this recursive
];

// Create circular reference
$problematicData['recursive'] = &$problematicData;

// This will fail
$result = json_encode($problematicData);
if ($result === false) {
    echo "JSON encoding failed: " . json_last_error_msg() . "\n";
}

// Clean up
fclose($problematicData['file_handle']);
?>

Working with APIs and External Data

Most modern web applications consume data from external APIs that return JSON responses. Understanding how to request, parse, and validate this data is essential for building integrated applications.

<?php
function fetchApiData($url) {
    // Using cURL for HTTP requests
    $curl = curl_init();
    curl_setopt_array($curl, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_HTTPHEADER => [
            'Accept: application/json',
            'User-Agent: MyApp/1.0'
        ]
    ]);

    $response = curl_exec($curl);
    $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    curl_close($curl);

    if ($httpCode !== 200) {
        throw new Exception("API request failed with status: $httpCode");
    }

    return safeJsonDecode($response);
}

// Example: Fetching weather data (simulated)
try {
    // In real usage, this would be an actual API endpoint
    $weatherData = [
        'location' => 'Buffalo, NY',
        'temperature' => 72,
        'conditions' => 'Partly Cloudy',
        'humidity' => 65,
        'wind_speed' => 8
    ];

    // Simulate API response
    $jsonResponse = json_encode($weatherData);
    $data = safeJsonDecode($jsonResponse);

    echo "Weather in {$data['location']}:\n";
    echo "Temperature: {$data['temperature']}°F\n";
    echo "Conditions: {$data['conditions']}\n";

} catch (Exception $e) {
    echo "Failed to fetch weather data: " . $e->getMessage() . "\n";
}
?>

When working with external APIs, always validate the structure of returned data before using it:

<?php
function validateWeatherData($data) {
    $required = ['location', 'temperature', 'conditions'];

    foreach ($required as $field) {
        if (!isset($data[$field])) {
            throw new Exception("Missing required field: $field");
        }
    }

    if (!is_numeric($data['temperature'])) {
        throw new Exception("Temperature must be numeric");
    }

    return true;
}

// Usage with validation
$apiResponse = '{"location":"Buffalo, NY","temperature":"72","conditions":"Sunny"}';

try {
    $data = safeJsonDecode($apiResponse);
    validateWeatherData($data);
    echo "Valid weather data received\n";
} catch (Exception $e) {
    echo "Data validation failed: " . $e->getMessage() . "\n";
}
?>

Building JSON APIs

Creating your own JSON APIs means designing endpoints that return structured data in response to HTTP requests. This involves handling input, processing data, and formatting appropriate JSON responses.

<?php
class SimpleApiHandler {
    private $users = [
        1 => ['id' => 1, 'name' => 'Alice Johnson', 'email' => '[email protected]'],
        2 => ['id' => 2, 'name' => 'Bob Smith', 'email' => '[email protected]'],
        3 => ['id' => 3, 'name' => 'Charlie Brown', 'email' => '[email protected]']
    ];

    public function handleRequest() {
        header('Content-Type: application/json');

        $method = $_SERVER['REQUEST_METHOD'];
        $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

        try {
            switch ($path) {
                case '/api/users':
                    if ($method === 'GET') {
                        return $this->getAllUsers();
                    }
                    break;

                case '/api/user':
                    if ($method === 'GET') {
                        return $this->getUser();
                    }
                    break;

                default:
                    return $this->errorResponse('Endpoint not found', 404);
            }
        } catch (Exception $e) {
            return $this->errorResponse('Internal server error', 500);
        }
    }

    private function getAllUsers() {
        return $this->successResponse(array_values($this->users));
    }

    private function getUser() {
        $userId = intval($_GET['id'] ?? 0);

        if (isset($this->users[$userId])) {
            return $this->successResponse($this->users[$userId]);
        } else {
            return $this->errorResponse('User not found', 404);
        }
    }

    private function successResponse($data) {
        return json_encode([
            'success' => true,
            'data' => $data
        ]);
    }

    private function errorResponse($message, $code = 400) {
        http_response_code($code);
        return json_encode([
            'success' => false,
            'error' => $message
        ]);
    }
}

// Handle API request
$api = new SimpleApiHandler();
echo $api->handleRequest();
?>

This basic API demonstrates important concepts: proper HTTP status codes, consistent response format, error handling, and content-type headers. Real APIs would include authentication, rate limiting, and more sophisticated routing.

Data Storage and Configuration

JSON excels at storing configuration data, user preferences, and application state. Unlike databases, JSON files are human-readable and don't require special software to edit.

<?php
class JsonConfigManager {
    private $configFile;

    public function __construct($configFile = 'config.json') {
        $this->configFile = $configFile;
    }

    public function loadConfig() {
        if (!file_exists($this->configFile)) {
            return $this->getDefaultConfig();
        }

        $json = file_get_contents($this->configFile);
        $config = json_decode($json, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception('Invalid configuration file');
        }

        return $config;
    }

    public function saveConfig($config) {
        $json = json_encode($config, JSON_PRETTY_PRINT);

        if (file_put_contents($this->configFile, $json) === false) {
            throw new Exception('Failed to save configuration');
        }

        return true;
    }

    private function getDefaultConfig() {
        return [
            'app_name' => 'My Application',
            'debug_mode' => false,
            'database' => [
                'host' => 'localhost',
                'port' => 3306,
                'name' => 'myapp'
            ],
            'features' => [
                'user_registration' => true,
                'email_notifications' => true,
                'api_access' => false
            ]
        ];
    }
}

// Usage example
try {
    $configManager = new JsonConfigManager();
    $config = $configManager->loadConfig();

    echo "App name: " . $config['app_name'] . "\n";
    echo "Debug mode: " . ($config['debug_mode'] ? 'On' : 'Off') . "\n";

    // Modify configuration
    $config['debug_mode'] = true;
    $config['features']['api_access'] = true;

    $configManager->saveConfig($config);
    echo "Configuration updated successfully\n";

} catch (Exception $e) {
    echo "Configuration error: " . $e->getMessage() . "\n";
}
?>

Advanced JSON Techniques

As your applications become more sophisticated, you'll encounter scenarios requiring advanced JSON handling—custom serialization, complex data structures, and performance optimization.

Custom JSON Serialization

Sometimes you need control over how objects get converted to JSON, especially when working with classes that contain sensitive data or complex relationships.

<?php
class User implements JsonSerializable {
    private $id;
    private $username;
    private $email;
    private $password; // Sensitive data
    private $created_at;

    public function __construct($id, $username, $email, $password) {
        $this->id = $id;
        $this->username = $username;
        $this->email = $email;
        $this->password = password_hash($password, PASSWORD_DEFAULT);
        $this->created_at = new DateTime();
    }

    // Control JSON serialization
    public function jsonSerialize() {
        return [
            'id' => $this->id,
            'username' => $this->username,
            'email' => $this->email,
            // Exclude password from JSON
            'member_since' => $this->created_at->format('Y-m-d')
        ];
    }

    public function getPublicProfile() {
        return [
            'username' => $this->username,
            'member_since' => $this->created_at->format('Y-m-d')
        ];
    }
}

$user = new User(1, 'alice_j', '[email protected]', 'secret123');

// Full user data (excluding password)
echo "Full user JSON:\n" . json_encode($user) . "\n\n";

// Public profile only
echo "Public profile JSON:\n" . json_encode($user->getPublicProfile()) . "\n";
?>

Handling Large JSON Data

When working with large JSON datasets, memory usage and processing time become important considerations. Streaming approaches help handle data that won't fit entirely in memory.

<?php
function processLargeJsonFile($filename, $callback) {
    if (!file_exists($filename)) {
        throw new Exception("File not found: $filename");
    }

    $handle = fopen($filename, 'r');
    if (!$handle) {
        throw new Exception("Cannot open file: $filename");
    }

    $buffer = '';
    $bracketCount = 0;
    $inString = false;
    $recordCount = 0;

    while (!feof($handle)) {
        $char = fread($handle, 1);
        $buffer .= $char;

        // Simple JSON object detection
        if ($char === '{' && !$inString) {
            $bracketCount++;
        } elseif ($char === '}' && !$inString) {
            $bracketCount--;

            // Complete object found
            if ($bracketCount === 0) {
                $object = json_decode($buffer, true);
                if ($object !== null) {
                    $callback($object);
                    $recordCount++;
                }
                $buffer = '';
            }
        } elseif ($char === '"') {
            $inString = !$inString;
        }
    }

    fclose($handle);
    return $recordCount;
}

// Example usage
function processRecord($record) {
    echo "Processing: " . $record['name'] . "\n";
}

// This would work with a large JSON file containing multiple objects
// processLargeJsonFile('large-data.json', 'processRecord');
?>

Performance and Best Practices

JSON operations can impact application performance, especially when handling large datasets or high-frequency API calls. Understanding optimization techniques helps maintain responsive applications.

<?php
// Performance comparison: JSON vs other formats
function benchmarkSerialization($data, $iterations = 1000) {
    // JSON encoding
    $start = microtime(true);
    for ($i = 0; $i < $iterations; $i++) {
        $json = json_encode($data);
    }
    $jsonTime = microtime(true) - $start;

    // PHP serialization
    $start = microtime(true);
    for ($i = 0; $i < $iterations; $i++) {
        $serialized = serialize($data);
    }
    $serializeTime = microtime(true) - $start;

    echo "JSON encoding: " . number_format($jsonTime * 1000, 2) . " ms\n";
    echo "PHP serialize: " . number_format($serializeTime * 1000, 2) . " ms\n";
    echo "JSON size: " . strlen($json) . " bytes\n";
    echo "Serialize size: " . strlen($serialized) . " bytes\n";
}

$testData = [
    'users' => array_fill(0, 100, ['name' => 'User Name', 'email' => '[email protected]']),
    'timestamp' => time(),
    'metadata' => ['version' => '1.0', 'environment' => 'production']
];

benchmarkSerialization($testData);
?>

Best practices for JSON performance:

<?php
// Cache JSON encoding for static data
class JsonCache {
    private static $cache = [];

    public static function encode($data, $key = null) {
        $key = $key ?: md5(serialize($data));

        if (!isset(self::$cache[$key])) {
            self::$cache[$key] = json_encode($data);
        }

        return self::$cache[$key];
    }
}

// Use appropriate JSON flags for your needs
function optimizedJsonEncode($data, $options = 0) {
    // Common optimizations
    $flags = JSON_UNESCAPED_SLASHES; // Don't escape forward slashes

    if (defined('JSON_UNESCAPED_UNICODE')) {
        $flags |= JSON_UNESCAPED_UNICODE; // Better for international text
    }

    return json_encode($data, $flags | $options);
}

// Example usage
$apiResponse = ['message' => 'Success', 'data' => ['url' => 'https://example.com/path']];
echo optimizedJsonEncode($apiResponse);
?>

Key Takeaways

JSON is the backbone of modern web communication, enabling seamless data exchange between different systems and programming languages. Master the basic encoding and decoding operations first, then build understanding of error handling, validation, and performance optimization.

Always validate JSON data from external sources—never trust that it contains the structure or data types you expect. Implement proper error handling to make your applications resilient to malformed data and network issues.

When building APIs, follow consistent patterns for success and error responses. Use appropriate HTTP status codes, include meaningful error messages, and design your JSON structure to be intuitive for API consumers.

Your string, array, and file handling skills from previous lessons are essential for effective JSON work. JSON often serves as the bridge between file storage, user input, and API communication, making it a central technology in modern web development.

Understanding JSON prepares you for building modern web applications that communicate with JavaScript frontends, mobile apps, and external services. It's an essential skill for any PHP developer working in today's interconnected web ecosystem.