Skip to content

Serialise IP addresses to JSON#125

Merged
zanbaldwin merged 1 commit into
6.xfrom
z/json
Jun 17, 2026
Merged

Serialise IP addresses to JSON#125
zanbaldwin merged 1 commit into
6.xfrom
z/json

Conversation

@zanbaldwin

Copy link
Copy Markdown
Member

No description provided.

@zanbaldwin zanbaldwin self-assigned this Jun 17, 2026
@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown

Greptile Summary

This PR implements \JsonSerializable support across all IP classes by extending Contracts\OutputInterface from \JsonSerializable and adding a jsonSerialize(): string method to AbstractIP that delegates to toString(). Tests are added for all three concrete types (IPv4, IPv6, Multi) and documentation is updated accordingly.

  • OutputInterface (marked @experimental) now extends \JsonSerializable, making jsonSerialize() a required method on the interface — existing subclasses of AbstractIP inherit the implementation automatically, but any external code that directly implements OutputInterface will need to add jsonSerialize().
  • The string return type on AbstractIP::jsonSerialize() is a valid covariant narrowing of the mixed return declared by \JsonSerializable in PHP 8.1+, and works correctly on earlier versions too.

Confidence Score: 4/5

Safe to merge — the change is small, well-tested, and the two observations are cosmetic/test-quality issues that do not affect correctness.

The core implementation is correct and the tests cover all three IP types with the project's required dual-annotation pattern. The only findings are an always-passing assertInstanceOf assertion that silently removes test signal, and an inconsistency in the docs example comment style. Neither affects runtime behaviour.

No files require special attention; docs/03-overview.md has a minor comment-style inconsistency worth a quick polish pass.

Important Files Changed

Filename Overview
src/Contracts/OutputInterface.php Extends OutputInterface from \JsonSerializable — clean addition since the interface is @experimental, though external implementors now must implement jsonSerialize()
src/AbstractIP.php Adds jsonSerialize() delegating to toString() — correct, covariant string return type is valid against PHP 8.1+ mixed return, and toString() is never documented to throw
tests/Version/IPv4Test.php Adds testJsonSerializesToCanonicalNotation with correct dual annotation per project conventions
tests/Version/IPv6Test.php Adds testJsonSerializesToCanonicalNotation with correct dual annotation per project conventions
tests/Version/MultiTest.php Adds testJsonSerializesToProtocolAppropriateNotation with correct dual annotation per project conventions
docs/03-overview.md Adds JSON serialisation section; example output comment uses single-quoted string() notation inconsistent with adjacent docs, and relies on formatter-dependent output
docs/10-api.md Adds jsonSerialize() row to API reference table correctly
CHANGELOG.md Adds changelog entry for OutputInterface extending JsonSerializable

Class Diagram

%%{init: {'theme': 'neutral'}}%%
classDiagram
    class JsonSerializable {
        <<interface>>
        +jsonSerialize() mixed
    }
    class OutputInterface {
        <<interface, experimental>>
        +getBinary() string
        +getOctets() array
        +toString() string
        +__toString() string
    }
    class IpInterface {
        <<interface>>
        +factory(string $ip)
        +isMapped() bool
        +isDerived() bool
        +isCompatible() bool
        +isEmbedded() bool
    }
    class AbstractIP {
        <<abstract>>
        +jsonSerialize() string
        +getBinary() string
        +getOctets() array
        +equals(IpInterface $ip) bool
    }
    class IPv4 {
        +toString() string
    }
    class IPv6 {
        +toString() string
    }
    class Multi {
        +toString() string
    }

    JsonSerializable <|-- OutputInterface : extends (NEW)
    OutputInterface <|-- IpInterface
    IpInterface <|.. AbstractIP : implements
    AbstractIP <|-- IPv4
    AbstractIP <|-- IPv6
    AbstractIP <|-- Multi
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
classDiagram
    class JsonSerializable {
        <<interface>>
        +jsonSerialize() mixed
    }
    class OutputInterface {
        <<interface, experimental>>
        +getBinary() string
        +getOctets() array
        +toString() string
        +__toString() string
    }
    class IpInterface {
        <<interface>>
        +factory(string $ip)
        +isMapped() bool
        +isDerived() bool
        +isCompatible() bool
        +isEmbedded() bool
    }
    class AbstractIP {
        <<abstract>>
        +jsonSerialize() string
        +getBinary() string
        +getOctets() array
        +equals(IpInterface $ip) bool
    }
    class IPv4 {
        +toString() string
    }
    class IPv6 {
        +toString() string
    }
    class Multi {
        +toString() string
    }

    JsonSerializable <|-- OutputInterface : extends (NEW)
    OutputInterface <|-- IpInterface
    IpInterface <|.. AbstractIP : implements
    AbstractIP <|-- IPv4
    AbstractIP <|-- IPv6
    AbstractIP <|-- Multi
Loading

Reviews (1): Last reviewed commit: "feature(contracts): ✨ serialise IP addre..." | Re-trigger Greptile

public function testJsonSerializesToCanonicalNotation(string $value, string $expectedHex, string $expectedDot): void
{
$ip = IP::fromProtocol($value);
$this->assertInstanceOf(\JsonSerializable::class, $ip);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 assertInstanceOf adds no signal here

Because IpInterface always extends OutputInterface which now extends \JsonSerializable, every IP object will always satisfy this assertion unconditionally. The same pattern repeats in IPv6Test.php and MultiTest.php. A test that can never fail doesn't protect against regression — removing these three lines would keep the suite honest without losing any real coverage.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread docs/03-overview.md
Comment on lines +307 to +311
<?php
use Darsyn\IP\Version\Multi as IP;

$ip = IP::fromProtocol('::ffff:7f00:1');
echo \json_encode(['client_ip' => $ip]); // string('{"client_ip":"127.0.0.1"}')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Formatter-dependent output in example comment

The comment // string('{"client_ip":"127.0.0.1"}') assumes the default ConsistentFormatter is active; if a user has swapped in a different ProtocolFormatterInterface (e.g. one that keeps the IPv6 mapped form), the output would differ from what the example shows. The surrounding docs (e.g. the toString() section just above this block) use string("...") with double-quotes throughout, while this example uses string('...') with single-quotes — worth keeping consistent with adjacent style.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@zanbaldwin zanbaldwin merged commit 7a90a1d into 6.x Jun 17, 2026
24 checks passed
@zanbaldwin zanbaldwin deleted the z/json branch June 17, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant