jfather 0.2.0 → 0.4.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.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Sébastien Règne
3
+ Copyright (c) 2024-2025 Sébastien Règne
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -5,19 +5,24 @@
5
5
  <!-- markdownlint-disable-next-line no-inline-html-->
6
6
  <img src="asset/logo.svg" align="right" alt="">
7
7
 
8
- [![npm][img-npm]][link-npm]
9
- [![build][img-build]][link-build]
10
- [![coverage][img-coverage]][link-coverage]
11
- [![semver][img-semver]][link-semver]
8
+ [![npm][img-npm]][link-npm] [![build][img-build]][link-build]
9
+ [![coverage][img-coverage]][link-coverage] [![semver][img-semver]][link-semver]
12
10
 
13
11
  > _Boys use JSON; Men use JFather._
14
12
 
15
13
  ## Overview
16
14
 
17
- JFather is a utility library to **merge**, **extend** and **override**
18
- [JSON](https://www.json.org/json-fr.html "JavaScript Object Notation") objects.
15
+ JFather is a
16
+ [JSON](https://www.json.org/json-en.html "JavaScript Object Notation") utility
17
+ library to:
19
18
 
20
- ```JavaScript
19
+ - [**merge**](#merge) deeply two JSON objects.
20
+ - [**extend**](#extend) a JSON objects with `"$extends"` property.
21
+ - [**override**](#override) an array with `"$foo[0]"` (replace a value) or
22
+ `"$foo[]"` (append values) properties.
23
+
24
+ <!-- prettier-ignore-start -->
25
+ ```javascript
21
26
  import JFather from "jfather";
22
27
 
23
28
  // Merge two objects.
@@ -47,8 +52,8 @@ console.log(extended);
47
52
  // "quote": "With great fist comes great KO"
48
53
  // }
49
54
 
50
- // Override an object.
51
- const overridden = await JFather.merge(
55
+ // Override arrays of an object.
56
+ const overridden = JFather.merge(
52
57
  { "foo": ["a", "alpha"] },
53
58
  { "$foo[0]": "A", "$foo[]": ["BETA"] }
54
59
  );
@@ -80,16 +85,17 @@ console.log(allIn);
80
85
  // "quote": "I'm no God. I'm not even a man. I'm just Molecule Man."
81
86
  // }
82
87
  ```
88
+ <!-- prettier-ignore-end -->
83
89
 
84
90
  ## Installation
85
91
 
86
92
  JFather is published on [npm][link-npm] (its CDN:
87
93
  [esm.sh](https://esm.sh/jfather),
88
94
  [jsDelivr](https://www.jsdelivr.com/package/npm/jfather),
89
- [UNPKG](https://unpkg.com/browse/jfather/)) and
90
- [Deno](https://deno.land/x/jfather).
95
+ [UNPKG](https://unpkg.com/browse/jfather/)),
96
+ [JSR](https://jsr.io/@regseb/jfather) and [Deno](https://deno.land/x/jfather).
91
97
 
92
- ```JavaScript
98
+ ```javascript
93
99
  // Node.js and Bun (after `npm install jfather`):
94
100
  import JFather from "jfather";
95
101
 
@@ -98,242 +104,308 @@ import JFather from "https://esm.sh/jfather@0";
98
104
  import JFather from "https://cdn.jsdelivr.net/npm/jfather@0";
99
105
  import JFather from "https://unpkg.com/jfather@0";
100
106
 
101
- // Deno:
102
- import JFather from "https://deno.land/x/jfather/mod.js";
107
+ // Deno (after `deno add jsr:@regseb/jfather`):
108
+ import JFather from "jsr:@regseb/jfather";
103
109
  ```
104
110
 
105
111
  ## Features
106
112
 
107
113
  ### Merge
108
114
 
109
- <!-- markdownlint-disable no-inline-html -->
110
- <table>
111
- <tr>
112
- <th><code>parent</code></th>
113
- <th><code>child</code></th>
114
- <th><code>JFather.merge(parent, child)</code></th>
115
- </tr>
116
- <tr>
117
- <td><pre lang="JSON"><code>1</code></pre></td>
118
- <td><pre lang="JSON"><code>2</code></pre></td>
119
- <td><pre lang="JSON"><code>2</code></pre></td>
120
- </tr>
121
- <tr>
122
- <td><pre lang="JSON"><code>{
123
- "foo": "alpha",
124
- "bar": "ALPHA"
125
- }</code></pre></td>
126
- <td><pre lang="JSON"><code>{
127
- "foo": "beta",
128
- "baz": "BETA"
129
- }</code></pre></td>
130
- <td><pre lang="JSON"><code>{
131
- "foo": "beta",
132
- "bar": "ALPHA",
133
- "baz": "BETA"
134
- }</code></pre></td>
135
- </tr>
136
- <tr>
137
- <td><pre lang="JSON"><code>{
138
- "foo": [1, 10, 11]
139
- }</code></pre></td>
140
- <td><pre lang="JSON"><code>{
141
- "foo": [2, 20, 22]
142
- }</code></pre></td>
143
- <td><pre lang="JSON"><code>{
144
- "foo": [2, 20, 22]
145
- }</code></pre></td>
146
- </tr>
147
- </table>
148
- <!-- markdownlint-enable no-inline-html -->
115
+ With any two variable types (except objects), merge returns the value of the
116
+ child. The following example shows how to use it with numbers: the result is `2`
117
+ (retrieved from the child value).
118
+
119
+ ```javascript
120
+ const parent = 1;
121
+ const child = 2;
122
+ console.log(JFather.merge(parent, child));
123
+ // 2
124
+ ```
125
+
126
+ If both variables are objects, the object properties are merged one by one. In
127
+ this example, the `"foo"` property overwrites that of the parent. The properties
128
+ `"bar"` and `"baz"` are simply copied, as they are only in either the parent or
129
+ the child.
130
+
131
+ <!-- prettier-ignore-start -->
132
+ ```javascript
133
+ const parent = { "foo": "alpha", "bar": "ALPHA" };
134
+ const child = { "foo": "beta", "baz": "BETA" };
135
+ console.log(JFather.merge(parent, child));
136
+ // { "foo": "beta", "bar": "ALPHA", "baz": "BETA" }
137
+ ```
138
+ <!-- prettier-ignore-end -->
139
+
140
+ Merging is done recursively. The following example shows the merging of two
141
+ objects, which in turn contains the merging of the `"foo"` sub-objects.
142
+
143
+ <!-- prettier-ignore-start -->
144
+ ```javascript
145
+ const parent = {
146
+ "foo": { "bar": 1, "baz": 2 },
147
+ "qux": "a"
148
+ };
149
+ const child = {
150
+ "foo": { "bar": 10, "quux": 20 },
151
+ "corge": "b"
152
+ };
153
+ console.log(JFather.merge(parent, child));
154
+ // {
155
+ // "foo": { "bar": 10, "baz": 2, "quux": 20 },
156
+ // "qux": "a",
157
+ // "corge": "b"
158
+ // }
159
+ ```
160
+ <!-- prettier-ignore-end -->
161
+
162
+ Arrays are processed like any other type: the value of the child overrides that
163
+ of the parent. For more detailed merging, see the [_Override_](#override)
164
+ chapter, which shows how to merge arrays.
165
+
166
+ <!-- prettier-ignore-start -->
167
+ ```javascript
168
+ const parent = { "foo": [1, 10, 11] };
169
+ const child = { "foo": [2, 20, 22] };
170
+ console.log(JFather.merge(parent, child));
171
+ // { "foo": [2, 20, 22] }
172
+ ```
173
+ <!-- prettier-ignore-end -->
149
174
 
150
175
  ### Extend
151
176
 
152
- <!-- markdownlint-disable no-inline-html -->
153
- <table>
154
- <tr>
155
- <th><code>https://foo.bar/parent.json</code></th>
156
- <th><code>child</code></th>
157
- <th><code>await JFather.extend(child)</code></th>
158
- </tr>
159
- <tr>
160
- <td><pre lang="JSON"><code>{
161
- "baz": "qux"
162
- }</code></pre></td>
163
- <td><pre lang="JSON"><code>{
164
- "$extends": "https://foo.bar/parent.json"
165
- }</code></pre></td>
166
- <td><pre lang="JSON"><code>{
167
- "baz": "qux"
168
- }</code></pre></td>
169
- </tr>
170
- <tr>
171
- <td><pre lang="JSON"><code>{
172
- "baz": "qux"
173
- }</code></pre></td>
174
- <td><pre lang="JSON"><code>{
175
- "$extends": "https://foo.bar/parent.json",
176
- "baz": "quux"
177
- }</code></pre></td>
178
- <td><pre lang="JSON"><code>{
179
- "baz": "quux"
180
- }</code></pre></td>
181
- </tr>
182
- <tr>
183
- <td><pre lang="JSON"><code>{
184
- "baz": "qux"
185
- }</code></pre></td>
186
- <td><pre lang="JSON"><code>{
187
- "$extends": "https://foo.bar/parent.json",
188
- "quux": "corge"
189
- }</code></pre></td>
190
- <td><pre lang="JSON"><code>{
191
- "baz": "qux",
192
- "quux": "corge"
193
- }</code></pre></td>
194
- </tr>
195
- <tr>
196
- <td><pre lang="JSON"><code>{
197
- "baz": "qux"
198
- }</code></pre></td>
199
- <td><pre lang="JSON"><code>{
200
- "quux": {
201
- "$extends": "https://foo.bar/parent.json",
202
- "corge": "grault"
203
- }
204
- }</code></pre></td>
205
- <td><pre lang="JSON"><code>{
206
- "quux": {
207
- "baz": "qux",
208
- "corge": "grault"
177
+ You can extend an object using the `"$extends"` property, which must link to a
178
+ JSON file. The remote JSON file and the current object will be merged.
179
+
180
+ In this example, the child object is empty (except for the `"$extends"`
181
+ property). The result therefore contains the parent object.
182
+
183
+ <!-- prettier-ignore-start -->
184
+ ```javascript
185
+ // https://example.com/parent.json
186
+ // { "foo": 42 }
187
+
188
+ const obj = { "$extends": "https://example.com/parent.json" };
189
+ console.log(await JFather.extend(obj));
190
+ // { "foo": 42 }
191
+ ```
192
+ <!-- prettier-ignore-end -->
193
+
194
+ As with merge, if a property is in both parent and child, the child's value is
195
+ used. Otherwise, both parent and child properties are added to the result.
196
+
197
+ <!-- prettier-ignore-start -->
198
+ ```javascript
199
+ // https://example.com/parent.json
200
+ // { "foo": "A", "bar": "Alpha" }
201
+
202
+ const obj = {
203
+ "$extends": "https://example.com/parent.json",
204
+ "foo": "B",
205
+ "baz": "Beta"
206
+ };
207
+ console.log(await JFather.extend(obj));
208
+ // { "foo": "B", "bar": "Alpha", "baz": "Beta" }
209
+ ```
210
+ <!-- prettier-ignore-end -->
211
+
212
+ It is possible to extend a child's sub-object. In the example below, the parent
213
+ is merged with the child's `"bar"` sub-object.
214
+
215
+ <!-- prettier-ignore-start -->
216
+ ```javascript
217
+ // https://example.com/parent.json
218
+ // { "foo": 42 }
219
+
220
+ const obj = {
221
+ "bar": {
222
+ "$extends": "https://example.com/parent.json",
223
+ "baz": 3.14
209
224
  }
210
- }</code></pre></td>
211
- </tr>
212
- <tr>
213
- <td><pre lang="JSON"><code>{
214
- "baz": {
215
- "qux": [1, 2],
216
- "quux": "a"
217
- },
218
- "corge": true
219
- }</code></pre></td>
220
- <td><pre lang="JSON"><code>{
221
- "$extends": "https://foo.bar/parent.json#baz"
222
- }</code></pre></td>
223
- <td><pre lang="JSON"><code>{
224
- "qux": [1, 2],
225
- "quux": "a"
226
- }</code></pre></td>
227
- </tr>
228
- </table>
229
- <!-- markdownlint-enable no-inline-html -->
225
+ };
226
+ console.log(await JFather.extend(obj));
227
+ // {
228
+ // "bar": { "foo": 42, "baz": 3.14 }
229
+ // }
230
+ ```
231
+ <!-- prettier-ignore-end -->
232
+
233
+ In the parent link, you can define a path to retrieve a sub-object from the
234
+ parent. The path is set in the URL hash:
235
+
236
+ - `#foo`: the value of the `"foo"` property;
237
+ - `#foo.bar`: the value of the `"bar"` sub-property in the `"foo"` property;
238
+ - `#foo[42]`: the value of the forty-third array element in the `"foo"`
239
+ property;
240
+ - `#foo[0].bar`: the value of the sub-property `"bar"` in the first element of
241
+ the array in the property `"foo"`.
242
+
243
+ This example merges the `"foo"` property of the parent with the child.
244
+
245
+ <!-- prettier-ignore-start -->
246
+ ```javascript
247
+ // https://example.com/parent.json
248
+ // {
249
+ // "foo": { "bar": [1, 2], "baz": "a" },
250
+ // "qux": true
251
+ // }
252
+
253
+ const obj = { "$extends": "https://example.com/parent.json#foo" };
254
+ console.log(await JFather.extend(obj));
255
+ // { "bar": [1, 2], "baz": "a" }
256
+ ```
257
+ <!-- prettier-ignore-end -->
230
258
 
231
259
  ### Override
232
260
 
233
- <!-- markdownlint-disable no-inline-html -->
234
- <table>
235
- <tr>
236
- <th><code>parent</code></th>
237
- <th><code>child</code></th>
238
- <th><code>JFather.merge(parent, child)</code></th>
239
- </tr>
240
- <tr>
241
- <td><pre lang="JSON"><code>{
242
- "foo": ["a", "Alpha"]
243
- }</code></pre></td>
244
- <td><pre lang="JSON"><code>{
245
- "$foo[]": ["b", "Beta"]
246
- }</code></pre></td>
247
- <td><pre lang="JSON"><code>{
248
- "foo": ["a", "Alpha", "b", "Beta"]
249
- }</code></pre></td>
250
- </tr>
251
- <tr>
252
- <td><pre lang="JSON"><code>{
253
- "foo": ["a", "Alpha"]
254
- }</code></pre></td>
255
- <td><pre lang="JSON"><code>{
256
- "$foo[0]": "A"
257
- }</code></pre></td>
258
- <td><pre lang="JSON"><code>{
259
- "foo": ["A", "Alpha"]
260
- }</code></pre></td>
261
- </tr>
262
- <tr>
263
- <td><pre lang="JSON"><code>{
264
- "foo": [{ "bar": ["a"] }]
265
- }</code></pre></td>
266
- <td><pre lang="JSON"><code>{
267
- "$foo[0]": { "$bar[]": ["b", "c"] }
268
- }</code></pre></td>
269
- <td><pre lang="JSON"><code>{
270
- "foo": [{ "bar": ["a", "b", "c"] }]
271
- }</code></pre></td>
272
- </tr>
273
- </table>
274
- <!-- markdownlint-enable no-inline-html -->
261
+ If an object has arrays, the merge overwrites the parent's array with the
262
+ child's. With the properties `"$foo[42]"` and `"$foo[]"`, you can refine the
263
+ merge.
264
+
265
+ In this example, the array `"foo"` is not overwritten. The first value of the
266
+ `"foo"` array is merged with the value of the child's `"$foo[0]"` property. And
267
+ the second value of `"foo"` is copied into the result.
268
+
269
+ <!-- prettier-ignore-start -->
270
+ ```javascript
271
+ const parent = { "foo": ["a", "Alpha"] };
272
+ const child = { "$foo[0]": "B" };
273
+ console.log(JFather.merge(parent, child));
274
+ // { "foo": ["B", "Alpha"] }
275
+ ```
276
+ <!-- prettier-ignore-end -->
277
+
278
+ With `"$foo[]"`, the child's values are added to those of the parent. In the
279
+ example below, the values `"b"` and `"Beta"` are added to the array of the
280
+ `"foo"` property.
281
+
282
+ <!-- prettier-ignore-start -->
283
+ ```javascript
284
+ const parent = { "foo": ["a", "Alpha"] };
285
+ const child = { "$foo[]": ["b", "Beta"] };
286
+ console.log(JFather.merge(parent, child));
287
+ // { "foo": ["a", "Alpha", "b", "Beta"] }
288
+ ```
289
+ <!-- prettier-ignore-end -->
290
+
291
+ You can combine the two overloads to, for example:
292
+
293
+ - merge the first value of the parent's `"foo"` array with the value of the
294
+ child's `"$foo[0]"` property;
295
+ - add the values `"b"` and `"c"` to the array of the sub-property `"bar"`.
296
+
297
+ <!-- prettier-ignore-start -->
298
+ ```javascript
299
+ const parent = {
300
+ "foo": [{
301
+ "bar": ["a"]
302
+ }]
303
+ };
304
+ const child = {
305
+ "$foo[0]": {
306
+ "$bar[]": ["b", "c"]
307
+ }
308
+ };
309
+ console.log(JFather.merge(parent, child));
310
+ // {
311
+ // "foo": [{
312
+ // "bar": ["a", "b", "c"]
313
+ // }]
314
+ // }
315
+ ```
316
+ <!-- prettier-ignore-end -->
317
+
318
+ If the child overloads a property that does not exist in the parent, the
319
+ overload is ignored. The overload is also ignored if the parent object is not an
320
+ array. In the following example, the child has two overloads which are ignored:
321
+ the overload on the property `"bar"` which is not an array, and the overload on
322
+ `"baz"` which does not exist in the parent.
323
+
324
+ <!-- prettier-ignore-start -->
325
+ ```javascript
326
+ const parent = { "foo": ["a", "A"], "bar": 42 };
327
+ const child = { "$bar[0]": 3.14, "$baz[]": ["beta"] };
328
+ console.log(JFather.merge(parent, child));
329
+ // { "foo": ["a", "A"], "bar": 42 }
330
+ ```
331
+ <!-- prettier-ignore-end -->
275
332
 
276
333
  ## API
277
334
 
278
- - [`merge()`](#merge)
279
- - [`extend()`](#extend)
280
- - [`load()`](#load)
281
- - [`parse()`](#parse)
335
+ - [`JFather.merge(parent, child)`](#jfathermergeparent-child)
336
+ - [`JFather.extend(obj, [options])`](#jfatherextendobj-options)
337
+ - [`JFather.load(url, [options])`](#jfatherloadurl-options)
338
+ - [`JFather.parse(text, [options])`](#jfatherparsetext-options)
282
339
 
283
- ### `merge()`
340
+ ### `JFather.merge(parent, child)`
284
341
 
285
342
  Merge and override `parent` with `child`.
286
343
 
287
- ```JavaScript
288
- JFather.merge(parent, child);
289
- ```
290
-
291
344
  - Parameters:
292
- - `parent`: The parent object.
293
- - `child`: The child object.
294
- - Returns: The merged object.
345
+ - `parent` [`<any>`][mdn-any] The parent object.
346
+ - `child` [`<any>`][mdn-any] The child object.
347
+ - Returns: [`<any>`][mdn-any] The merged object.
295
348
 
296
- ### `extend()`
349
+ ### `JFather.extend(obj, [options])`
297
350
 
298
351
  Extend `obj`, merge and override.
299
352
 
300
- ```JavaScript
301
- JFather.extend(obj);
302
- ```
303
-
304
353
  - Parameter:
305
- - `obj`: The object with any `$extends` properties.
306
- - Returns: A promise with the extended object.
354
+ - `obj` [`<any>`][mdn-any] The object with any `$extends` properties.
355
+ - `options` [`<Object>`][mdn-object]
356
+ - `request` [`<Function>`][mdn-function] The function for getting a JSON
357
+ object remotely. By default, the object is got with [`fetch()`][mdn-fetch]
358
+ and [`Response.json()`][mdn-response-json].
359
+ - Returns: [`<Promise>`][mdn-promise] A promise with the extended object.
307
360
 
308
- ### `load()`
361
+ ### `JFather.load(url, [options])`
309
362
 
310
- Load from a `url`, extend, merge and override.
311
-
312
- ```JavaScript
313
- JFather.load(url);
314
- ```
363
+ Load from an `url`, extend, merge and override.
315
364
 
316
365
  - Parameter:
317
- - `url`: The string containing the URL of a JSON file.
318
- - Returns: A promise with the loaded object.
366
+ - `url` [`<String>`][mdn-string] | [`<URL>`][mdn-url] The string containing
367
+ the URL of a JSON file.
368
+ - `options` [`<Object>`][mdn-object]
369
+ - `request` [`<Function>`][mdn-function] The function for getting a JSON
370
+ object remotely. By default, the object is got with [`fetch()`][mdn-fetch]
371
+ and [`Response.json()`][mdn-response-json].
372
+ - Returns: [`<Promise>`][mdn-promise] A promise with the loaded object.
319
373
 
320
- ### `parse()`
374
+ ### `JFather.parse(text, [options])`
321
375
 
322
376
  Parse a `text`, extend, merge and override.
323
377
 
324
- ```JavaScript
325
- JFather.parse(text);
326
- ```
327
-
328
378
  - Parameter:
329
- - `text`: The string containing a JSON object.
330
- - Returns: A promise with the parsed object.
331
-
332
- [img-npm]: https://img.shields.io/npm/dm/jfather?label=npm&logo=npm&logoColor=whitesmoke
333
- [img-build]: https://img.shields.io/github/actions/workflow/status/regseb/jfather/ci.yml?branch=main&logo=github&logoColor=whitesmoke
334
- [img-coverage]: https://img.shields.io/endpoint?label=coverage&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fregseb%2Fjfather%2Fmain&logo=stryker&logoColor=whitesmoke
335
- [img-semver]: https://img.shields.io/badge/semver-2.0.0-blue?logo=semver&logoColor=whitesmoke
379
+ - `text` [`<String>`][mdn-string] The string containing a JSON object.
380
+ - `options` [`<Object>`][mdn-object]
381
+ - `request` [`<Function>`][mdn-function] The function for getting a JSON
382
+ object remotely. By default, the object is got with [`fetch()`][mdn-fetch]
383
+ and [`Response.json()`][mdn-response-json].
384
+ - Returns: [`<Promise>`][mdn-promise] A promise with the parsed object.
385
+
386
+ [mdn-any]: https://developer.mozilla.org/Web/JavaScript/Data_structures
387
+ [mdn-function]:
388
+ https://developer.mozilla.org/JavaScript/Reference/Global_Objects/Function
389
+ [mdn-object]:
390
+ https://developer.mozilla.org/JavaScript/Reference/Global_Objects/Object
391
+ [mdn-promise]:
392
+ https://developer.mozilla.org/JavaScript/Reference/Global_Objects/Promise
393
+ [mdn-string]:
394
+ https://developer.mozilla.org/JavaScript/Reference/Global_Objects/String
395
+ [mdn-fetch]: https://developer.mozilla.org/Web/API/fetch
396
+ [mdn-response-json]: https://developer.mozilla.org/Web/API/Response/json
397
+ [mdn-url]: https://developer.mozilla.org/Web/API/URL
398
+ [img-npm]:
399
+ https://img.shields.io/npm/dm/jfather?label=npm&logo=npm&logoColor=whitesmoke
400
+ [img-build]:
401
+ https://img.shields.io/github/actions/workflow/status/regseb/jfather/ci.yml?branch=main&logo=github&logoColor=whitesmoke
402
+ [img-coverage]:
403
+ https://img.shields.io/endpoint?label=coverage&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fregseb%2Fjfather%2Fmain
404
+ [img-semver]:
405
+ https://img.shields.io/badge/semver-2.0.0-blue?logo=semver&logoColor=whitesmoke
336
406
  [link-npm]: https://www.npmjs.com/package/jfather
337
- [link-build]: https://github.com/regseb/jfather/actions/workflows/ci.yml?query=branch%3Amain
338
- [link-coverage]: https://dashboard.stryker-mutator.io/reports/github.com/regseb/jfather/main
407
+ [link-build]:
408
+ https://github.com/regseb/jfather/actions/workflows/ci.yml?query=branch%3Amain
409
+ [link-coverage]:
410
+ https://dashboard.stryker-mutator.io/reports/github.com/regseb/jfather/main
339
411
  [link-semver]: https://semver.org/spec/v2.0.0.html "Semantic Versioning 2.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jfather",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "JSON with merge, extend and override.",
5
5
  "keywords": [
6
6
  "jfather",
@@ -18,7 +18,10 @@
18
18
  },
19
19
  "license": "MIT",
20
20
  "author": "Sébastien Règne <regseb@gmail.com> (https://github.com/regseb)",
21
- "funding": "https://www.paypal.me/sebastienregne",
21
+ "funding": [
22
+ "https://buymeacoffee.com/regseb",
23
+ "https://www.paypal.me/sebastienregne"
24
+ ],
22
25
  "files": [
23
26
  "./src/",
24
27
  "./types/"
@@ -39,42 +42,41 @@
39
42
  "lint:fix": "metalint --fix",
40
43
  "lint:types": "tsc --project .tsconfig_lint.json",
41
44
  "test": "npm run test:coverage",
42
- "test:unit": "mocha --config test/mocharc.json",
45
+ "test:unit": "node --test",
43
46
  "test:coverage": "stryker run",
44
47
  "jsdocs": "typedoc --tsconfig .tsconfig_jsdocs.json",
45
48
  "prepare": "tsc --project .tsconfig_types.json",
46
49
  "clean": "node .script/clean.js"
47
50
  },
48
51
  "devDependencies": {
49
- "@prantlf/jsonlint": "14.0.3",
50
- "@prettier/plugin-xml": "3.3.1",
51
- "@stryker-mutator/core": "8.2.6",
52
- "@stryker-mutator/mocha-runner": "8.2.6",
53
- "@types/mocha": "10.0.6",
54
- "@types/node": "20.11.24",
55
- "@types/sinon": "17.0.3",
56
- "eslint": "8.57.0",
57
- "eslint-plugin-array-func": "4.0.0",
52
+ "@prantlf/jsonlint": "16.0.0",
53
+ "@prettier/plugin-xml": "3.4.1",
54
+ "@stryker-mutator/core": "8.7.1",
55
+ "@stryker-mutator/tap-runner": "8.7.1",
56
+ "@types/eslint-plugin-mocha": "10.4.0",
57
+ "@types/node": "22.10.5",
58
+ "eslint": "9.17.0",
59
+ "eslint-plugin-array-func": "5.0.2",
58
60
  "eslint-plugin-eslint-comments": "3.2.0",
59
- "eslint-plugin-import": "2.29.1",
60
- "eslint-plugin-jsdoc": "48.2.0",
61
- "eslint-plugin-mocha": "10.3.0",
62
- "eslint-plugin-n": "16.6.2",
63
- "eslint-plugin-no-unsanitized": "4.0.2",
64
- "eslint-plugin-promise": "6.1.1",
65
- "eslint-plugin-regexp": "2.2.0",
66
- "eslint-plugin-unicorn": "51.0.1",
67
- "markdownlint": "0.33.0",
68
- "metalint": "0.15.0",
69
- "mocha": "10.3.0",
70
- "npm-package-json-lint": "7.1.0",
71
- "prettier": "3.2.5",
72
- "sinon": "17.0.1",
73
- "typedoc": "0.25.10",
74
- "typescript": "5.3.3",
61
+ "eslint-plugin-import": "2.31.0",
62
+ "eslint-plugin-jsdoc": "50.6.1",
63
+ "eslint-plugin-mocha": "10.5.0",
64
+ "eslint-plugin-n": "17.15.1",
65
+ "eslint-plugin-no-unsanitized": "4.1.2",
66
+ "eslint-plugin-promise": "7.2.1",
67
+ "eslint-plugin-regexp": "2.7.0",
68
+ "eslint-plugin-unicorn": "56.0.1",
69
+ "globals": "15.14.0",
70
+ "markdownlint": "0.37.3",
71
+ "metalint": "0.19.0",
72
+ "npm-package-json-lint": "8.0.0",
73
+ "prettier": "3.4.2",
74
+ "publint": "0.2.12",
75
+ "typedoc": "0.27.6",
76
+ "typescript": "5.7.2",
75
77
  "yaml-lint": "1.7.0"
76
78
  },
77
79
  "engines": {
78
- "node": ">=20.6.0"
80
+ "node": ">=20.18"
79
81
  }
80
82
  }
package/src/jfather.js CHANGED
@@ -4,6 +4,15 @@
4
4
  * @author Sébastien Règne
5
5
  */
6
6
 
7
+ /**
8
+ * Les options des fonctions de JFather.
9
+ *
10
+ * @typedef {Object} Options
11
+ * @prop {Function} [request] La fonction pour récupérer un objet JSON à
12
+ * distance. Par défaut, l'objet est récupéré avec
13
+ * `fetch()` et `Response.json()`.
14
+ */
15
+
7
16
  /**
8
17
  * Exécute une fonction sur un objet et tous ses sous-objets (en partant des
9
18
  * objets les plus profonds).
@@ -80,11 +89,11 @@ export const query = function (obj, chain) {
80
89
  return obj;
81
90
  }
82
91
 
83
- const re = /^\.(?<prop>\w+)|^\[(?<index>\d+)\]/u;
92
+ const re = /^\.(?<prop>\w+)|^\[(?<index>\d+)\]/v;
84
93
  const sub = {
85
94
  obj,
86
95
  // Préfixer le chemin avec un point si nécessaire.
87
- chain: /^[.[]/u.test(chain) ? chain : "." + chain,
96
+ chain: /^[.\[]/v.test(chain) ? chain : "." + chain,
88
97
  };
89
98
  while (0 !== sub.chain.length) {
90
99
  const result = re.exec(sub.chain);
@@ -109,7 +118,11 @@ export const query = function (obj, chain) {
109
118
  * @returns {any} La fusion des deux objets.
110
119
  */
111
120
  export const merge = function (parent, child) {
112
- if (Object !== parent?.constructor || Object !== child?.constructor) {
121
+ if (
122
+ child === parent ||
123
+ Object !== parent?.constructor ||
124
+ Object !== child?.constructor
125
+ ) {
113
126
  return clone(child);
114
127
  }
115
128
 
@@ -140,7 +153,7 @@ export const merge = function (parent, child) {
140
153
  if (Array.isArray(overridden[key])) {
141
154
  const overelemRegex = new RegExp(
142
155
  `^\\$${key}\\[(?<index>\\d*)\\]$`,
143
- "u",
156
+ "v",
144
157
  );
145
158
  const overelems = Object.entries(child)
146
159
  .map(([k, v]) => [overelemRegex.exec(k)?.groups?.index, v])
@@ -161,50 +174,59 @@ export const merge = function (parent, child) {
161
174
  };
162
175
 
163
176
  /**
164
- * Étendre un objet JSON en utilisant les propriétés <code>"$extends"</code>.
177
+ * Étendre un objet JSON en utilisant la propriété `"$extends"`.
165
178
  *
166
- * @param {Record<string, any>} obj L'objet qui sera étendu.
179
+ * @param {Record<string, any>} obj L'objet qui sera étendu.
180
+ * @param {Options} [options] Les options.
167
181
  * @returns {Promise<Record<string, any>>} Une promesse contenant l'objet
168
182
  * étendu.
169
183
  */
170
- export const inherit = async function (obj) {
184
+ export const inherit = async function (obj, options) {
171
185
  if (undefined === obj.$extends) {
172
186
  return obj;
173
187
  }
174
188
 
175
189
  // eslint-disable-next-line no-use-before-define
176
- return merge(await load(obj.$extends), obj);
190
+ return merge(await load(obj.$extends, options), obj);
177
191
  };
178
192
 
179
193
  /**
180
194
  * Étendre un objet récursivement.
181
195
  *
182
- * @param {any} obj L'objet qui sera étendu.
196
+ * @param {any} obj L'objet qui sera étendu.
197
+ * @param {Options} [options] Les options.
183
198
  * @returns {Promise<any>} Une promesse contenant l'objet étendu.
184
199
  */
185
- export const extend = function (obj) {
186
- return walkAsync(obj, inherit);
200
+ export const extend = function (obj, options) {
201
+ return walkAsync(obj, (/** @type {any} */ v) => inherit(v, options));
187
202
  };
188
203
 
189
204
  /**
190
205
  * Charge un objet JSON depuis une URL.
191
206
  *
192
- * @param {string} url L'URL du fichier JSON.
207
+ * @param {string|URL} url L'URL du fichier JSON.
208
+ * @param {Options} [options] Les options.
193
209
  * @returns {Promise<any>} Une promesse contenant l'objet.
194
210
  */
195
- export const load = async function (url) {
196
- const response = await fetch(url);
197
- const json = await response.json();
211
+ export const load = async function (url, options) {
212
+ let json;
213
+ if (undefined === options?.request) {
214
+ const response = await fetch(url);
215
+ json = await response.json();
216
+ } else {
217
+ json = await options.request(url);
218
+ }
198
219
  // Enlever le "#" dans le hash de l'URL.
199
- return await extend(query(json, new URL(url).hash.slice(1)));
220
+ return await extend(query(json, new URL(url).hash.slice(1)), options);
200
221
  };
201
222
 
202
223
  /**
203
224
  * Parse une chaine de caractères.
204
225
  *
205
- * @param {string} text La chaine de caractères qui sera parsée.
226
+ * @param {string} text La chaine de caractères qui sera parsée.
227
+ * @param {Options} [options] Les options.
206
228
  * @returns {Promise<any>} L'objet.
207
229
  */
208
- export const parse = function (text) {
209
- return extend(JSON.parse(text));
230
+ export const parse = function (text, options) {
231
+ return extend(JSON.parse(text), options);
210
232
  };
@@ -3,7 +3,18 @@ export function walkAsync(obj: any, fn: Function): Promise<any>;
3
3
  export function clone(obj: any): any;
4
4
  export function query(obj: Record<string, any>, chain: string): any;
5
5
  export function merge(parent: any, child: any): any;
6
- export function inherit(obj: Record<string, any>): Promise<Record<string, any>>;
7
- export function extend(obj: any): Promise<any>;
8
- export function load(url: string): Promise<any>;
9
- export function parse(text: string): Promise<any>;
6
+ export function inherit(obj: Record<string, any>, options?: Options): Promise<Record<string, any>>;
7
+ export function extend(obj: any, options?: Options): Promise<any>;
8
+ export function load(url: string | URL, options?: Options): Promise<any>;
9
+ export function parse(text: string, options?: Options): Promise<any>;
10
+ /**
11
+ * Les options des fonctions de JFather.
12
+ */
13
+ export type Options = {
14
+ /**
15
+ * La fonction pour récupérer un objet JSON à
16
+ * distance. Par défaut, l'objet est récupéré avec
17
+ * `fetch()` et `Response.json()`.
18
+ */
19
+ request?: Function;
20
+ };