canon 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +163 -67
- data/README.adoc +400 -7
- data/docs/Gemfile +9 -0
- data/docs/INDEX.adoc +99 -182
- data/docs/_config.yml +100 -0
- data/docs/advanced/diff-classification.adoc +547 -0
- data/docs/advanced/diff-pipeline.adoc +358 -0
- data/docs/advanced/index.adoc +214 -0
- data/docs/advanced/semantic-diff-report.adoc +390 -0
- data/docs/{VERBOSE.adoc → advanced/verbose-mode-architecture.adoc} +51 -53
- data/docs/features/diff-formatting/algorithm-specific-output.adoc +533 -0
- data/docs/{CHARACTER_VISUALIZATION.adoc → features/diff-formatting/character-visualization.adoc} +23 -62
- data/docs/features/diff-formatting/colors-and-symbols.adoc +606 -0
- data/docs/features/diff-formatting/context-and-grouping.adoc +490 -0
- data/docs/features/diff-formatting/display-filtering.adoc +472 -0
- data/docs/features/diff-formatting/index.adoc +140 -0
- data/docs/features/environment-configuration/index.adoc +327 -0
- data/docs/features/environment-configuration/override-system.adoc +436 -0
- data/docs/features/environment-configuration/size-limits.adoc +273 -0
- data/docs/features/index.adoc +173 -0
- data/docs/features/input-validation/index.adoc +521 -0
- data/docs/features/match-options/algorithm-specific-behavior.adoc +365 -0
- data/docs/features/match-options/html-policies.adoc +312 -0
- data/docs/features/match-options/index.adoc +621 -0
- data/docs/getting-started/index.adoc +83 -0
- data/docs/getting-started/quick-start.adoc +76 -0
- data/docs/guides/choosing-configuration.adoc +689 -0
- data/docs/guides/index.adoc +181 -0
- data/docs/{CLI.adoc → interfaces/cli/index.adoc} +18 -13
- data/docs/interfaces/index.adoc +101 -0
- data/docs/{RSPEC.adoc → interfaces/rspec/index.adoc} +242 -31
- data/docs/{RUBY_API.adoc → interfaces/ruby-api/index.adoc} +118 -16
- data/docs/lychee.toml +65 -0
- data/docs/reference/cli-options.adoc +418 -0
- data/docs/reference/environment-variables.adoc +375 -0
- data/docs/reference/index.adoc +204 -0
- data/docs/reference/options-across-interfaces.adoc +417 -0
- data/docs/understanding/algorithms/dom-diff.adoc +389 -0
- data/docs/understanding/algorithms/index.adoc +314 -0
- data/docs/understanding/algorithms/semantic-tree-diff.adoc +533 -0
- data/docs/understanding/architecture.adoc +447 -0
- data/docs/understanding/comparison-pipeline.adoc +317 -0
- data/docs/understanding/formats/html.adoc +380 -0
- data/docs/understanding/formats/index.adoc +261 -0
- data/docs/understanding/formats/json.adoc +390 -0
- data/docs/understanding/formats/xml.adoc +366 -0
- data/docs/understanding/formats/yaml.adoc +504 -0
- data/docs/understanding/index.adoc +130 -0
- data/lib/canon/cli.rb +42 -1
- data/lib/canon/commands/diff_command.rb +108 -23
- data/lib/canon/comparison/compare_profile.rb +101 -0
- data/lib/canon/comparison/comparison_result.rb +41 -2
- data/lib/canon/comparison/html_comparator.rb +292 -71
- data/lib/canon/comparison/html_compare_profile.rb +117 -0
- data/lib/canon/comparison/match_options.rb +42 -4
- data/lib/canon/comparison/strategies/base_match_strategy.rb +99 -0
- data/lib/canon/comparison/strategies/match_strategy_factory.rb +74 -0
- data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +220 -0
- data/lib/canon/comparison/xml_comparator.rb +695 -91
- data/lib/canon/comparison.rb +207 -2
- data/lib/canon/config/env_provider.rb +71 -0
- data/lib/canon/config/env_schema.rb +58 -0
- data/lib/canon/config/override_resolver.rb +55 -0
- data/lib/canon/config/type_converter.rb +59 -0
- data/lib/canon/config.rb +158 -29
- data/lib/canon/data_model.rb +29 -0
- data/lib/canon/diff/diff_classifier.rb +74 -14
- data/lib/canon/diff/diff_context_builder.rb +41 -0
- data/lib/canon/diff/diff_line.rb +18 -2
- data/lib/canon/diff/diff_node.rb +18 -3
- data/lib/canon/diff/diff_node_mapper.rb +71 -12
- data/lib/canon/diff/formatting_detector.rb +53 -0
- data/lib/canon/diff_formatter/by_line/base_formatter.rb +60 -5
- data/lib/canon/diff_formatter/by_line/html_formatter.rb +68 -16
- data/lib/canon/diff_formatter/by_line/json_formatter.rb +0 -37
- data/lib/canon/diff_formatter/by_line/simple_formatter.rb +0 -42
- data/lib/canon/diff_formatter/by_line/xml_formatter.rb +116 -31
- data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +0 -37
- data/lib/canon/diff_formatter/by_object/base_formatter.rb +126 -19
- data/lib/canon/diff_formatter/by_object/xml_formatter.rb +30 -1
- data/lib/canon/diff_formatter/debug_output.rb +7 -1
- data/lib/canon/diff_formatter/diff_detail_formatter.rb +674 -57
- data/lib/canon/diff_formatter/legend.rb +42 -0
- data/lib/canon/diff_formatter.rb +78 -9
- data/lib/canon/errors.rb +56 -0
- data/lib/canon/formatters/html_formatter_base.rb +35 -1
- data/lib/canon/formatters/json_formatter.rb +3 -0
- data/lib/canon/formatters/yaml_formatter.rb +3 -0
- data/lib/canon/html/data_model.rb +229 -0
- data/lib/canon/html.rb +9 -0
- data/lib/canon/options/cli_generator.rb +70 -0
- data/lib/canon/options/registry.rb +234 -0
- data/lib/canon/rspec_matchers.rb +34 -13
- data/lib/canon/tree_diff/adapters/html_adapter.rb +316 -0
- data/lib/canon/tree_diff/adapters/json_adapter.rb +204 -0
- data/lib/canon/tree_diff/adapters/xml_adapter.rb +285 -0
- data/lib/canon/tree_diff/adapters/yaml_adapter.rb +213 -0
- data/lib/canon/tree_diff/core/attribute_comparator.rb +84 -0
- data/lib/canon/tree_diff/core/matching.rb +241 -0
- data/lib/canon/tree_diff/core/node_signature.rb +164 -0
- data/lib/canon/tree_diff/core/node_weight.rb +135 -0
- data/lib/canon/tree_diff/core/tree_node.rb +450 -0
- data/lib/canon/tree_diff/matchers/hash_matcher.rb +258 -0
- data/lib/canon/tree_diff/matchers/similarity_matcher.rb +168 -0
- data/lib/canon/tree_diff/matchers/structural_propagator.rb +242 -0
- data/lib/canon/tree_diff/matchers/universal_matcher.rb +220 -0
- data/lib/canon/tree_diff/operation_converter.rb +631 -0
- data/lib/canon/tree_diff/operations/operation.rb +92 -0
- data/lib/canon/tree_diff/operations/operation_detector.rb +626 -0
- data/lib/canon/tree_diff/tree_diff_integrator.rb +140 -0
- data/lib/canon/tree_diff.rb +33 -0
- data/lib/canon/validators/json_validator.rb +3 -1
- data/lib/canon/validators/yaml_validator.rb +3 -1
- data/lib/canon/version.rb +1 -1
- data/lib/canon/xml/data_model.rb +22 -23
- data/lib/canon/xml/element_matcher.rb +128 -20
- data/lib/canon/xml/namespace_helper.rb +110 -0
- data/lib/canon.rb +3 -0
- metadata +81 -23
- data/_config.yml +0 -116
- data/docs/ADVANCED_TOPICS.adoc +0 -20
- data/docs/BASIC_USAGE.adoc +0 -16
- data/docs/CUSTOMIZING_BEHAVIOR.adoc +0 -19
- data/docs/DIFF_ARCHITECTURE.adoc +0 -435
- data/docs/DIFF_FORMATTING.adoc +0 -540
- data/docs/FORMATS.adoc +0 -447
- data/docs/INPUT_VALIDATION.adoc +0 -477
- data/docs/MATCH_ARCHITECTURE.adoc +0 -463
- data/docs/MATCH_OPTIONS.adoc +0 -719
- data/docs/MODES.adoc +0 -432
- data/docs/NORMATIVE_INFORMATIVE_DIFFS.adoc +0 -219
- data/docs/OPTIONS.adoc +0 -1387
- data/docs/PREPROCESSING.adoc +0 -491
- data/docs/SEMANTIC_DIFF_REPORT.adoc +0 -528
- data/docs/UNDERSTANDING_CANON.adoc +0 -17
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Format Support
|
|
3
|
+
parent: Understanding
|
|
4
|
+
nav_order: 3
|
|
5
|
+
has_children: true
|
|
6
|
+
---
|
|
7
|
+
= Format support
|
|
8
|
+
:toc:
|
|
9
|
+
:toclevels: 3
|
|
10
|
+
|
|
11
|
+
== Purpose
|
|
12
|
+
|
|
13
|
+
This section describes Canon's support for XML, HTML, JSON, and YAML formats, including canonicalization rules, format detection, and format-specific features.
|
|
14
|
+
|
|
15
|
+
For usage examples, see link:../../interfaces/ruby-api/[Ruby API], link:../../interfaces/cli/[CLI], or link:../../interfaces/rspec/[RSpec documentation].
|
|
16
|
+
|
|
17
|
+
== Overview
|
|
18
|
+
|
|
19
|
+
Canon provides unified canonicalization and comparison for four serialization formats. Each format has specific rules and defaults optimized for its typical usage.
|
|
20
|
+
|
|
21
|
+
This page provides an overview of format support. See the child pages for format-specific details:
|
|
22
|
+
|
|
23
|
+
* link:xml.adoc[XML Format] - W3C C14N, namespace handling
|
|
24
|
+
* link:html.adoc[HTML Format] - HTML4/5 detection, rendering behavior
|
|
25
|
+
* link:json.adoc[JSON Format] - Sorted keys, type preservation
|
|
26
|
+
* link:yaml.adoc[YAML Format] - YAML specifics, anchors and aliases
|
|
27
|
+
|
|
28
|
+
== Format detection
|
|
29
|
+
|
|
30
|
+
Canon automatically detects format based on file extensions:
|
|
31
|
+
|
|
32
|
+
[cols="1,1"]
|
|
33
|
+
|===
|
|
34
|
+
|Extension |Format
|
|
35
|
+
|
|
36
|
+
|`.xml`
|
|
37
|
+
|XML
|
|
38
|
+
|
|
39
|
+
|`.html`, `.htm`
|
|
40
|
+
|HTML
|
|
41
|
+
|
|
42
|
+
|`.json`
|
|
43
|
+
|JSON
|
|
44
|
+
|
|
45
|
+
|`.yaml`, `.yml`
|
|
46
|
+
|YAML
|
|
47
|
+
|===
|
|
48
|
+
|
|
49
|
+
You can override auto-detection by explicitly specifying the format:
|
|
50
|
+
|
|
51
|
+
.Explicit format specification
|
|
52
|
+
[example]
|
|
53
|
+
====
|
|
54
|
+
[source,ruby]
|
|
55
|
+
----
|
|
56
|
+
# Ruby API
|
|
57
|
+
Canon.format(content, :xml)
|
|
58
|
+
|
|
59
|
+
# CLI
|
|
60
|
+
$ canon format file.txt --format xml
|
|
61
|
+
|
|
62
|
+
# Comparison
|
|
63
|
+
Canon::Comparison.equivalent?(doc1, doc2, format: :xml)
|
|
64
|
+
----
|
|
65
|
+
====
|
|
66
|
+
|
|
67
|
+
== Format comparison matrix
|
|
68
|
+
|
|
69
|
+
[cols="1,1,1,1,1"]
|
|
70
|
+
|===
|
|
71
|
+
|Feature |XML |HTML |JSON |YAML
|
|
72
|
+
|
|
73
|
+
|Canonicalization standard
|
|
74
|
+
|W3C C14N 1.1
|
|
75
|
+
|Custom
|
|
76
|
+
|Custom
|
|
77
|
+
|YAML 1.2
|
|
78
|
+
|
|
79
|
+
|Comment support
|
|
80
|
+
|Yes
|
|
81
|
+
|Yes
|
|
82
|
+
|No
|
|
83
|
+
|Yes
|
|
84
|
+
|
|
85
|
+
|Attribute/key ordering
|
|
86
|
+
|Ignored default
|
|
87
|
+
|Ignored default
|
|
88
|
+
|Strict default
|
|
89
|
+
|Strict default
|
|
90
|
+
|
|
91
|
+
|Default diff mode
|
|
92
|
+
|by_object
|
|
93
|
+
|by_line
|
|
94
|
+
|by_object
|
|
95
|
+
|by_object
|
|
96
|
+
|
|
97
|
+
|Whitespace handling
|
|
98
|
+
|Strict default
|
|
99
|
+
|Normalized default
|
|
100
|
+
|Strict default
|
|
101
|
+
|Strict default
|
|
102
|
+
|
|
103
|
+
|Namespace support
|
|
104
|
+
|Yes
|
|
105
|
+
|Limited (XHTML)
|
|
106
|
+
|No
|
|
107
|
+
|No
|
|
108
|
+
|===
|
|
109
|
+
|
|
110
|
+
== Format defaults summary
|
|
111
|
+
|
|
112
|
+
Each format has sensible defaults based on typical usage:
|
|
113
|
+
|
|
114
|
+
[cols="1,1,1,1,1"]
|
|
115
|
+
|===
|
|
116
|
+
|Dimension |XML |HTML |JSON |YAML
|
|
117
|
+
|
|
118
|
+
|`text_content`
|
|
119
|
+
|`:strict`
|
|
120
|
+
|`:normalize`
|
|
121
|
+
|`:strict`
|
|
122
|
+
|`:strict`
|
|
123
|
+
|
|
124
|
+
|`structural_whitespace`
|
|
125
|
+
|`:strict`
|
|
126
|
+
|`:normalize`
|
|
127
|
+
|`:strict`
|
|
128
|
+
|`:strict`
|
|
129
|
+
|
|
130
|
+
|`attribute_whitespace`
|
|
131
|
+
|`:strict`
|
|
132
|
+
|`:normalize`
|
|
133
|
+
|—
|
|
134
|
+
|—
|
|
135
|
+
|
|
136
|
+
|`attribute_order`
|
|
137
|
+
|`:ignore`
|
|
138
|
+
|`:ignore`
|
|
139
|
+
|—
|
|
140
|
+
|—
|
|
141
|
+
|
|
142
|
+
|`attribute_values`
|
|
143
|
+
|`:strict`
|
|
144
|
+
|`:strict`
|
|
145
|
+
|—
|
|
146
|
+
|—
|
|
147
|
+
|
|
148
|
+
|`key_order`
|
|
149
|
+
|—
|
|
150
|
+
|—
|
|
151
|
+
|`:strict`
|
|
152
|
+
|`:strict`
|
|
153
|
+
|
|
154
|
+
|`comments`
|
|
155
|
+
|`:strict`
|
|
156
|
+
|`:ignore`
|
|
157
|
+
|—
|
|
158
|
+
|`:strict`
|
|
159
|
+
|===
|
|
160
|
+
|
|
161
|
+
Default diff mode:
|
|
162
|
+
* XML: `:by_object` (tree-based semantic diff)
|
|
163
|
+
* HTML: `:by_line` (line-based diff)
|
|
164
|
+
* JSON: `:by_object` (tree-based semantic diff)
|
|
165
|
+
* YAML: `:by_object` (tree-based semantic diff)
|
|
166
|
+
|
|
167
|
+
== Match profiles by format
|
|
168
|
+
|
|
169
|
+
Canon provides predefined profiles optimized for each format. The same profile name has format-appropriate settings.
|
|
170
|
+
|
|
171
|
+
=== strict profile
|
|
172
|
+
|
|
173
|
+
**Purpose**: Character-perfect matching
|
|
174
|
+
|
|
175
|
+
**Use when**: Testing exact serializer output, verifying formatting compliance, character-perfect matching required.
|
|
176
|
+
|
|
177
|
+
=== rendered profile
|
|
178
|
+
|
|
179
|
+
**Purpose**: Browser-rendered equivalence (most relevant for HTML)
|
|
180
|
+
|
|
181
|
+
**Use when**: Comparing how content would render, ignoring formatting that doesn't affect display.
|
|
182
|
+
|
|
183
|
+
=== spec_friendly profile
|
|
184
|
+
|
|
185
|
+
**Purpose**: Test-friendly comparison for RSpec
|
|
186
|
+
|
|
187
|
+
**Use when**: Writing RSpec tests, testing semantic correctness, ignoring pretty-printing differences. Most common for testing.
|
|
188
|
+
|
|
189
|
+
=== content_only profile
|
|
190
|
+
|
|
191
|
+
**Purpose**: Maximum tolerance - only data matters
|
|
192
|
+
|
|
193
|
+
**Use when**: Only structural equivalence needed, maximum flexibility for formatting differences.
|
|
194
|
+
|
|
195
|
+
== Working with multiple formats
|
|
196
|
+
|
|
197
|
+
Canon's unified API works consistently across all formats:
|
|
198
|
+
|
|
199
|
+
.Unified API examples
|
|
200
|
+
[example]
|
|
201
|
+
====
|
|
202
|
+
[source,ruby]
|
|
203
|
+
----
|
|
204
|
+
# Format any content
|
|
205
|
+
Canon.format(xml_content, :xml)
|
|
206
|
+
Canon.format(html_content, :html)
|
|
207
|
+
Canon.format(json_content, :json)
|
|
208
|
+
Canon.format(yaml_content, :yaml)
|
|
209
|
+
|
|
210
|
+
# Compare any format
|
|
211
|
+
Canon::Comparison.equivalent?(xml1, xml2)
|
|
212
|
+
Canon::Comparison.equivalent?(html1, html2)
|
|
213
|
+
Canon::Comparison.equivalent?(json1, json2)
|
|
214
|
+
Canon::Comparison.equivalent?(yaml1, yaml2)
|
|
215
|
+
|
|
216
|
+
# RSpec matchers
|
|
217
|
+
expect(actual_xml).to be_xml_equivalent_to(expected_xml)
|
|
218
|
+
expect(actual_html).to be_html_equivalent_to(expected_html)
|
|
219
|
+
expect(actual_json).to be_json_equivalent_to(expected_json)
|
|
220
|
+
expect(actual_yaml).to be_yaml_equivalent_to(expected_yaml)
|
|
221
|
+
----
|
|
222
|
+
====
|
|
223
|
+
|
|
224
|
+
== Format-specific comparators
|
|
225
|
+
|
|
226
|
+
You can use format-specific comparator classes directly:
|
|
227
|
+
|
|
228
|
+
.Format-specific comparators
|
|
229
|
+
[example]
|
|
230
|
+
====
|
|
231
|
+
[source,ruby]
|
|
232
|
+
----
|
|
233
|
+
# XML comparator
|
|
234
|
+
Canon::Comparison::XmlComparator.equivalent?(xml1, xml2,
|
|
235
|
+
match: { attribute_order: :ignore }
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# HTML comparator
|
|
239
|
+
Canon::Comparison::HtmlComparator.equivalent?(html1, html2,
|
|
240
|
+
match_profile: :rendered
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# JSON comparator
|
|
244
|
+
Canon::Comparison::JsonComparator.equivalent?(json1, json2,
|
|
245
|
+
match: { key_order: :ignore }
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# YAML comparator
|
|
249
|
+
Canon::Comparison::YamlComparator.equivalent?(yaml1, yaml2,
|
|
250
|
+
match: { comments: :ignore }
|
|
251
|
+
)
|
|
252
|
+
----
|
|
253
|
+
====
|
|
254
|
+
|
|
255
|
+
== See also
|
|
256
|
+
|
|
257
|
+
* link:../../interfaces/ruby-api/[Ruby API documentation]
|
|
258
|
+
* link:../../interfaces/cli/[Command-line interface]
|
|
259
|
+
* link:../../features/match-options/[Match options reference]
|
|
260
|
+
* link:../comparison-pipeline.adoc[Comparison Pipeline]
|
|
261
|
+
* link:../../features/preprocessing/[Preprocessing options]
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: JSON Format
|
|
3
|
+
parent: Format Support
|
|
4
|
+
grand_parent: Understanding
|
|
5
|
+
nav_order: 3
|
|
6
|
+
---
|
|
7
|
+
= JSON format
|
|
8
|
+
:toc:
|
|
9
|
+
:toclevels: 3
|
|
10
|
+
|
|
11
|
+
== Purpose
|
|
12
|
+
|
|
13
|
+
This page describes Canon's JSON format support, including canonicalization with sorted keys, type preservation, and JSON-specific features.
|
|
14
|
+
|
|
15
|
+
== Canonicalization
|
|
16
|
+
|
|
17
|
+
Canon provides JSON canonicalization with sorted keys at all nesting levels.
|
|
18
|
+
|
|
19
|
+
**Key features:**
|
|
20
|
+
|
|
21
|
+
* Alphabetically sorted object keys
|
|
22
|
+
* Consistent indentation (configurable)
|
|
23
|
+
* Proper escape sequences
|
|
24
|
+
* No trailing commas
|
|
25
|
+
* Unicode normalization
|
|
26
|
+
|
|
27
|
+
.JSON canonicalization example
|
|
28
|
+
[example]
|
|
29
|
+
====
|
|
30
|
+
[source,ruby]
|
|
31
|
+
----
|
|
32
|
+
json = '{"z":3,"a":1,"nested":{"y":2,"x":1}}'
|
|
33
|
+
|
|
34
|
+
Canon.format(json, :json)
|
|
35
|
+
# => {"a":1,"nested":{"x":1,"y":2},"z":3}
|
|
36
|
+
# Keys sorted at all levels
|
|
37
|
+
----
|
|
38
|
+
====
|
|
39
|
+
|
|
40
|
+
== Format defaults
|
|
41
|
+
|
|
42
|
+
[cols="1,1"]
|
|
43
|
+
|===
|
|
44
|
+
|Dimension |Default Behavior
|
|
45
|
+
|
|
46
|
+
|`text_content`
|
|
47
|
+
|`:strict`
|
|
48
|
+
|
|
49
|
+
|`structural_whitespace`
|
|
50
|
+
|`:strict`
|
|
51
|
+
|
|
52
|
+
|`key_order`
|
|
53
|
+
|`:strict`
|
|
54
|
+
|===
|
|
55
|
+
|
|
56
|
+
Default diff mode: `:by_object` (tree-based semantic diff)
|
|
57
|
+
|
|
58
|
+
== Match profiles for JSON
|
|
59
|
+
|
|
60
|
+
Canon provides predefined profiles optimized for JSON documents. Each profile configures preprocessing, match options, diff algorithm, and formatting.
|
|
61
|
+
|
|
62
|
+
=== strict profile
|
|
63
|
+
|
|
64
|
+
**Purpose**: Character-perfect JSON matching
|
|
65
|
+
|
|
66
|
+
**Configuration**:
|
|
67
|
+
|
|
68
|
+
[source,ruby]
|
|
69
|
+
----
|
|
70
|
+
{
|
|
71
|
+
preprocessing: :none,
|
|
72
|
+
diff_algorithm: :dom,
|
|
73
|
+
diff_mode: :by_object, # Tree-based diff output (JSON default)
|
|
74
|
+
match: {
|
|
75
|
+
text_content: :strict,
|
|
76
|
+
structural_whitespace: :strict,
|
|
77
|
+
key_order: :strict
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
----
|
|
81
|
+
|
|
82
|
+
**Use when**: Testing exact JSON serializer output, verifying JSON formatting compliance.
|
|
83
|
+
|
|
84
|
+
=== rendered profile
|
|
85
|
+
|
|
86
|
+
**Purpose**: Normalized JSON comparison
|
|
87
|
+
|
|
88
|
+
**Configuration**:
|
|
89
|
+
|
|
90
|
+
[source,ruby]
|
|
91
|
+
----
|
|
92
|
+
{
|
|
93
|
+
preprocessing: :none,
|
|
94
|
+
diff_algorithm: :dom,
|
|
95
|
+
diff_mode: :by_object,
|
|
96
|
+
match: {
|
|
97
|
+
text_content: :normalize,
|
|
98
|
+
structural_whitespace: :normalize,
|
|
99
|
+
key_order: :ignore # Allow unordered object keys
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
----
|
|
103
|
+
|
|
104
|
+
**Use when**: Comparing JSON data where key order and whitespace don't matter.
|
|
105
|
+
|
|
106
|
+
=== spec_friendly profile
|
|
107
|
+
|
|
108
|
+
**Purpose**: Test-friendly comparison for RSpec
|
|
109
|
+
|
|
110
|
+
**Configuration**:
|
|
111
|
+
|
|
112
|
+
[source,ruby]
|
|
113
|
+
----
|
|
114
|
+
{
|
|
115
|
+
preprocessing: :normalize,
|
|
116
|
+
diff_algorithm: :dom,
|
|
117
|
+
diff_mode: :by_object,
|
|
118
|
+
match: {
|
|
119
|
+
text_content: :normalize,
|
|
120
|
+
structural_whitespace: :ignore,
|
|
121
|
+
key_order: :ignore
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
----
|
|
125
|
+
|
|
126
|
+
**Use when**: Writing RSpec tests for JSON generation, testing semantic JSON correctness. Most common for JSON testing.
|
|
127
|
+
|
|
128
|
+
=== content_only profile
|
|
129
|
+
|
|
130
|
+
**Purpose**: Maximum tolerance - only values matter
|
|
131
|
+
|
|
132
|
+
**Configuration**:
|
|
133
|
+
|
|
134
|
+
[source,ruby]
|
|
135
|
+
----
|
|
136
|
+
{
|
|
137
|
+
preprocessing: :normalize,
|
|
138
|
+
diff_algorithm: :dom,
|
|
139
|
+
diff_mode: :by_object,
|
|
140
|
+
match: {
|
|
141
|
+
text_content: :normalize,
|
|
142
|
+
structural_whitespace: :ignore,
|
|
143
|
+
key_order: :ignore
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
----
|
|
147
|
+
|
|
148
|
+
**Use when**: Only JSON structure and values need to match, maximum flexibility for formatting and key order.
|
|
149
|
+
|
|
150
|
+
== JSON-specific features
|
|
151
|
+
|
|
152
|
+
=== Key ordering
|
|
153
|
+
|
|
154
|
+
Object keys are sorted alphabetically for consistent comparison.
|
|
155
|
+
|
|
156
|
+
.Key ordering example
|
|
157
|
+
[example]
|
|
158
|
+
====
|
|
159
|
+
[source,json]
|
|
160
|
+
----
|
|
161
|
+
// Unordered input
|
|
162
|
+
{
|
|
163
|
+
"name": "Alice",
|
|
164
|
+
"age": 30,
|
|
165
|
+
"city": "NYC"
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Canonicalized output (keys sorted)
|
|
169
|
+
{
|
|
170
|
+
"age": 30,
|
|
171
|
+
"city": "NYC",
|
|
172
|
+
"name": "Alice"
|
|
173
|
+
}
|
|
174
|
+
----
|
|
175
|
+
|
|
176
|
+
This ensures that two JSON objects with the same data but different key order are compared correctly when `key_order: :ignore` is used.
|
|
177
|
+
====
|
|
178
|
+
|
|
179
|
+
=== Type preservation
|
|
180
|
+
|
|
181
|
+
Distinguishes between numbers, strings, booleans, and null.
|
|
182
|
+
|
|
183
|
+
.Type preservation example
|
|
184
|
+
[example]
|
|
185
|
+
====
|
|
186
|
+
[source,json]
|
|
187
|
+
----
|
|
188
|
+
{
|
|
189
|
+
"string": "123",
|
|
190
|
+
"number": 123,
|
|
191
|
+
"boolean": true,
|
|
192
|
+
"null": null,
|
|
193
|
+
"float": 123.45
|
|
194
|
+
}
|
|
195
|
+
----
|
|
196
|
+
|
|
197
|
+
These values are treated as different types and won't be considered equivalent:
|
|
198
|
+
* `"123"` (string) ≠ `123` (number)
|
|
199
|
+
* `true` (boolean) ≠ `"true"` (string)
|
|
200
|
+
* `null` ≠ `"null"` (string)
|
|
201
|
+
====
|
|
202
|
+
|
|
203
|
+
=== Nested structures
|
|
204
|
+
|
|
205
|
+
Handles deeply nested objects and arrays.
|
|
206
|
+
|
|
207
|
+
.Nested structure example
|
|
208
|
+
[example]
|
|
209
|
+
====
|
|
210
|
+
[source,json]
|
|
211
|
+
----
|
|
212
|
+
{
|
|
213
|
+
"users": [
|
|
214
|
+
{
|
|
215
|
+
"id": 1,
|
|
216
|
+
"profile": {
|
|
217
|
+
"name": "Alice",
|
|
218
|
+
"settings": {
|
|
219
|
+
"theme": "dark"
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
----
|
|
226
|
+
|
|
227
|
+
Canon correctly handles arbitrarily nested JSON structures.
|
|
228
|
+
====
|
|
229
|
+
|
|
230
|
+
=== No comments
|
|
231
|
+
|
|
232
|
+
Standard JSON does not support comments.
|
|
233
|
+
|
|
234
|
+
NOTE: While some JSON parsers allow comments, standard JSON (RFC 8259) does not support them. Canon follows the standard specification.
|
|
235
|
+
|
|
236
|
+
== Usage examples
|
|
237
|
+
|
|
238
|
+
=== Basic JSON comparison
|
|
239
|
+
|
|
240
|
+
[source,ruby]
|
|
241
|
+
----
|
|
242
|
+
json1 = File.read("config1.json")
|
|
243
|
+
json2 = File.read("config2.json")
|
|
244
|
+
|
|
245
|
+
Canon::Comparison.equivalent?(json1, json2)
|
|
246
|
+
----
|
|
247
|
+
|
|
248
|
+
=== Ignoring key order
|
|
249
|
+
|
|
250
|
+
[source,ruby]
|
|
251
|
+
----
|
|
252
|
+
Canon::Comparison.equivalent?(json1, json2,
|
|
253
|
+
match: { key_order: :ignore }
|
|
254
|
+
)
|
|
255
|
+
----
|
|
256
|
+
|
|
257
|
+
=== Test-friendly JSON comparison
|
|
258
|
+
|
|
259
|
+
[source,ruby]
|
|
260
|
+
----
|
|
261
|
+
expect(actual_json).to be_json_equivalent_to(expected_json)
|
|
262
|
+
.with_profile(:spec_friendly)
|
|
263
|
+
----
|
|
264
|
+
|
|
265
|
+
=== Using JSON comparator directly
|
|
266
|
+
|
|
267
|
+
[source,ruby]
|
|
268
|
+
----
|
|
269
|
+
Canon::Comparison::JsonComparator.equivalent?(json1, json2,
|
|
270
|
+
match: { key_order: :ignore }
|
|
271
|
+
)
|
|
272
|
+
----
|
|
273
|
+
|
|
274
|
+
=== CLI usage
|
|
275
|
+
|
|
276
|
+
[source,bash]
|
|
277
|
+
----
|
|
278
|
+
# Basic comparison
|
|
279
|
+
canon diff config1.json config2.json --verbose
|
|
280
|
+
|
|
281
|
+
# Ignore key order
|
|
282
|
+
canon diff file1.json file2.json \
|
|
283
|
+
--match-profile spec_friendly \
|
|
284
|
+
--verbose
|
|
285
|
+
----
|
|
286
|
+
|
|
287
|
+
== Common JSON comparison scenarios
|
|
288
|
+
|
|
289
|
+
=== API response comparison
|
|
290
|
+
|
|
291
|
+
[source,ruby]
|
|
292
|
+
----
|
|
293
|
+
# Compare API responses ignoring key order
|
|
294
|
+
Canon::Comparison.equivalent?(response1, response2,
|
|
295
|
+
match: {
|
|
296
|
+
key_order: :ignore,
|
|
297
|
+
text_content: :normalize
|
|
298
|
+
},
|
|
299
|
+
verbose: true
|
|
300
|
+
)
|
|
301
|
+
----
|
|
302
|
+
|
|
303
|
+
=== Configuration file comparison
|
|
304
|
+
|
|
305
|
+
[source,ruby]
|
|
306
|
+
----
|
|
307
|
+
# Compare config files with flexible matching
|
|
308
|
+
Canon::Comparison.equivalent?(config1, config2,
|
|
309
|
+
match_profile: :spec_friendly,
|
|
310
|
+
verbose: true
|
|
311
|
+
)
|
|
312
|
+
----
|
|
313
|
+
|
|
314
|
+
=== Array order sensitivity
|
|
315
|
+
|
|
316
|
+
.Array order example
|
|
317
|
+
[example]
|
|
318
|
+
====
|
|
319
|
+
[source,json]
|
|
320
|
+
----
|
|
321
|
+
// File 1
|
|
322
|
+
{"items": [1, 2, 3]}
|
|
323
|
+
|
|
324
|
+
// File 2
|
|
325
|
+
{"items": [3, 2, 1]}
|
|
326
|
+
----
|
|
327
|
+
|
|
328
|
+
These are **NOT** equivalent because array order matters in JSON. Arrays are ordered sequences, unlike objects.
|
|
329
|
+
|
|
330
|
+
If you need unordered array comparison, you'll need to sort the arrays before comparison or use custom logic.
|
|
331
|
+
====
|
|
332
|
+
|
|
333
|
+
== JSON quirks and edge cases
|
|
334
|
+
|
|
335
|
+
=== Number precision
|
|
336
|
+
|
|
337
|
+
.Number precision
|
|
338
|
+
[example]
|
|
339
|
+
====
|
|
340
|
+
[source,json]
|
|
341
|
+
----
|
|
342
|
+
{"value": 1.0}
|
|
343
|
+
{"value": 1}
|
|
344
|
+
----
|
|
345
|
+
|
|
346
|
+
These may be treated as equivalent or different depending on the JSON parser. Canon preserves the distinction between integers and floats.
|
|
347
|
+
====
|
|
348
|
+
|
|
349
|
+
=== Empty vs missing
|
|
350
|
+
|
|
351
|
+
.Empty vs missing values
|
|
352
|
+
[example]
|
|
353
|
+
====
|
|
354
|
+
[source,json]
|
|
355
|
+
----
|
|
356
|
+
// Different: empty string vs missing key
|
|
357
|
+
{"name": ""}
|
|
358
|
+
{}
|
|
359
|
+
|
|
360
|
+
// Different: null vs missing key
|
|
361
|
+
{"name": null}
|
|
362
|
+
{}
|
|
363
|
+
----
|
|
364
|
+
|
|
365
|
+
Canon distinguishes between empty values and missing keys.
|
|
366
|
+
====
|
|
367
|
+
|
|
368
|
+
=== Unicode handling
|
|
369
|
+
|
|
370
|
+
.Unicode handling
|
|
371
|
+
[example]
|
|
372
|
+
====
|
|
373
|
+
[source,json]
|
|
374
|
+
----
|
|
375
|
+
// These are equivalent
|
|
376
|
+
{"text": "café"}
|
|
377
|
+
{"text": "caf\u00e9"}
|
|
378
|
+
----
|
|
379
|
+
|
|
380
|
+
Canon normalizes Unicode escapes during canonicalization.
|
|
381
|
+
====
|
|
382
|
+
|
|
383
|
+
== See also
|
|
384
|
+
|
|
385
|
+
* link:../comparison-pipeline.adoc[Comparison Pipeline] - Understanding the 4 layers
|
|
386
|
+
* link:../../features/match-options/[Match Options] - All matching options
|
|
387
|
+
* link:../../guides/choosing-configuration.adoc[Choosing Configuration] - Decision guide
|
|
388
|
+
* link:index.adoc[Format Support] - Overview of all formats
|
|
389
|
+
* link:yaml.adoc[YAML Format] - YAML-specific features (similar to JSON)
|
|
390
|
+
* link:xml.adoc[XML Format] - XML-specific features
|