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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +163 -67
  3. data/README.adoc +400 -7
  4. data/docs/Gemfile +9 -0
  5. data/docs/INDEX.adoc +99 -182
  6. data/docs/_config.yml +100 -0
  7. data/docs/advanced/diff-classification.adoc +547 -0
  8. data/docs/advanced/diff-pipeline.adoc +358 -0
  9. data/docs/advanced/index.adoc +214 -0
  10. data/docs/advanced/semantic-diff-report.adoc +390 -0
  11. data/docs/{VERBOSE.adoc → advanced/verbose-mode-architecture.adoc} +51 -53
  12. data/docs/features/diff-formatting/algorithm-specific-output.adoc +533 -0
  13. data/docs/{CHARACTER_VISUALIZATION.adoc → features/diff-formatting/character-visualization.adoc} +23 -62
  14. data/docs/features/diff-formatting/colors-and-symbols.adoc +606 -0
  15. data/docs/features/diff-formatting/context-and-grouping.adoc +490 -0
  16. data/docs/features/diff-formatting/display-filtering.adoc +472 -0
  17. data/docs/features/diff-formatting/index.adoc +140 -0
  18. data/docs/features/environment-configuration/index.adoc +327 -0
  19. data/docs/features/environment-configuration/override-system.adoc +436 -0
  20. data/docs/features/environment-configuration/size-limits.adoc +273 -0
  21. data/docs/features/index.adoc +173 -0
  22. data/docs/features/input-validation/index.adoc +521 -0
  23. data/docs/features/match-options/algorithm-specific-behavior.adoc +365 -0
  24. data/docs/features/match-options/html-policies.adoc +312 -0
  25. data/docs/features/match-options/index.adoc +621 -0
  26. data/docs/getting-started/index.adoc +83 -0
  27. data/docs/getting-started/quick-start.adoc +76 -0
  28. data/docs/guides/choosing-configuration.adoc +689 -0
  29. data/docs/guides/index.adoc +181 -0
  30. data/docs/{CLI.adoc → interfaces/cli/index.adoc} +18 -13
  31. data/docs/interfaces/index.adoc +101 -0
  32. data/docs/{RSPEC.adoc → interfaces/rspec/index.adoc} +242 -31
  33. data/docs/{RUBY_API.adoc → interfaces/ruby-api/index.adoc} +118 -16
  34. data/docs/lychee.toml +65 -0
  35. data/docs/reference/cli-options.adoc +418 -0
  36. data/docs/reference/environment-variables.adoc +375 -0
  37. data/docs/reference/index.adoc +204 -0
  38. data/docs/reference/options-across-interfaces.adoc +417 -0
  39. data/docs/understanding/algorithms/dom-diff.adoc +389 -0
  40. data/docs/understanding/algorithms/index.adoc +314 -0
  41. data/docs/understanding/algorithms/semantic-tree-diff.adoc +533 -0
  42. data/docs/understanding/architecture.adoc +447 -0
  43. data/docs/understanding/comparison-pipeline.adoc +317 -0
  44. data/docs/understanding/formats/html.adoc +380 -0
  45. data/docs/understanding/formats/index.adoc +261 -0
  46. data/docs/understanding/formats/json.adoc +390 -0
  47. data/docs/understanding/formats/xml.adoc +366 -0
  48. data/docs/understanding/formats/yaml.adoc +504 -0
  49. data/docs/understanding/index.adoc +130 -0
  50. data/lib/canon/cli.rb +42 -1
  51. data/lib/canon/commands/diff_command.rb +108 -23
  52. data/lib/canon/comparison/compare_profile.rb +101 -0
  53. data/lib/canon/comparison/comparison_result.rb +41 -2
  54. data/lib/canon/comparison/html_comparator.rb +292 -71
  55. data/lib/canon/comparison/html_compare_profile.rb +117 -0
  56. data/lib/canon/comparison/match_options.rb +42 -4
  57. data/lib/canon/comparison/strategies/base_match_strategy.rb +99 -0
  58. data/lib/canon/comparison/strategies/match_strategy_factory.rb +74 -0
  59. data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +220 -0
  60. data/lib/canon/comparison/xml_comparator.rb +695 -91
  61. data/lib/canon/comparison.rb +207 -2
  62. data/lib/canon/config/env_provider.rb +71 -0
  63. data/lib/canon/config/env_schema.rb +58 -0
  64. data/lib/canon/config/override_resolver.rb +55 -0
  65. data/lib/canon/config/type_converter.rb +59 -0
  66. data/lib/canon/config.rb +158 -29
  67. data/lib/canon/data_model.rb +29 -0
  68. data/lib/canon/diff/diff_classifier.rb +74 -14
  69. data/lib/canon/diff/diff_context_builder.rb +41 -0
  70. data/lib/canon/diff/diff_line.rb +18 -2
  71. data/lib/canon/diff/diff_node.rb +18 -3
  72. data/lib/canon/diff/diff_node_mapper.rb +71 -12
  73. data/lib/canon/diff/formatting_detector.rb +53 -0
  74. data/lib/canon/diff_formatter/by_line/base_formatter.rb +60 -5
  75. data/lib/canon/diff_formatter/by_line/html_formatter.rb +68 -16
  76. data/lib/canon/diff_formatter/by_line/json_formatter.rb +0 -37
  77. data/lib/canon/diff_formatter/by_line/simple_formatter.rb +0 -42
  78. data/lib/canon/diff_formatter/by_line/xml_formatter.rb +116 -31
  79. data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +0 -37
  80. data/lib/canon/diff_formatter/by_object/base_formatter.rb +126 -19
  81. data/lib/canon/diff_formatter/by_object/xml_formatter.rb +30 -1
  82. data/lib/canon/diff_formatter/debug_output.rb +7 -1
  83. data/lib/canon/diff_formatter/diff_detail_formatter.rb +674 -57
  84. data/lib/canon/diff_formatter/legend.rb +42 -0
  85. data/lib/canon/diff_formatter.rb +78 -9
  86. data/lib/canon/errors.rb +56 -0
  87. data/lib/canon/formatters/html_formatter_base.rb +35 -1
  88. data/lib/canon/formatters/json_formatter.rb +3 -0
  89. data/lib/canon/formatters/yaml_formatter.rb +3 -0
  90. data/lib/canon/html/data_model.rb +229 -0
  91. data/lib/canon/html.rb +9 -0
  92. data/lib/canon/options/cli_generator.rb +70 -0
  93. data/lib/canon/options/registry.rb +234 -0
  94. data/lib/canon/rspec_matchers.rb +34 -13
  95. data/lib/canon/tree_diff/adapters/html_adapter.rb +316 -0
  96. data/lib/canon/tree_diff/adapters/json_adapter.rb +204 -0
  97. data/lib/canon/tree_diff/adapters/xml_adapter.rb +285 -0
  98. data/lib/canon/tree_diff/adapters/yaml_adapter.rb +213 -0
  99. data/lib/canon/tree_diff/core/attribute_comparator.rb +84 -0
  100. data/lib/canon/tree_diff/core/matching.rb +241 -0
  101. data/lib/canon/tree_diff/core/node_signature.rb +164 -0
  102. data/lib/canon/tree_diff/core/node_weight.rb +135 -0
  103. data/lib/canon/tree_diff/core/tree_node.rb +450 -0
  104. data/lib/canon/tree_diff/matchers/hash_matcher.rb +258 -0
  105. data/lib/canon/tree_diff/matchers/similarity_matcher.rb +168 -0
  106. data/lib/canon/tree_diff/matchers/structural_propagator.rb +242 -0
  107. data/lib/canon/tree_diff/matchers/universal_matcher.rb +220 -0
  108. data/lib/canon/tree_diff/operation_converter.rb +631 -0
  109. data/lib/canon/tree_diff/operations/operation.rb +92 -0
  110. data/lib/canon/tree_diff/operations/operation_detector.rb +626 -0
  111. data/lib/canon/tree_diff/tree_diff_integrator.rb +140 -0
  112. data/lib/canon/tree_diff.rb +33 -0
  113. data/lib/canon/validators/json_validator.rb +3 -1
  114. data/lib/canon/validators/yaml_validator.rb +3 -1
  115. data/lib/canon/version.rb +1 -1
  116. data/lib/canon/xml/data_model.rb +22 -23
  117. data/lib/canon/xml/element_matcher.rb +128 -20
  118. data/lib/canon/xml/namespace_helper.rb +110 -0
  119. data/lib/canon.rb +3 -0
  120. metadata +81 -23
  121. data/_config.yml +0 -116
  122. data/docs/ADVANCED_TOPICS.adoc +0 -20
  123. data/docs/BASIC_USAGE.adoc +0 -16
  124. data/docs/CUSTOMIZING_BEHAVIOR.adoc +0 -19
  125. data/docs/DIFF_ARCHITECTURE.adoc +0 -435
  126. data/docs/DIFF_FORMATTING.adoc +0 -540
  127. data/docs/FORMATS.adoc +0 -447
  128. data/docs/INPUT_VALIDATION.adoc +0 -477
  129. data/docs/MATCH_ARCHITECTURE.adoc +0 -463
  130. data/docs/MATCH_OPTIONS.adoc +0 -719
  131. data/docs/MODES.adoc +0 -432
  132. data/docs/NORMATIVE_INFORMATIVE_DIFFS.adoc +0 -219
  133. data/docs/OPTIONS.adoc +0 -1387
  134. data/docs/PREPROCESSING.adoc +0 -491
  135. data/docs/SEMANTIC_DIFF_REPORT.adoc +0 -528
  136. 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