1. Purpose

This page describes Canon’s JSON format support, including canonicalization with sorted keys, type preservation, and JSON-specific features.

2. Canonicalization

Canon provides JSON canonicalization with sorted keys at all nesting levels.

Key features:

  • Alphabetically sorted object keys

  • Consistent indentation (configurable)

  • Proper escape sequences

  • No trailing commas

  • Unicode normalization

Example 1. JSON canonicalization example
json = '{"z":3,"a":1,"nested":{"y":2,"x":1}}'

Canon.format(json, :json)
# => {"a":1,"nested":{"x":1,"y":2},"z":3}
# Keys sorted at all levels

3. Format defaults

Dimension Default Behavior

text_content

:strict

structural_whitespace

:strict

key_order

:strict

Default diff mode: :by_object (tree-based semantic diff)

4. Match profiles for JSON

Canon provides predefined profiles optimized for JSON documents. Each profile configures preprocessing, match options, diff algorithm, and formatting.

4.1. strict profile

Purpose: Character-perfect JSON matching

Configuration:

{
  preprocessing: :none,
  diff_algorithm: :dom,
  diff_mode: :by_object,     # Tree-based diff output (JSON default)
  match: {
    text_content: :strict,
    structural_whitespace: :strict,
    key_order: :strict
  }
}

Use when: Testing exact JSON serializer output, verifying JSON formatting compliance.

4.2. rendered profile

Purpose: Normalized JSON comparison

Configuration:

{
  preprocessing: :none,
  diff_algorithm: :dom,
  diff_mode: :by_object,
  match: {
    text_content: :normalize,
    structural_whitespace: :normalize,
    key_order: :ignore           # Allow unordered object keys
  }
}

Use when: Comparing JSON data where key order and whitespace don’t matter.

4.3. spec_friendly profile

Purpose: Test-friendly comparison for RSpec

Configuration:

{
  preprocessing: :normalize,
  diff_algorithm: :dom,
  diff_mode: :by_object,
  match: {
    text_content: :normalize,
    structural_whitespace: :ignore,
    key_order: :ignore
  }
}

Use when: Writing RSpec tests for JSON generation, testing semantic JSON correctness. Most common for JSON testing.

4.4. content_only profile

Purpose: Maximum tolerance - only values matter

Configuration:

{
  preprocessing: :normalize,
  diff_algorithm: :dom,
  diff_mode: :by_object,
  match: {
    text_content: :normalize,
    structural_whitespace: :ignore,
    key_order: :ignore
  }
}

Use when: Only JSON structure and values need to match, maximum flexibility for formatting and key order.

5. JSON-specific features

5.1. Key ordering

Object keys are sorted alphabetically for consistent comparison.

Example 2. Key ordering example
// Unordered input
{
  "name": "Alice",
  "age": 30,
  "city": "NYC"
}

// Canonicalized output (keys sorted)
{
  "age": 30,
  "city": "NYC",
  "name": "Alice"
}

This ensures that two JSON objects with the same data but different key order are compared correctly when key_order: :ignore is used.

5.2. Type preservation

Distinguishes between numbers, strings, booleans, and null.

Example 3. Type preservation example
{
  "string": "123",
  "number": 123,
  "boolean": true,
  "null": null,
  "float": 123.45
}

These values are treated as different types and won’t be considered equivalent: * "123" (string) ≠ 123 (number) * true (boolean) ≠ "true" (string) * null"null" (string)

5.3. Nested structures

Handles deeply nested objects and arrays.

Example 4. Nested structure example
{
  "users": [
    {
      "id": 1,
      "profile": {
        "name": "Alice",
        "settings": {
          "theme": "dark"
        }
      }
    }
  ]
}

Canon correctly handles arbitrarily nested JSON structures.

5.4. No comments

Standard JSON does not support comments.

While some JSON parsers allow comments, standard JSON (RFC 8259) does not support them. Canon follows the standard specification.

6. Usage examples

6.1. Basic JSON comparison

json1 = File.read("config1.json")
json2 = File.read("config2.json")

Canon::Comparison.equivalent?(json1, json2)

6.2. Ignoring key order

Canon::Comparison.equivalent?(json1, json2,
  match: { key_order: :ignore }
)

6.3. Test-friendly JSON comparison

expect(actual_json).to be_json_equivalent_to(expected_json)
  .with_profile(:spec_friendly)

6.4. Using JSON comparator directly

Canon::Comparison::JsonComparator.equivalent?(json1, json2,
  match: { key_order: :ignore }
)

6.5. CLI usage

# Basic comparison
canon diff config1.json config2.json --verbose

# Ignore key order
canon diff file1.json file2.json \
  --match-profile spec_friendly \
  --verbose

7. Common JSON comparison scenarios

7.1. API response comparison

# Compare API responses ignoring key order
Canon::Comparison.equivalent?(response1, response2,
  match: {
    key_order: :ignore,
    text_content: :normalize
  },
  verbose: true
)

7.2. Configuration file comparison

# Compare config files with flexible matching
Canon::Comparison.equivalent?(config1, config2,
  match_profile: :spec_friendly,
  verbose: true
)

7.3. Array order sensitivity

Example 5. Array order example
// File 1
{"items": [1, 2, 3]}

// File 2
{"items": [3, 2, 1]}

These are NOT equivalent because array order matters in JSON. Arrays are ordered sequences, unlike objects.

If you need unordered array comparison, you’ll need to sort the arrays before comparison or use custom logic.

8. JSON quirks and edge cases

8.1. Number precision

Example 6. Number precision
{"value": 1.0}
{"value": 1}

These may be treated as equivalent or different depending on the JSON parser. Canon preserves the distinction between integers and floats.

8.2. Empty vs missing

Example 7. Empty vs missing values
// Different: empty string vs missing key
{"name": ""}
{}

// Different: null vs missing key
{"name": null}
{}

Canon distinguishes between empty values and missing keys.

8.3. Unicode handling

Example 8. Unicode handling
// These are equivalent
{"text": "café"}
{"text": "caf\u00e9"}

Canon normalizes Unicode escapes during canonicalization.

9. See also