json-merge 1.1.2 → 7.0.0

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.
data/README.md DELETED
@@ -1,1036 +0,0 @@
1
- [![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0][🖼️galtzo-i]][🖼️galtzo-discord] [![ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5][🖼️ruby-lang-i]][🖼️ruby-lang] [![kettle-rb Logo by Aboling0, CC BY-SA 4.0][🖼️kettle-rb-i]][🖼️kettle-rb]
2
-
3
- [🖼️galtzo-i]: https://logos.galtzo.com/assets/images/galtzo-floss/avatar-192px.svg
4
- [🖼️galtzo-discord]: https://discord.gg/3qme4XHNKN
5
- [🖼️ruby-lang-i]: https://logos.galtzo.com/assets/images/ruby-lang/avatar-192px.svg
6
- [🖼️ruby-lang]: https://www.ruby-lang.org/
7
- [🖼️kettle-rb-i]: https://logos.galtzo.com/assets/images/kettle-rb/avatar-192px.svg
8
- [🖼️kettle-rb]: https://github.com/kettle-rb
9
-
10
- # ☯️ Json::Merge
11
-
12
- [![Version][👽versioni]][👽dl-rank] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![CodeCov Test Coverage][🏀codecovi]][🏀codecov] [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] [![QLTY Maintainability][🏀qlty-mnti]][🏀qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Apache SkyWalking Eyes License Compatibility Check][🚎15-🪪-wfi]][🚎15-🪪-wf]
13
-
14
- `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know][🖼️galtzo-discord], as I may have missed the [discord notification][🖼️galtzo-discord].
15
-
16
- -----
17
-
18
- `if ci_badges.map(&:color).all? { it == "green"}` 👇️ send money so I can do more of this. FLOSS maintenance is now my full-time job.
19
-
20
- [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate at ko-fi.com][🖇kofi-img]][🖇kofi]
21
-
22
- <details>
23
- <summary>👣 How will this project approach the September 2025 hostile takeover of RubyGems? 🚑️</summary>
24
-
25
- I've summarized my thoughts in [this blog post](https://dev.to/galtzo/hostile-takeover-of-rubygems-my-thoughts-5hlo).
26
-
27
- </details>
28
-
29
- ## 🌻 Synopsis
30
-
31
- Json::Merge is a standalone Ruby module that intelligently merges two versions of a JSON file using tree-sitter AST analysis. It's like a smart "git merge" specifically designed for JSON configuration files. Built on top of [ast-merge][ast-merge], it shares the same architecture as [prism-merge][prism-merge] for Ruby source files.
32
-
33
- For JSONC (JSON with Comments) support, see the [jsonc-merge][jsonc-merge] gem.
34
-
35
- ### Key Features
36
-
37
- - **Tree-Sitter Powered**: Uses tree-sitter-json for accurate AST parsing
38
- - **Intelligent**: Matches objects and arrays by structural signatures
39
- - **Fuzzy Property Matching**: `ObjectMatchRefiner` matches similar property names
40
- (e.g., `databaseUrl` ↔ `database_url`) using Levenshtein distance for naming convention differences
41
- - **Full Provenance**: Tracks origin of every node
42
- - **Standalone**: Minimal dependencies - just `ast-merge` and `ruby_tree_sitter`
43
- - **Customizable**:
44
- - `signature_generator` - callable custom signature generators
45
- - `preference` - setting of `:template`, `:destination`, or a Hash for per-node-type preferences
46
- - `node_splitter` - Hash mapping node types to callables for per-node-type merge customization (see [ast-merge][ast-merge] docs)
47
- - `add_template_only_nodes` - setting to retain nodes that do not exist in destination
48
- - `match_refiners` - array of refiners for fuzzy matching (e.g., `ObjectMatchRefiner`)
49
-
50
- ### Supported Node Types
51
-
52
- | Node Type | Signature Format | Matching Behavior |
53
- | --- | --- | --- |
54
- | Object | `[:object, key_signatures...]` | Objects match by their key structure |
55
- | Array | `[:array, element_count]` | Arrays match by position and type |
56
- | Pair | `[:pair, key_name]` | Key-value pairs match by key name |
57
- | String | `[:string, value]` | Strings match by value |
58
- | Number | `[:number, value]` | Numbers match by value |
59
- | Boolean | `[:boolean, value]` | Booleans match by value |
60
- | Null | `[:null]` | Null values always match |
61
-
62
- ### Example
63
-
64
- ```ruby
65
- require "json/merge"
66
-
67
- template = File.read("template.json")
68
- destination = File.read("destination.json")
69
-
70
- merger = Json::Merge::SmartMerger.new(template, destination)
71
- result = merger.merge
72
-
73
- File.write("merged.json", result.to_json)
74
- ```
75
-
76
- ### The `*-merge` Gem Family
77
-
78
- The `*-merge` gem family provides intelligent, AST-based merging for various file formats. At the foundation is [tree_haver][tree_haver], which provides a unified cross-Ruby parsing API that works seamlessly across MRI, JRuby, and TruffleRuby.
79
-
80
- | Gem | Version / CI | Language<br>/ Format | Parser Backend(s) | Description |
81
- |------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------:|----------------------|-------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|
82
- | [tree_haver][tree_haver] | [![Version][tree_haver-gem-i]][tree_haver-gem] <br/> [![CI][tree_haver-ci-i]][tree_haver-ci] | Multi | Supported Backends: MRI C, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus, Parslet | **Foundation**: Cross-Ruby adapter for parsing libraries (like Faraday for HTTP) |
83
- | [ast-merge][ast-merge] | [![Version][ast-merge-gem-i]][ast-merge-gem] <br/> [![CI][ast-merge-ci-i]][ast-merge-ci] | Text | internal | **Infrastructure**: Shared base classes and merge logic for all `*-merge` gems |
84
- | [bash-merge][bash-merge] | [![Version][bash-merge-gem-i]][bash-merge-gem] <br/> [![CI][bash-merge-ci-i]][bash-merge-ci] | Bash | [tree-sitter-bash][ts-bash] (via tree_haver) | Smart merge for Bash scripts |
85
- | [commonmarker-merge][commonmarker-merge] | [![Version][commonmarker-merge-gem-i]][commonmarker-merge-gem] <br/> [![CI][commonmarker-merge-ci-i]][commonmarker-merge-ci] | Markdown | [Commonmarker][commonmarker] (via tree_haver) | Smart merge for Markdown (CommonMark via comrak Rust) |
86
- | [dotenv-merge][dotenv-merge] | [![Version][dotenv-merge-gem-i]][dotenv-merge-gem] <br/> [![CI][dotenv-merge-ci-i]][dotenv-merge-ci] | Dotenv | internal | Smart merge for `.env` files |
87
- | [json-merge][json-merge] | [![Version][json-merge-gem-i]][json-merge-gem] <br/> [![CI][json-merge-ci-i]][json-merge-ci] | JSON | [tree-sitter-json][ts-json] (via tree_haver) | Smart merge for JSON files |
88
- | [jsonc-merge][jsonc-merge] | [![Version][jsonc-merge-gem-i]][jsonc-merge-gem] <br/> [![CI][jsonc-merge-ci-i]][jsonc-merge-ci] | JSONC | [tree-sitter-jsonc][ts-jsonc] (via tree_haver) | ⚠️ Proof of concept; Smart merge for JSON with Comments |
89
- | [markdown-merge][markdown-merge] | [![Version][markdown-merge-gem-i]][markdown-merge-gem] <br/> [![CI][markdown-merge-ci-i]][markdown-merge-ci] | Markdown | [Commonmarker][commonmarker] / [Markly][markly] (via tree_haver), [Parslet][parslet] | **Foundation**: Shared base for Markdown mergers with inner code block merging |
90
- | [markly-merge][markly-merge] | [![Version][markly-merge-gem-i]][markly-merge-gem] <br/> [![CI][markly-merge-ci-i]][markly-merge-ci] | Markdown | [Markly][markly] (via tree_haver) | Smart merge for Markdown (CommonMark via cmark-gfm C) |
91
- | [prism-merge][prism-merge] | [![Version][prism-merge-gem-i]][prism-merge-gem] <br/> [![CI][prism-merge-ci-i]][prism-merge-ci] | Ruby | [Prism][prism] (`prism` std lib gem) | Smart merge for Ruby source files |
92
- | [psych-merge][psych-merge] | [![Version][psych-merge-gem-i]][psych-merge-gem] <br/> [![CI][psych-merge-ci-i]][psych-merge-ci] | YAML | [Psych][psych] (`psych` std lib gem) | Smart merge for YAML files |
93
- | [rbs-merge][rbs-merge] | [![Version][rbs-merge-gem-i]][rbs-merge-gem] <br/> [![CI][rbs-merge-ci-i]][rbs-merge-ci] | RBS | [tree-sitter-bash][ts-rbs] (via tree_haver), [RBS][rbs] (`rbs` std lib gem) | Smart merge for Ruby type signatures |
94
- | [toml-merge][toml-merge] | [![Version][toml-merge-gem-i]][toml-merge-gem] <br/> [![CI][toml-merge-ci-i]][toml-merge-ci] | TOML | [Parslet + toml][toml], [Citrus + toml-rb][toml-rb], [tree-sitter-toml][ts-toml] (all via tree_haver) | Smart merge for TOML files |
95
-
96
- #### Backend Platform Compatibility
97
-
98
- tree_haver supports multiple parsing backends, but not all backends work on all Ruby platforms:
99
-
100
- | Platform 👉️<br> TreeHaver Backend 👇️ | MRI | JRuby | TruffleRuby | Notes |
101
- |-------------------------------------------------|:---:|:-----:|:-----------:|----------------------------------------------------------------------------|
102
- | **MRI** ([ruby_tree_sitter][ruby_tree_sitter]) | ✅ | ❌ | ❌ | C extension, MRI only |
103
- | **Rust** ([tree_stump][tree_stump]) | ✅ | ❌ | ❌ | Rust extension via magnus/rb-sys, MRI only |
104
- | **FFI** ([ffi][ffi]) | ✅ | ✅ | ❌ | TruffleRuby's FFI doesn't support `STRUCT_BY_VALUE` |
105
- | **Java** ([jtreesitter][jtreesitter]) | ❌ | ✅ | ❌ | JRuby only, requires grammar JARs |
106
- | **Prism** ([prism][prism]) | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
107
- | **Psych** ([psych][psych]) | ✅ | ✅ | ✅ | YAML parsing, stdlib |
108
- | **Citrus** ([citrus][citrus]) | ✅ | ✅ | ✅ | Pure Ruby PEG parser, no native dependencies |
109
- | **Parslet** ([parslet][parslet]) | ✅ | ✅ | ✅ | Pure Ruby PEG parser, no native dependencies |
110
- | **Commonmarker** ([commonmarker][commonmarker]) | ✅ | ❌ | ❓ | Rust extension for Markdown (via [commonmarker-merge][commonmarker-merge]) |
111
- | **Markly** ([markly][markly]) | ✅ | ❌ | ❓ | C extension for Markdown (via [markly-merge][markly-merge]) |
112
-
113
- **Legend**: ✅ = Works, ❌ = Does not work, ❓ = Untested
114
-
115
- **Why some backends don't work on certain platforms**:
116
-
117
- - **JRuby**: Runs on the JVM; cannot load native C/Rust extensions (`.so` files)
118
- - **TruffleRuby**: Has C API emulation via Sulong/LLVM, but it doesn't expose all MRI internals that native extensions require (e.g., `RBasic.flags`, `rb_gc_writebarrier`)
119
- - **FFI on TruffleRuby**: TruffleRuby's FFI implementation doesn't support returning structs by value, which tree-sitter's C API requires
120
-
121
- **Example implementations** for the gem templating use case:
122
-
123
- | Gem | Purpose | Description |
124
- |--------------------------|-----------------|-----------------------------------------------|
125
- | [kettle-dev][kettle-dev] | Gem Development | Gem templating tool using `*-merge` gems |
126
- | [kettle-jem][kettle-jem] | Gem Templating | Gem template library with smart merge support |
127
-
128
- [tree_haver]: https://github.com/kettle-rb/tree_haver
129
- [ast-merge]: https://github.com/kettle-rb/ast-merge
130
- [prism-merge]: https://github.com/kettle-rb/prism-merge
131
- [psych-merge]: https://github.com/kettle-rb/psych-merge
132
- [json-merge]: https://github.com/kettle-rb/json-merge
133
- [jsonc-merge]: https://github.com/kettle-rb/jsonc-merge
134
- [bash-merge]: https://github.com/kettle-rb/bash-merge
135
- [rbs-merge]: https://github.com/kettle-rb/rbs-merge
136
- [dotenv-merge]: https://github.com/kettle-rb/dotenv-merge
137
- [toml-merge]: https://github.com/kettle-rb/toml-merge
138
- [markdown-merge]: https://github.com/kettle-rb/markdown-merge
139
- [markly-merge]: https://github.com/kettle-rb/markly-merge
140
- [commonmarker-merge]: https://github.com/kettle-rb/commonmarker-merge
141
- [kettle-dev]: https://github.com/kettle-rb/kettle-dev
142
- [kettle-jem]: https://github.com/kettle-rb/kettle-jem
143
- [tree_haver-gem]: https://bestgems.org/gems/tree_haver
144
- [ast-merge-gem]: https://bestgems.org/gems/ast-merge
145
- [prism-merge-gem]: https://bestgems.org/gems/prism-merge
146
- [psych-merge-gem]: https://bestgems.org/gems/psych-merge
147
- [json-merge-gem]: https://bestgems.org/gems/json-merge
148
- [jsonc-merge-gem]: https://bestgems.org/gems/jsonc-merge
149
- [bash-merge-gem]: https://bestgems.org/gems/bash-merge
150
- [rbs-merge-gem]: https://bestgems.org/gems/rbs-merge
151
- [dotenv-merge-gem]: https://bestgems.org/gems/dotenv-merge
152
- [toml-merge-gem]: https://bestgems.org/gems/toml-merge
153
- [markdown-merge-gem]: https://bestgems.org/gems/markdown-merge
154
- [markly-merge-gem]: https://bestgems.org/gems/markly-merge
155
- [commonmarker-merge-gem]: https://bestgems.org/gems/commonmarker-merge
156
- [kettle-dev-gem]: https://bestgems.org/gems/kettle-dev
157
- [kettle-jem-gem]: https://bestgems.org/gems/kettle-jem
158
- [tree_haver-gem-i]: https://img.shields.io/gem/v/tree_haver.svg
159
- [ast-merge-gem-i]: https://img.shields.io/gem/v/ast-merge.svg
160
- [prism-merge-gem-i]: https://img.shields.io/gem/v/prism-merge.svg
161
- [psych-merge-gem-i]: https://img.shields.io/gem/v/psych-merge.svg
162
- [json-merge-gem-i]: https://img.shields.io/gem/v/json-merge.svg
163
- [jsonc-merge-gem-i]: https://img.shields.io/gem/v/jsonc-merge.svg
164
- [bash-merge-gem-i]: https://img.shields.io/gem/v/bash-merge.svg
165
- [rbs-merge-gem-i]: https://img.shields.io/gem/v/rbs-merge.svg
166
- [dotenv-merge-gem-i]: https://img.shields.io/gem/v/dotenv-merge.svg
167
- [toml-merge-gem-i]: https://img.shields.io/gem/v/toml-merge.svg
168
- [markdown-merge-gem-i]: https://img.shields.io/gem/v/markdown-merge.svg
169
- [markly-merge-gem-i]: https://img.shields.io/gem/v/markly-merge.svg
170
- [commonmarker-merge-gem-i]: https://img.shields.io/gem/v/commonmarker-merge.svg
171
- [kettle-dev-gem-i]: https://img.shields.io/gem/v/kettle-dev.svg
172
- [kettle-jem-gem-i]: https://img.shields.io/gem/v/kettle-jem.svg
173
- [tree_haver-ci-i]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml/badge.svg
174
- [ast-merge-ci-i]: https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml/badge.svg
175
- [prism-merge-ci-i]: https://github.com/kettle-rb/prism-merge/actions/workflows/current.yml/badge.svg
176
- [psych-merge-ci-i]: https://github.com/kettle-rb/psych-merge/actions/workflows/current.yml/badge.svg
177
- [json-merge-ci-i]: https://github.com/kettle-rb/json-merge/actions/workflows/current.yml/badge.svg
178
- [jsonc-merge-ci-i]: https://github.com/kettle-rb/jsonc-merge/actions/workflows/current.yml/badge.svg
179
- [bash-merge-ci-i]: https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml/badge.svg
180
- [rbs-merge-ci-i]: https://github.com/kettle-rb/rbs-merge/actions/workflows/current.yml/badge.svg
181
- [dotenv-merge-ci-i]: https://github.com/kettle-rb/dotenv-merge/actions/workflows/current.yml/badge.svg
182
- [toml-merge-ci-i]: https://github.com/kettle-rb/toml-merge/actions/workflows/current.yml/badge.svg
183
- [markdown-merge-ci-i]: https://github.com/kettle-rb/markdown-merge/actions/workflows/current.yml/badge.svg
184
- [markly-merge-ci-i]: https://github.com/kettle-rb/markly-merge/actions/workflows/current.yml/badge.svg
185
- [commonmarker-merge-ci-i]: https://github.com/kettle-rb/commonmarker-merge/actions/workflows/current.yml/badge.svg
186
- [kettle-dev-ci-i]: https://github.com/kettle-rb/kettle-dev/actions/workflows/current.yml/badge.svg
187
- [kettle-jem-ci-i]: https://github.com/kettle-rb/kettle-jem/actions/workflows/current.yml/badge.svg
188
- [tree_haver-ci]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml
189
- [ast-merge-ci]: https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml
190
- [prism-merge-ci]: https://github.com/kettle-rb/prism-merge/actions/workflows/current.yml
191
- [psych-merge-ci]: https://github.com/kettle-rb/psych-merge/actions/workflows/current.yml
192
- [json-merge-ci]: https://github.com/kettle-rb/json-merge/actions/workflows/current.yml
193
- [jsonc-merge-ci]: https://github.com/kettle-rb/jsonc-merge/actions/workflows/current.yml
194
- [bash-merge-ci]: https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml
195
- [rbs-merge-ci]: https://github.com/kettle-rb/rbs-merge/actions/workflows/current.yml
196
- [dotenv-merge-ci]: https://github.com/kettle-rb/dotenv-merge/actions/workflows/current.yml
197
- [toml-merge-ci]: https://github.com/kettle-rb/toml-merge/actions/workflows/current.yml
198
- [markdown-merge-ci]: https://github.com/kettle-rb/markdown-merge/actions/workflows/current.yml
199
- [markly-merge-ci]: https://github.com/kettle-rb/markly-merge/actions/workflows/current.yml
200
- [commonmarker-merge-ci]: https://github.com/kettle-rb/commonmarker-merge/actions/workflows/current.yml
201
- [kettle-dev-ci]: https://github.com/kettle-rb/kettle-dev/actions/workflows/current.yml
202
- [kettle-jem-ci]: https://github.com/kettle-rb/kettle-jem/actions/workflows/current.yml
203
- [prism]: https://github.com/ruby/prism
204
- [psych]: https://github.com/ruby/psych
205
- [ffi]: https://github.com/ffi/ffi
206
- [ts-json]: https://github.com/tree-sitter/tree-sitter-json
207
- [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
208
- [ts-bash]: https://github.com/tree-sitter/tree-sitter-bash
209
- [ts-rbs]: https://github.com/joker1007/tree-sitter-rbs
210
- [ts-toml]: https://github.com/tree-sitter-grammars/tree-sitter-toml
211
- [dotenv]: https://github.com/bkeepers/dotenv
212
- [rbs]: https://github.com/ruby/rbs
213
- [toml-rb]: https://github.com/emancu/toml-rb
214
- [toml]: https://github.com/jm/toml
215
- [markly]: https://github.com/ioquatix/markly
216
- [commonmarker]: https://github.com/gjtorikian/commonmarker
217
- [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
218
- [tree_stump]: https://github.com/joker1007/tree_stump
219
- [jtreesitter]: https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter
220
- [citrus]: https://github.com/mjackson/citrus
221
- [parslet]: https://github.com/kschiess/parslet
222
-
223
- ## 💡 Info you can shake a stick at
224
-
225
- | Tokens to Remember | [![Gem name][⛳️name-img]][👽dl-rank] [![Gem namespace][⛳️namespace-img]][📜src-gh] |
226
- | --- | --- |
227
- | Works with JRuby | [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] |
228
- | Works with Truffle Ruby | [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] |
229
- | Works with MRI Ruby 3 | [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] |
230
- | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][🖼️galtzo-discord] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
231
- | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ\!](https://img.shields.io/badge/KLOC-4.308-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue)][🧮kloc] |
232
- | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] |
233
- | Compliance | [![License: MIT][📄license-img]][📄license-ref] [![Compatible with Apache Software Projects: Verified by SkyWalking Eyes][📄license-compat-img]][📄license-compat] [![📄ilo-declaration-img][📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
234
- | Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] |
235
- | Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
236
- | `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
237
-
238
- ### Compatibility
239
-
240
- Compatible with MRI Ruby 3.2.0+, and concordant releases of JRuby, and TruffleRuby.
241
-
242
- | 🚚 *Amazing* test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
243
- | --- | --- |
244
- | 👟 Check it out\! | ✨ [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ |
245
-
246
- ### Federated DVCS
247
-
248
- <details markdown="1">
249
- <summary>Find this repo on federated forges (Coming soon!)</summary>
250
-
251
- | Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
252
- | --- | --- | --- | --- | --- | --- | --- |
253
- | 🧪 [kettle-rb/json-merge on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜gl-wiki] | 🐭 Tiny Matrix | ➖ |
254
- | 🧊 [kettle-rb/json-merge on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ |
255
- | 🐙 [kettle-rb/json-merge on GitHub][📜src-gh] | Another Mirror | [💚][🤝gh-issues] | [💚][🤝gh-pulls] | [💚][📜gh-wiki] | 💯 Full Matrix | [💚][gh-discussions] |
256
- | 🎮️ [Discord Server][🖼️galtzo-discord] | [![Live Chat on Discord][✉️discord-invite-img-ftb]][🖼️galtzo-discord] | [Let's][🖼️galtzo-discord] | [talk][🖼️galtzo-discord] | [about][🖼️galtzo-discord] | [this][🖼️galtzo-discord] | [library\!][🖼️galtzo-discord] |
257
-
258
- </details>
259
-
260
- [gh-discussions]: https://github.com/kettle-rb/json-merge/discussions
261
-
262
- ### Enterprise Support [![Tidelift](https://tidelift.com/badges/package/rubygems/json-merge)][🏙️entsup-tidelift]
263
-
264
- Available as part of the Tidelift Subscription.
265
-
266
- <details markdown="1">
267
- <summary>Need enterprise-level guarantees?</summary>
268
-
269
- The maintainers of this and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.
270
-
271
- [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]
272
-
273
- - 💡Subscribe for support guarantees covering *all* your FLOSS dependencies
274
-
275
- - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
276
-
277
- - 💡Tidelift pays maintainers to maintain the software you depend on\!<br/>📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers
278
- Alternatively:
279
-
280
- - [![Live Chat on Discord][✉️discord-invite-img-ftb]][🖼️galtzo-discord]
281
-
282
- - [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork]
283
-
284
- - [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor]
285
-
286
- </details>
287
-
288
- ## ✨ Installation
289
-
290
- Install the gem and add to the application's Gemfile by executing:
291
-
292
- ```console
293
- bundle add json-merge
294
- ```
295
-
296
- If bundler is not being used to manage dependencies, install the gem by executing:
297
-
298
- ```console
299
- gem install json-merge
300
- ```
301
-
302
- ### 🔒 Secure Installation
303
-
304
- <details markdown="1">
305
- <summary>For Medium or High Security Installations</summary>
306
-
307
- This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by
308
- [stone\_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with
309
- by following the instructions below.
310
-
311
- Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate:
312
-
313
- ```console
314
- gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem)
315
- ```
316
-
317
- You only need to do that once. Then proceed to install with:
318
-
319
- ```console
320
- gem install json-merge -P HighSecurity
321
- ```
322
-
323
- The `HighSecurity` trust profile will verify signed gems, and not allow the installation of unsigned dependencies.
324
-
325
- If you want to up your security game full-time:
326
-
327
- ```console
328
- bundle config set --global trust-policy MediumSecurity
329
- ```
330
-
331
- `MediumSecurity` instead of `HighSecurity` is necessary if not all the gems you use are signed.
332
-
333
- NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine.
334
-
335
- </details>
336
-
337
- ### 🌳 Tree-Sitter Parser Installation
338
-
339
- This gem requires the `tree-sitter-json` parser library to be installed on your system.
340
- The parser is a native shared library (`.so` on Linux, `.dylib` on macOS) that provides
341
- JSON syntax parsing capabilities. For JSONC (JSON with Comments) support, use the
342
- [jsonc-merge][jsonc-merge] gem instead.
343
-
344
- #### Option 1: Pre-built Binaries (Recommended)
345
-
346
- Download pre-built parsers from [Faveod/tree-sitter-parsers](https://github.com/Faveod/tree-sitter-parsers/releases):
347
-
348
- **Linux (x64):**
349
-
350
- ```console
351
- # Download and extract
352
- curl -Lo parsers.tar.gz https://github.com/Faveod/tree-sitter-parsers/releases/download/v4.10/tree-sitter-parsers-4.10-linux-x64.tar.gz
353
- tar -xzf parsers.tar.gz
354
-
355
- # Install system-wide (requires sudo)
356
- sudo cp libtree-sitter-json.so /usr/lib/
357
- sudo ldconfig
358
-
359
- # Or install locally and set environment variable
360
- mkdir -p ~/.local/lib/tree-sitter
361
- cp libtree-sitter-json.so ~/.local/lib/tree-sitter/
362
- export TREE_SITTER_JSON_PATH="$HOME/.local/lib/tree-sitter/libtree-sitter-json.so"
363
- ```
364
-
365
- **Debian/Ubuntu (amd64):**
366
-
367
- ```console
368
- curl -Lo parsers.deb https://github.com/Faveod/tree-sitter-parsers/releases/download/v4.10/tree-sitter-parsers-4.10-amd64.deb
369
- sudo dpkg -i parsers.deb
370
- ```
371
-
372
- **macOS (Apple Silicon):**
373
-
374
- ```console
375
- curl -Lo parsers.tar.gz https://github.com/Faveod/tree-sitter-parsers/releases/download/v4.10/tree-sitter-parsers-4.10-macos-arm64.tar.gz
376
- tar -xzf parsers.tar.gz
377
-
378
- # Install to a location and set environment variable
379
- mkdir -p ~/.local/lib/tree-sitter
380
- cp libtree-sitter-json.dylib ~/.local/lib/tree-sitter/
381
- export TREE_SITTER_JSON_PATH="$HOME/.local/lib/tree-sitter/libtree-sitter-json.dylib"
382
- ```
383
-
384
- #### Option 2: Build from Source
385
-
386
- ```console
387
- git clone https://github.com/tree-sitter/tree-sitter-json.git
388
- cd tree-sitter-json
389
- make
390
- sudo cp libtree-sitter-json.so /usr/lib/ # Linux
391
- # or
392
- sudo cp libtree-sitter-json.dylib /usr/local/lib/ # macOS
393
- ```
394
-
395
- #### Option 3: Package Manager
396
-
397
- Some package managers provide tree-sitter parsers:
398
-
399
- **Fedora Atomic (Silverblue, Kinoite, Bazzite, Aurora, etc.):**
400
-
401
- ```console
402
- # Install via rpm-ostree (requires reboot)
403
- rpm-ostree install libtree-sitter-json
404
-
405
- # The library will be installed to /usr/lib64/libtree-sitter-json.so
406
- # You may need to set the environment variable:
407
- export TREE_SITTER_JSON_PATH="/usr/lib64/libtree-sitter-json.so"
408
- ```
409
-
410
- **Fedora (traditional):**
411
-
412
- ```console
413
- sudo dnf install libtree-sitter-json
414
- ```
415
-
416
- **Arch Linux:**
417
-
418
- ```console
419
- # Check AUR for tree-sitter-json
420
- yay -S tree-sitter-json
421
- ```
422
-
423
- #### Environment Variable
424
-
425
- If the parser is not in a standard location (`/usr/lib/`, `/usr/lib64/`, `/usr/local/lib/`),
426
- set the `TREE_SITTER_JSON_PATH` environment variable to point to the parser library:
427
-
428
- ```console
429
- export TREE_SITTER_JSON_PATH="/path/to/libtree-sitter-json.so"
430
- ```
431
-
432
- **Note:** Some distributions install the library with a version number suffix
433
- (e.g., `libtree-sitter-json.so.14` instead of `libtree-sitter-json.so`).
434
- If the gem can't find the parser, check for versioned files and either:
435
-
436
- - Set `TREE_SITTER_JSON_PATH` to the full versioned path, or
437
- - Create a symlink: `sudo ln -s /usr/lib64/libtree-sitter-json.so.14 /usr/lib64/libtree-sitter-json.so`
438
- Add this to your shell profile (`.bashrc`, `.zshrc`, etc.) for persistence.
439
-
440
- ### 💎 Ruby Interface Gems
441
-
442
- In addition to the tree-sitter parser library, you need a Ruby gem that provides
443
- bindings to tree-sitter. Choose **one** of the following based on your Ruby implementation:
444
-
445
- | Gem | Ruby Support | Description |
446
- | --- | --- | --- |
447
- | [ruby\_tree\_sitter][ruby_tree_sitter] | MRI only | C extension bindings (recommended for MRI) |
448
- | [tree\_stump][tree_stump] | MRI (maybe JRuby) | Rust-based bindings via Rutie |
449
- | [ffi][ffi] | MRI, JRuby, TruffleRuby | Generic FFI bindings (used by tree\_haver's FFI backend) |
450
-
451
- [ruby_tree_sitter]: https://github.com/Faveod/ruby_tree_sitter
452
- [tree_stump]: https://github.com/nickstenning/tree_stump
453
- [ffi]: https://github.com/ffi/ffi
454
-
455
- #### For MRI Ruby (Recommended)
456
-
457
- ```console
458
- gem install ruby_tree_sitter
459
- ```
460
-
461
- Or add to your Gemfile:
462
-
463
- ```ruby
464
- gem "ruby_tree_sitter", "~> 2.0"
465
- ```
466
-
467
- #### For JRuby or TruffleRuby
468
-
469
- ```console
470
- gem install ffi
471
- ```
472
-
473
- Or add to your Gemfile:
474
-
475
- ```ruby
476
- gem "ffi"
477
- ```
478
-
479
- The `tree_haver` gem (a dependency of json-merge) will automatically detect and use
480
- the appropriate backend based on which gems are available.
481
-
482
- **Note:** The `ruby_tree_sitter` gem only compiles on MRI Ruby. For JRuby or TruffleRuby,
483
- you must use the FFI backend.
484
-
485
- ## ⚙️ Configuration
486
-
487
- ### Signature Match Preference
488
-
489
- Control which version to use when nodes have matching signatures but different content:
490
-
491
- ```ruby
492
- # Use template version (for config updates)
493
- merger = Json::Merge::SmartMerger.new(
494
- template,
495
- destination,
496
- preference: :template,
497
- )
498
-
499
- # Use destination version (default - preserve customizations)
500
- merger = Json::Merge::SmartMerger.new(
501
- template,
502
- destination,
503
- preference: :destination,
504
- )
505
- ```
506
-
507
- ### Template-Only Nodes
508
-
509
- Control whether to add nodes that only exist in the template:
510
-
511
- ```ruby
512
- # Add template-only nodes
513
- merger = Json::Merge::SmartMerger.new(
514
- template,
515
- destination,
516
- add_template_only_nodes: true,
517
- )
518
- ```
519
-
520
- ### Object Match Refiner
521
-
522
- When JSON object properties (key-value pairs) don't match by exact key name, the
523
- `ObjectMatchRefiner` uses fuzzy matching to pair entries with:
524
-
525
- - Similar key names (e.g., `databaseUrl` vs `database_url`)
526
- - Keys with typos or different naming conventions (camelCase vs snake\_case)
527
- - Array elements with similar structure or content
528
-
529
- <!-- end list -->
530
-
531
- ```ruby
532
- # Enable object fuzzy matching
533
- merger = Json::Merge::SmartMerger.new(
534
- template,
535
- destination,
536
- match_refiners: [
537
- Json::Merge::ObjectMatchRefiner.new(threshold: 0.5),
538
- ],
539
- )
540
- ```
541
-
542
- #### ObjectMatchRefiner Options
543
-
544
- | Option | Default | Description |
545
- | --- | --- | --- |
546
- | `threshold` | 0.5 | Minimum similarity score (0.0-1.0) to accept a match |
547
- | `key_weight` | 0.7 | Weight for key name similarity |
548
- | `value_weight` | 0.3 | Weight for value similarity |
549
-
550
- ```ruby
551
- # Custom weights for key-centric matching
552
- refiner = Json::Merge::ObjectMatchRefiner.new(
553
- threshold: 0.6,
554
- key_weight: 0.8, # Focus more on key names
555
- value_weight: 0.2, # Less focus on values
556
- )
557
- ```
558
-
559
- ### Debug Logging
560
-
561
- Enable debug logging to see merge decisions:
562
-
563
- ```bash
564
- export JSON_MERGE_DEBUG=1
565
- ```
566
-
567
- ## 🔧 Basic Usage
568
-
569
- ### Merging Two JSON Files
570
-
571
- ```ruby
572
- require "json/merge"
573
-
574
- template_content = File.read("template.json")
575
- dest_content = File.read("destination.json")
576
-
577
- merger = Json::Merge::SmartMerger.new(template_content, dest_content)
578
- result = merger.merge
579
-
580
- File.write("merged.json", result.to_json)
581
- ```
582
-
583
- ### Analyzing a JSON File
584
-
585
- ```ruby
586
- require "json/merge"
587
-
588
- source = File.read("config.json")
589
- analysis = Json::Merge::FileAnalysis.new(source)
590
-
591
- # Iterate over all top-level nodes
592
- analysis.statements.each do |node|
593
- sig = analysis.generate_signature(node)
594
- puts "#{node.class}: #{sig.inspect}"
595
- end
596
- ```
597
-
598
- ### Fuzzy Property Matching
599
-
600
- When property names differ between template and destination (e.g., naming convention changes),
601
- use the `ObjectMatchRefiner`:
602
-
603
- ```ruby
604
- require "json/merge"
605
-
606
- template = <<~JSON
607
- {
608
- "databaseUrl": "postgres://localhost/app",
609
- "cacheTimeout": 3600,
610
- "apiEndpoint": "https://api.example.com"
611
- }
612
- JSON
613
-
614
- destination = <<~JSON
615
- {
616
- "database_url": "postgres://localhost/custom",
617
- "cache_ttl": 7200,
618
- "api_endpoint": "https://custom.example.com"
619
- }
620
- JSON
621
-
622
- # Default merge won't match keys (names differ - camelCase vs snake_case)
623
- # Use ObjectMatchRefiner for fuzzy matching
624
- merger = Json::Merge::SmartMerger.new(
625
- template,
626
- destination,
627
- match_refiners: [
628
- Json::Merge::ObjectMatchRefiner.new(threshold: 0.5),
629
- ],
630
- )
631
- result = merger.merge
632
-
633
- # Properties are matched despite naming convention differences:
634
- # - databaseUrl ↔ database_url (similar when normalized)
635
- # - cacheTimeout ↔ cache_ttl (similar: "cache")
636
- # - apiEndpoint ↔ api_endpoint (similar when normalized)
637
- ```
638
-
639
- ### Array Element Matching
640
-
641
- The `ObjectMatchRefiner` also handles array elements with similar structure:
642
-
643
- ```ruby
644
- template = <<~JSON
645
- {
646
- "users": [
647
- { "id": 1, "userName": "alice" },
648
- { "id": 2, "userName": "bob" }
649
- ]
650
- }
651
- JSON
652
-
653
- destination = <<~JSON
654
- {
655
- "users": [
656
- { "id": 1, "user_name": "alice_custom" },
657
- { "id": 3, "user_name": "charlie" }
658
- ]
659
- }
660
- JSON
661
-
662
- merger = Json::Merge::SmartMerger.new(
663
- template,
664
- destination,
665
- match_refiners: [
666
- Json::Merge::ObjectMatchRefiner.new(threshold: 0.5),
667
- ],
668
- )
669
- # Array elements with matching IDs or similar structure are paired
670
- ```
671
-
672
- ## 🦷 FLOSS Funding
673
-
674
- While kettle-rb tools are free software and will always be, the project would benefit immensely from some funding.
675
- Raising a monthly budget of... "dollars" would make the project more sustainable.
676
-
677
- We welcome both individual and corporate sponsors\! We also offer a
678
- wide array of funding channels to account for your preferences
679
- (although currently [Open Collective][🖇osc] is our preferred funding platform).
680
-
681
- **If you're working in a company that's making significant use of kettle-rb tools we'd
682
- appreciate it if you suggest to your company to become a kettle-rb sponsor.**
683
-
684
- You can support the development of kettle-rb tools via
685
- [GitHub Sponsors][🖇sponsor],
686
- [Liberapay][⛳liberapay],
687
- [PayPal][🖇paypal],
688
- [Open Collective][🖇osc]
689
- and [Tidelift][🏙️entsup-tidelift].
690
-
691
- | 📍 NOTE |
692
- | --- |
693
- | If doing a sponsorship in the form of donation is problematic for your company <br/> from an accounting standpoint, we'd recommend the use of Tidelift, <br/> where you can get a support-like subscription instead. |
694
-
695
- ### Open Collective for Individuals
696
-
697
- Support us with a monthly donation and help us continue our activities. \[[Become a backer][🖇osc-backers]\]
698
-
699
- NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
700
-
701
- <!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
702
- No backers yet. Be the first!
703
- <!-- OPENCOLLECTIVE-INDIVIDUALS:END -->
704
-
705
- ### Open Collective for Organizations
706
-
707
- Become a sponsor and get your logo on our README on GitHub with a link to your site. \[[Become a sponsor][🖇osc-sponsors]\]
708
-
709
- NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
710
-
711
- <!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
712
- No sponsors yet. Be the first!
713
- <!-- OPENCOLLECTIVE-ORGANIZATIONS:END -->
714
-
715
- [kettle-readme-backers]: https://github.com/kettle-rb/json-merge/blob/main/exe/kettle-readme-backers
716
-
717
- ### Another way to support open-source
718
-
719
- I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats).
720
-
721
- If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in `bundle fund`.
722
-
723
- I’m developing a new library, [floss\_funding][🖇floss-funding-gem], designed to empower open-source developers like myself to get paid for the work we do, in a sustainable way. Please give it a look.
724
-
725
- **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
726
-
727
- [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS efforts using Patreon][🖇patreon-img]][🖇patreon]
728
-
729
- ## 🔐 Security
730
-
731
- See [SECURITY.md][🔐security].
732
-
733
- ## 🤝 Contributing
734
-
735
- If you need some ideas of where to help, you could work on adding more code coverage,
736
- or if it is already 💯 (see [below](#code-coverage)) check [reek](REEK), [issues][🤝gh-issues], or [PRs][🤝gh-pulls],
737
- or use the gem and think about how it could be better.
738
-
739
- We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
740
-
741
- See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
742
-
743
- ### 🚀 Release Instructions
744
-
745
- See [CONTRIBUTING.md][🤝contributing].
746
-
747
- ### Code Coverage
748
-
749
- [![Coverage Graph][🏀codecov-g]][🏀codecov]
750
-
751
- [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls]
752
-
753
- [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov]
754
-
755
- ### 🪇 Code of Conduct
756
-
757
- Everyone interacting with this project's codebases, issue trackers,
758
- chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct].
759
-
760
- ## 🌈 Contributors
761
-
762
- [![Contributors][🖐contributors-img]][🖐contributors]
763
-
764
- Made with [contributors-img][🖐contrib-rocks].
765
-
766
- Also see GitLab Contributors: <https://gitlab.com/kettle-rb/json-merge/-/graphs/main>
767
-
768
- <details>
769
- <summary>⭐️ Star History</summary>
770
-
771
- <a href="https://star-history.com/#kettle-rb/json-merge&Date">
772
- <picture>
773
- <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=kettle-rb/json-merge&type=Date&theme=dark" />
774
- <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=kettle-rb/json-merge&type=Date" />
775
- <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=kettle-rb/json-merge&type=Date" />
776
- </picture>
777
- </a>
778
-
779
- </details>
780
-
781
- ## 📌 Versioning
782
-
783
- This Library adheres to [![Semantic Versioning 2.0.0][📌semver-img]][📌semver].
784
- Violations of this scheme should be reported as bugs.
785
- Specifically, if a minor or patch version is released that breaks backward compatibility,
786
- a new version should be immediately released that restores compatibility.
787
- Breaking changes to the public API will only be introduced with new major versions.
788
-
789
- > dropping support for a platform is both obviously and objectively a breaking change <br/>
790
- > —Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716][📌semver-breaking]
791
-
792
- I understand that policy doesn't work universally ("exceptions to every rule\!"),
793
- but it is the policy here.
794
- As such, in many cases it is good to specify a dependency on this library using
795
- the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
796
-
797
- For example:
798
-
799
- ```ruby
800
- spec.add_dependency("json-merge", "~> 1.0")
801
- ```
802
-
803
- <details markdown="1">
804
- <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
805
-
806
- SemVer should, IMO, but doesn't explicitly, say that dropping support for specific Platforms
807
- is a *breaking change* to an API, and for that reason the bike shedding is endless.
808
-
809
- To get a better understanding of how SemVer is intended to work over a project's lifetime,
810
- read this article from the creator of SemVer:
811
-
812
- - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
813
-
814
- </details>
815
-
816
- See [CHANGELOG.md][📌changelog] for a list of releases.
817
-
818
- ## 📄 License
819
-
820
- The gem is available as open source under the terms of
821
- the [MIT License][📄license] [![License: MIT][📄license-img]][📄license-ref].
822
- See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright-notice-explainer].
823
-
824
- ### © Copyright
825
-
826
- <ul>
827
- <li>
828
- Copyright (c) 2025 Peter H. Boling, of
829
- <a href="https://discord.gg/3qme4XHNKN">
830
- Galtzo.com
831
- <picture>
832
- <img src="https://logos.galtzo.com/assets/images/galtzo-floss/avatar-128px-blank.svg" alt="Galtzo.com Logo (Wordless) by Aboling0, CC BY-SA 4.0" width="24">
833
- </picture>
834
- </a>, and json-merge contributors.
835
- </li>
836
- </ul>
837
-
838
- ## 🤑 A request for help
839
-
840
- Maintainers have teeth and need to pay their dentists.
841
- After getting laid off in an RIF in March, and encountering difficulty finding a new one,
842
- I began spending most of my time building open source tools.
843
- I'm hoping to be able to pay for my kids' health insurance this month,
844
- so if you value the work I am doing, I need your support.
845
- Please consider sponsoring me or the project.
846
-
847
- To join the community or get help 👇️ Join the Discord.
848
-
849
- [![Live Chat on Discord][✉️discord-invite-img-ftb]][🖼️galtzo-discord]
850
-
851
- To say "thanks\!" ☝️ Join the Discord or 👇️ send money.
852
-
853
- [![Sponsor kettle-rb/json-merge on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal]
854
-
855
- ### Please give the project a star ⭐ ♥.
856
-
857
- Thanks for RTFM. ☺️
858
-
859
- [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
860
- [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
861
- [⛳liberapay]: https://liberapay.com/pboling/donate
862
- [🖇osc-all-img]: https://img.shields.io/opencollective/all/kettle-rb
863
- [🖇osc-sponsors-img]: https://img.shields.io/opencollective/sponsors/kettle-rb
864
- [🖇osc-backers-img]: https://img.shields.io/opencollective/backers/kettle-rb
865
- [🖇osc-backers]: https://opencollective.com/kettle-rb#backer
866
- [🖇osc-backers-i]: https://opencollective.com/kettle-rb/backers/badge.svg?style=flat
867
- [🖇osc-sponsors]: https://opencollective.com/kettle-rb#sponsor
868
- [🖇osc-sponsors-i]: https://opencollective.com/kettle-rb/sponsors/badge.svg?style=flat
869
- [🖇osc-all-bottom-img]: https://img.shields.io/opencollective/all/kettle-rb?style=for-the-badge
870
- [🖇osc-sponsors-bottom-img]: https://img.shields.io/opencollective/sponsors/kettle-rb?style=for-the-badge
871
- [🖇osc-backers-bottom-img]: https://img.shields.io/opencollective/backers/kettle-rb?style=for-the-badge
872
- [🖇osc]: https://opencollective.com/kettle-rb
873
- [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
874
- [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
875
- [🖇sponsor]: https://github.com/sponsors/pboling
876
- [🖇polar-img]: https://img.shields.io/badge/polar-donate-a51611.svg?style=flat
877
- [🖇polar]: https://polar.sh/pboling
878
- [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
879
- [🖇kofi]: https://ko-fi.com/O5O86SNP4
880
- [🖇patreon-img]: https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat
881
- [🖇patreon]: https://patreon.com/galtzo
882
- [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
883
- [🖇buyme-img]: https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20latte&emoji=&slug=pboling&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff
884
- [🖇buyme]: https://www.buymeacoffee.com/pboling
885
- [🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
886
- [🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
887
- [🖇paypal]: https://www.paypal.com/paypalme/peterboling
888
- [🖇floss-funding.dev]: https://floss-funding.dev
889
- [🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
890
- [✉️discord-invite]: https://discord.gg/3qme4XHNKN
891
- [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
892
- [✉️ruby-friends-img]: https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white
893
- [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
894
- [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
895
- [⛳️gem-namespace]: https://github.com/kettle-rb/json-merge
896
- [⛳️namespace-img]: https://img.shields.io/badge/namespace-Json::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white
897
- [⛳️gem-name]: https://bestgems.org/gems/json-merge
898
- [⛳️name-img]: https://img.shields.io/badge/name-json--merge-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
899
- [⛳️tag-img]: https://img.shields.io/github/tag/kettle-rb/json-merge.svg
900
- [⛳️tag]: http://github.com/kettle-rb/json-merge/releases
901
- [🚂maint-blog]: http://www.railsbling.com/tags/json-merge
902
- [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
903
- [🚂maint-contact]: http://www.railsbling.com/contact
904
- [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
905
- [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
906
- [💖🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling
907
- [💖✌️wellfound]: https://wellfound.com/u/peter-boling
908
- [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
909
- [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
910
- [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
911
- [💖🐘ruby-mast]: https://ruby.social/@galtzo
912
- [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
913
- [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
914
- [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
915
- [💖🌳linktree]: https://linktr.ee/galtzo
916
- [💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
917
- [💖💁🏼‍♂️devto]: https://dev.to/galtzo
918
- [💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
919
- [💖💁🏼‍♂️aboutme]: https://about.me/peter.boling
920
- [💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
921
- [💖🧊berg]: https://codeberg.org/pboling
922
- [💖🐙hub]: https://github.org/pboling
923
- [💖🛖hut]: https://sr.ht/~galtzo/
924
- [💖🧪lab]: https://gitlab.com/pboling
925
- [👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
926
- [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
927
- [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
928
- [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
929
- [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-json-merge?utm_source=rubygems-json-merge&utm_medium=referral&utm_campaign=readme
930
- [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
931
- [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
932
- [💁🏼‍♂️peterboling]: http://www.peterboling.com
933
- [🚂railsbling]: http://www.railsbling.com
934
- [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
935
- [📜src-gl]: https://gitlab.com/kettle-rb/json-merge/
936
- [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
937
- [📜src-cb]: https://codeberg.org/kettle-rb/json-merge
938
- [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green
939
- [📜src-gh]: https://github.com/kettle-rb/json-merge
940
- [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
941
- [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
942
- [📜gl-wiki]: https://gitlab.com/kettle-rb/json-merge/-/wikis/home
943
- [📜gh-wiki]: https://github.com/kettle-rb/json-merge/wiki
944
- [📜gl-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
945
- [📜gh-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
946
- [👽dl-rank]: https://bestgems.org/gems/json-merge
947
- [👽dl-ranki]: https://img.shields.io/gem/rd/json-merge.svg
948
- [👽oss-help]: https://www.codetriage.com/kettle-rb/json-merge
949
- [👽oss-helpi]: https://www.codetriage.com/kettle-rb/json-merge/badges/users.svg
950
- [👽version]: https://bestgems.org/gems/json-merge
951
- [👽versioni]: https://img.shields.io/gem/v/json-merge.svg
952
- [🏀qlty-mnt]: https://qlty.sh/gh/kettle-rb/projects/json-merge
953
- [🏀qlty-mnti]: https://qlty.sh/gh/kettle-rb/projects/json-merge/maintainability.svg
954
- [🏀qlty-cov]: https://qlty.sh/gh/kettle-rb/projects/json-merge/metrics/code?sort=coverageRating
955
- [🏀qlty-covi]: https://qlty.sh/gh/kettle-rb/projects/json-merge/coverage.svg
956
- [🏀codecov]: https://codecov.io/gh/kettle-rb/json-merge
957
- [🏀codecovi]: https://codecov.io/gh/kettle-rb/json-merge/graph/badge.svg
958
- [🏀coveralls]: https://coveralls.io/github/kettle-rb/json-merge?branch=main
959
- [🏀coveralls-img]: https://coveralls.io/repos/github/kettle-rb/json-merge/badge.svg?branch=main
960
- [🖐codeQL]: https://github.com/kettle-rb/json-merge/security/code-scanning
961
- [🖐codeQL-img]: https://github.com/kettle-rb/json-merge/actions/workflows/codeql-analysis.yml/badge.svg
962
- [🚎2-cov-wf]: https://github.com/kettle-rb/json-merge/actions/workflows/coverage.yml
963
- [🚎2-cov-wfi]: https://github.com/kettle-rb/json-merge/actions/workflows/coverage.yml/badge.svg
964
- [🚎3-hd-wf]: https://github.com/kettle-rb/json-merge/actions/workflows/heads.yml
965
- [🚎3-hd-wfi]: https://github.com/kettle-rb/json-merge/actions/workflows/heads.yml/badge.svg
966
- [🚎5-st-wf]: https://github.com/kettle-rb/json-merge/actions/workflows/style.yml
967
- [🚎5-st-wfi]: https://github.com/kettle-rb/json-merge/actions/workflows/style.yml/badge.svg
968
- [🚎6-s-wf]: https://github.com/kettle-rb/json-merge/actions/workflows/supported.yml
969
- [🚎6-s-wfi]: https://github.com/kettle-rb/json-merge/actions/workflows/supported.yml/badge.svg
970
- [🚎9-t-wf]: https://github.com/kettle-rb/json-merge/actions/workflows/truffle.yml
971
- [🚎9-t-wfi]: https://github.com/kettle-rb/json-merge/actions/workflows/truffle.yml/badge.svg
972
- [🚎11-c-wf]: https://github.com/kettle-rb/json-merge/actions/workflows/current.yml
973
- [🚎11-c-wfi]: https://github.com/kettle-rb/json-merge/actions/workflows/current.yml/badge.svg
974
- [🚎12-crh-wf]: https://github.com/kettle-rb/json-merge/actions/workflows/dep-heads.yml
975
- [🚎12-crh-wfi]: https://github.com/kettle-rb/json-merge/actions/workflows/dep-heads.yml/badge.svg
976
- [🚎13-🔒️-wf]: https://github.com/kettle-rb/json-merge/actions/workflows/locked_deps.yml
977
- [🚎13-🔒️-wfi]: https://github.com/kettle-rb/json-merge/actions/workflows/locked_deps.yml/badge.svg
978
- [🚎14-🔓️-wf]: https://github.com/kettle-rb/json-merge/actions/workflows/unlocked_deps.yml
979
- [🚎14-🔓️-wfi]: https://github.com/kettle-rb/json-merge/actions/workflows/unlocked_deps.yml/badge.svg
980
- [🚎15-🪪-wf]: https://github.com/kettle-rb/json-merge/actions/workflows/license-eye.yml
981
- [🚎15-🪪-wfi]: https://github.com/kettle-rb/json-merge/actions/workflows/license-eye.yml/badge.svg
982
- [💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white
983
- [💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white
984
- [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
985
- [💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue
986
- [💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
987
- [💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green
988
- [💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue
989
- [💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green
990
- [💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue
991
- [🤝gh-issues]: https://github.com/kettle-rb/json-merge/issues
992
- [🤝gh-pulls]: https://github.com/kettle-rb/json-merge/pulls
993
- [🤝gl-issues]: https://gitlab.com/kettle-rb/json-merge/-/issues
994
- [🤝gl-pulls]: https://gitlab.com/kettle-rb/json-merge/-/merge_requests
995
- [🤝cb-issues]: https://codeberg.org/kettle-rb/json-merge/issues
996
- [🤝cb-pulls]: https://codeberg.org/kettle-rb/json-merge/pulls
997
- [🤝cb-donate]: https://donate.codeberg.org/
998
- [🤝contributing]: CONTRIBUTING.md
999
- [🏀codecov-g]: https://codecov.io/gh/kettle-rb/json-merge/graphs/tree.svg
1000
- [🖐contrib-rocks]: https://contrib.rocks
1001
- [🖐contributors]: https://github.com/kettle-rb/json-merge/graphs/contributors
1002
- [🖐contributors-img]: https://contrib.rocks/image?repo=kettle-rb/json-merge
1003
- [🚎contributors-gl]: https://gitlab.com/kettle-rb/json-merge/-/graphs/main
1004
- [🪇conduct]: CODE_OF_CONDUCT.md
1005
- [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
1006
- [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
1007
- [📌semver]: https://semver.org/spec/v2.0.0.html
1008
- [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
1009
- [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
1010
- [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
1011
- [📌changelog]: CHANGELOG.md
1012
- [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
1013
- [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
1014
- [📌gitmoji]: https://gitmoji.dev
1015
- [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1016
- [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1017
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.631-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1018
- [🔐security]: SECURITY.md
1019
- [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1020
- [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
1021
- [📄license]: LICENSE.txt
1022
- [📄license-ref]: https://opensource.org/licenses/MIT
1023
- [📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg
1024
- [📄license-compat]: https://dev.to/galtzo/how-to-check-license-compatibility-41h0
1025
- [📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache
1026
- [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
1027
- [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
1028
- [🚎yard-current]: http://rubydoc.info/gems/json-merge
1029
- [🚎yard-head]: https://json-merge.galtzo.com
1030
- [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
1031
- [💎SHA_checksums]: https://gitlab.com/kettle-rb/json-merge/-/tree/main/checksums
1032
- [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
1033
- [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
1034
- [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
1035
- [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
1036
- [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/