Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,12 @@ jobs:
fail-fast: false
matrix:
cip_tag:
- "5.41"
- "5.40"
- "5.38"
- "5.36"
- "5.34"
- "5.32"
- "5.30"
- "5.28"
- "5.26"
- "5.24"
- "5.22"
- "5.20"
- "5.45"
- "5.44"
- "5.42"
jq:
- "1.7.1"
- "1.8.2"

env:
CIP_TAG: ${{ matrix.cip_tag }}
Expand Down Expand Up @@ -58,9 +52,15 @@ jobs:
run: |
cip start

- name: Install jq
run: |
cip sudo curl -fsSL -o /usr/local/bin/jq https://github.com/jqlang/jq/releases/download/jq-${{ matrix.jq }}/jq-linux-amd64
cip sudo chmod +x /usr/local/bin/jq

- name: Diagnostics
run: |
cip diag
cip exec jq --version

- name: Install-Dependencies
run: |
Expand Down
114 changes: 114 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Test::JSON::Diff ![static](https://github.com/uperl/Test-JSON-Diff/workflows/static/badge.svg) ![linux](https://github.com/uperl/Test-JSON-Diff/workflows/linux/badge.svg)

Check two large JSON strings for structural equality

# SYNOPSIS

```perl
use Test2::V0;
use Test::JSON::Diff qw( json_eq_or_diff );

json_eq_or_diff '{"a":1,"b":[1,2]}', '{ "b" : [1,2], "a" : 1 }';
json_eq_or_diff $actual_json, $expected_json, 'response body';
json_eq_or_diff $actual_json, $expected_json, { max_lines => 100 };
json_eq_or_diff $actual_json, $expected_json, 'response body', { context => 5 };

done_testing;
```

# DESCRIPTION

This module provides a [Test2](https://metacpan.org/pod/Test2) compatible test for comparing two JSON
documents for structural equality. It is intended for large documents,
so the JSON is never decoded into Perl. Instead each document is
canonicalized with `jq` and, only if they differ, the canonical forms
are compared with `diff`. The failure diagnostic is a unified diff of
the pretty-printed JSON.

Two documents are considered the same if they differ only in:

- object key order

`{"a":"b","c":"d"}` is the same as `{"c":"d","a":"b"}`.

- whitespace outside of strings

`{"a":"b"}` is the same as `{ "a" : "b" }`.

Any other difference is a failure, including:

- array order

`[1,2]` is not the same as `[2,1]`.

- types

`[1]` is not the same as `["1"]`, and `[true]` is not the same as `[1]`.

- number literals

`[1]` is not the same as `[1.0]`. Number literals are compared as
written, which also means that large integers are compared exactly.

# FUNCTIONS

## json\_eq\_or\_diff

```
json_eq_or_diff $actual_json, $expected_json;
json_eq_or_diff $actual_json, $expected_json, $test_name;
json_eq_or_diff $actual_json, $expected_json, \%options;
json_eq_or_diff $actual_json, $expected_json, $test_name, \%options;
```

Passes if `$actual_json` and `$expected_json` are structurally the
same JSON. Both must be strings of raw, undecoded, UTF-8 encoded JSON
containing exactly one JSON value. If either is not valid JSON, the
test fails and the diagnostic contains the error reported by `jq`.

If the documents differ, the diagnostic is a unified diff of the
pretty-printed, key sorted JSON, with the expected document as the
original (`-`) and the actual document as the new (`+`).

`$test_name` defaults to `json is the same`.

Options:

- context

The number of lines of context around each change in the diff.
Defaults to `3`.

- max\_lines

The maximum number of lines of diff output to include in the
diagnostic. If the diff is longer, the remaining lines are replaced
with `...`. Defaults to `50`.

This function will die if an unrecognized option is passed, or if
either `jq` or `diff` cannot be found in the `PATH`.

# CAVEATS

Strings containing wide characters are not currently supported; the
JSON must be passed as UTF-8 encoded bytes.

This module requires `jq` 1.7 or later, since older versions do not
preserve number literals. This is checked when the distribution is
installed, but not at runtime.

# SEE ALSO

- [Test::Differences](https://metacpan.org/pod/Test::Differences)
- [https://jqlang.org](https://jqlang.org)

# AUTHOR

Graham Ollis <plicease@cpan.org>

# COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Graham Ollis.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
4 changes: 3 additions & 1 deletion author.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ pod_spelling_system:
# (regardless of what spell check thinks)
# or stuff that I like to spell incorrectly
# intentionally
stopwords: []
stopwords:
- canonicalized
- undecoded

pod_coverage:
skip: 0
Expand Down
7 changes: 5 additions & 2 deletions dist.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@ copyright_year = 2026
version = 0.01

[@Author::Plicease]
:version = 2.80
:version = 2.79
release_tests = 1
installer = Author::Plicease::MakeMaker
github_user = uperl
default_branch = main
test2_v0 = 1
workflow = static
workflow = linux
version_plugin = PkgVersion::Block

[Author::Plicease::Core]

[Author::Plicease::Upload]
cpan = 1

[Prereqs / ConfigureRequires]
-phase = configure
File::Which = 0


57 changes: 57 additions & 0 deletions inc/mymm.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package mymm;

use strict;
use warnings;
use File::Which qw( which );

# Test::JSON::Diff shells out to jq and diff, and requires jq 1.7 or better
# since earlier versions do not preserve number literals. If they aren't
# available, bail out without writing a Makefile, which CPAN testers
# reports as N/A rather than FAIL.

sub unsupported
{
my($reason) = @_;
print "OS unsupported: $reason\n";
exit 0;
}

sub myWriteMakefile
{
my %args = @_;

my $jq = which('jq');
unsupported('Test::JSON::Diff requires jq 1.7 or better, which I am unable to find')
unless defined $jq;

my $version = do {
open my $fh, '-|', $jq, '--version' or unsupported("unable to run $jq --version: $!");
my $line = <$fh>;
close $fh;
$line = '' unless defined $line;
chomp $line;
$line;
};

if($version =~ /^jq-([0-9]+)\.([0-9]+)/)
{
my($major, $minor) = ($1, $2);
unsupported("Test::JSON::Diff requires jq 1.7 or better, found $version at $jq")
if $major < 1 || ($major == 1 && $minor < 7);
print "found $version at $jq\n";
}
else
{
unsupported("unable to determine version of $jq (got '$version')");
}

my $diff = which('diff');
unsupported('Test::JSON::Diff requires diff, which I am unable to find')
unless defined $diff;
print "found diff at $diff\n";

require ExtUtils::MakeMaker;
ExtUtils::MakeMaker::WriteMakefile(%args);
}

1;
Loading
Loading