Skip to content

feat(encoding): Include null/false getter values and add #[JsonIgnore] attribute #57

@usernane

Description

@usernane

Problem Statement

When serializing objects via JsonConverter::objectToJson(), getters returning null or false are silently skipped. This causes two problems:

  1. Boolean data loss — a method like isActive() returning false is a valid value, not a signal to omit the property.
  2. Inconsistency — public properties with null values ARE included, but getters returning null are NOT.

Example:

class User {
    public ?string $nickname = null;     // ✅ Included as "nickname": null

    public function getMiddleName(): ?string {
        return null;                      // ❌ Silently excluded
    }

    public function isActive(): bool {
        return false;                     // ❌ Silently excluded
    }
}

$json = new Json();
$user = new User();
$json->addObject('user', $user);
echo $json;
// Current:  {"user":{"nickname":null}}
// Expected: {"user":{"MiddleName":null,"Active":false,"nickname":null}}

The root cause is in JsonConverter::objectToJson():

if ($propVal !== false && $propVal !== null) {
    $json->add(substr($methods[$y], 3), $propVal);
}

Proposed Solution

Two changes:

Part 1: Include all getter return values by default

Remove the null/false filter. All public getter results are serialized:

$propVal = call_user_func([$obj, $methods[$y]]);
$json->add(substr($methods[$y], 3), $propVal);

This matches the behavior already applied to public properties and aligns with json_encode philosophy (null is a valid JSON value).

Part 2: Add #[JsonIgnore] attribute for opt-out control

Introduce a PHP 8.1 attribute that can be applied to both getters and public properties to exclude them from serialization:

use WebFiori\Json\JsonIgnore;

class User {
    #[JsonIgnore]
    public string $internalId = 'abc-123';  // excluded

    #[JsonIgnore]
    public function getDebugInfo(): string {  // excluded
        return 'internal';
    }

    public function getName(): string {       // included
        return 'Ibrahim';
    }

    public ?string $email = null;            // included as null
}

Implementation sketch:

// For getters:
$refMethod = new \ReflectionMethod($obj, $methods[$y]);
if (!empty($refMethod->getAttributes(JsonIgnore::class))) {
    continue;
}

// For public properties:
foreach ($publicProps as $prop) {
    if (!empty($prop->getAttributes(JsonIgnore::class))) {
        continue;
    }
    $json->add($prop->getName(), $prop->getValue($obj));
}

The attribute class:

namespace WebFiori\Json;

#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
class JsonIgnore {}

Alternatives Considered

  • Only skip null, include false — fixes booleans but still can't serialize null from getters.
  • Configurable null strategy (nullStrategy: 'include') — adds complexity without solving the opt-out problem.
  • Return a sentinel object to signal skip — awkward API, non-standard.

Breaking Change

Yes — objects that previously relied on returning null or false from getters to hide properties will now have those properties appear in output. Developers should migrate to #[JsonIgnore] for explicit exclusion. This is a major version change candidate.

Additional Context

  • PHP 8.1 is already the minimum version, so native attributes are available.
  • #[JsonIgnore] is a well-known pattern in other ecosystems (Jackson in Java, System.Text.Json in .NET, Symfony Serializer).
  • This pairs well with future attributes like #[JsonProperty('custom_name')] and #[JsonType(UserObj::class)].

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions