manyfest 1.0.43 → 1.0.44
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/README.md +91 -260
- package/docs/.nojekyll +0 -0
- package/docs/README.md +108 -0
- package/docs/_sidebar.md +17 -0
- package/docs/address-notation.md +244 -0
- package/docs/cover.md +11 -0
- package/docs/hash-translation.md +202 -0
- package/docs/index.html +51 -0
- package/docs/quickstart.md +203 -0
- package/docs/reading.md +339 -0
- package/docs/schema-manipulation.md +186 -0
- package/docs/schema.md +319 -0
- package/docs/validating.md +344 -0
- package/docs/writing.md +300 -0
- package/package.json +1 -1
- package/source/Manyfest-ObjectAddress-CheckAddressExists.js +3 -4
- package/source/Manyfest-ObjectAddress-DeleteValue.js +7 -5
- package/source/Manyfest-ObjectAddress-GetValue.js +2 -6
- package/source/Manyfest-ObjectAddress-Parser.js +3 -3
- package/source/Manyfest-ObjectAddress-SetValue.js +7 -4
- package/source/Manyfest-ParseConditionals.js +0 -13
- package/source/Manyfest.js +28 -15
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# Validating Objects
|
|
2
|
+
|
|
3
|
+
Manyfest validates objects against their schema definitions, checking for required elements, data type conformance, and missing properties. Validation never throws exceptions; results come back as well-formed JSON.
|
|
4
|
+
|
|
5
|
+
## Access
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
const libManyfest = require('manyfest');
|
|
9
|
+
|
|
10
|
+
// Validation requires a schema with descriptors
|
|
11
|
+
const manifest = new libManyfest({
|
|
12
|
+
Scope: 'Animal',
|
|
13
|
+
Descriptors: {
|
|
14
|
+
'IDAnimal': { Name: 'Database ID', DataType: 'Integer', Default: 0 },
|
|
15
|
+
'Name': { Description: 'The animal species name.' },
|
|
16
|
+
'Type': { Description: 'Whether the animal is wild, domesticated, etc.' }
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Core Concepts
|
|
22
|
+
|
|
23
|
+
### Validation Results
|
|
24
|
+
|
|
25
|
+
The `validate` method returns a result object with three properties:
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
{
|
|
29
|
+
Error: null, // null when valid, true when errors exist
|
|
30
|
+
Errors: [], // Array of human-readable error message strings
|
|
31
|
+
MissingElements: [] // Array of addresses for elements not found in the object
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A clean validation looks like this:
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
const result = manifest.validate({ IDAnimal: 42, Name: 'Rabbit', Type: 'Wild' });
|
|
39
|
+
// { Error: null, Errors: [], MissingElements: [] }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Validating an Object
|
|
43
|
+
|
|
44
|
+
### validate
|
|
45
|
+
|
|
46
|
+
Pass an object to check it against the schema:
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
const manifest = new libManyfest({
|
|
50
|
+
Scope: 'Animal',
|
|
51
|
+
Descriptors: {
|
|
52
|
+
'IDAnimal': { DataType: 'Integer', Required: true },
|
|
53
|
+
'Name': { DataType: 'String' },
|
|
54
|
+
'Weight': { DataType: 'Float' }
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const animal = { IDAnimal: 8675309, Name: 'BatBrains', Weight: 2.5 };
|
|
59
|
+
const result = manifest.validate(animal);
|
|
60
|
+
// { Error: null, Errors: [], MissingElements: [] }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Non-Object Input
|
|
64
|
+
|
|
65
|
+
Passing a non-object produces an immediate error:
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
const result = manifest.validate('not an object');
|
|
69
|
+
// { Error: true, Errors: ['Expected passed in object to be type object but was passed in string'], MissingElements: [] }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Required Elements
|
|
73
|
+
|
|
74
|
+
Mark a descriptor as `Required: true` to produce an error when that property is missing:
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
const manifest = new libManyfest({
|
|
78
|
+
Scope: 'User',
|
|
79
|
+
Descriptors: {
|
|
80
|
+
'Email': { DataType: 'String', Required: true },
|
|
81
|
+
'Name': { DataType: 'String', Required: true },
|
|
82
|
+
'Bio': { DataType: 'String' }
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const user = { Email: 'alice@example.com' };
|
|
87
|
+
const result = manifest.validate(user);
|
|
88
|
+
// {
|
|
89
|
+
// Error: true,
|
|
90
|
+
// Errors: ['Element at address "Name" is flagged REQUIRED but is not set in the object.'],
|
|
91
|
+
// MissingElements: ['Name', 'Bio']
|
|
92
|
+
// }
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Note that `Bio` appears in `MissingElements` but not in `Errors` because it is not required.
|
|
96
|
+
|
|
97
|
+
## Strict Mode
|
|
98
|
+
|
|
99
|
+
When `strict` is enabled, every described element that is missing from the object becomes an error, regardless of the `Required` flag:
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
const manifest = new libManyfest({
|
|
103
|
+
Scope: 'Config',
|
|
104
|
+
strict: true,
|
|
105
|
+
Descriptors: {
|
|
106
|
+
'Host': { DataType: 'String' },
|
|
107
|
+
'Port': { DataType: 'Integer' },
|
|
108
|
+
'Debug': { DataType: 'Boolean' }
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const config = { Host: 'localhost' };
|
|
113
|
+
const result = manifest.validate(config);
|
|
114
|
+
// {
|
|
115
|
+
// Error: true,
|
|
116
|
+
// Errors: [
|
|
117
|
+
// 'Element at address "Port" is flagged REQUIRED but is not set in the object.',
|
|
118
|
+
// 'Element at address "Debug" is flagged REQUIRED but is not set in the object.'
|
|
119
|
+
// ],
|
|
120
|
+
// MissingElements: ['Port', 'Debug']
|
|
121
|
+
// }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Data Type Checking
|
|
125
|
+
|
|
126
|
+
When a descriptor specifies a `DataType`, the value is checked for type conformance.
|
|
127
|
+
|
|
128
|
+
### Supported Types
|
|
129
|
+
|
|
130
|
+
| DataType | Expected JavaScript Type | Notes |
|
|
131
|
+
|----------|------------------------|-------|
|
|
132
|
+
| String | `string` | |
|
|
133
|
+
| Number | `number` | Any numeric value |
|
|
134
|
+
| Integer | `number` | Must not contain a decimal point |
|
|
135
|
+
| Float | `number` | Any numeric value |
|
|
136
|
+
| PreciseNumber | `string` | A string that parses as a valid number |
|
|
137
|
+
| DateTime | any | Must be parsable by `new Date()` |
|
|
138
|
+
|
|
139
|
+
### Type Validation Examples
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
const manifest = new libManyfest({
|
|
143
|
+
Scope: 'Record',
|
|
144
|
+
Descriptors: {
|
|
145
|
+
'Name': { DataType: 'String' },
|
|
146
|
+
'Count': { DataType: 'Integer' },
|
|
147
|
+
'Price': { DataType: 'Float' },
|
|
148
|
+
'Precision': { DataType: 'PreciseNumber' },
|
|
149
|
+
'Created': { DataType: 'DateTime' }
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Valid object:**
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
manifest.validate({
|
|
158
|
+
Name: 'Widget',
|
|
159
|
+
Count: 10,
|
|
160
|
+
Price: 19.99,
|
|
161
|
+
Precision: '19.9900',
|
|
162
|
+
Created: '2024-01-15T10:30:00Z'
|
|
163
|
+
});
|
|
164
|
+
// { Error: null, Errors: [], MissingElements: [] }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Type mismatches:**
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
manifest.validate({
|
|
171
|
+
Name: 42,
|
|
172
|
+
Count: 10.5,
|
|
173
|
+
Price: 'free',
|
|
174
|
+
Precision: 19.99,
|
|
175
|
+
Created: 'not a date'
|
|
176
|
+
});
|
|
177
|
+
// Errors include:
|
|
178
|
+
// 'Element at address "Name" has a DataType String but is of the type number.'
|
|
179
|
+
// 'Element at address "Count" has a DataType Integer but has a decimal point in the number.'
|
|
180
|
+
// 'Element at address "Price" has a DataType Float but is of the type string.'
|
|
181
|
+
// 'Element at address "Precision" has a DataType PreciseNumber but is of the type number.'
|
|
182
|
+
// 'Element at address "Created" has a DataType DateTime but is not parsable as a Date by Javascript.'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Integer vs Float
|
|
186
|
+
|
|
187
|
+
Both `Integer` and `Float` require a JavaScript `number` type. The distinction is that `Integer` additionally checks that the value does not contain a decimal point:
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
const manifest = new libManyfest({
|
|
191
|
+
Scope: 'Test',
|
|
192
|
+
Descriptors: {
|
|
193
|
+
'WholeNumber': { DataType: 'Integer' },
|
|
194
|
+
'Decimal': { DataType: 'Float' }
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
manifest.validate({ WholeNumber: 10, Decimal: 3.14 });
|
|
199
|
+
// No errors
|
|
200
|
+
|
|
201
|
+
manifest.validate({ WholeNumber: 10.5, Decimal: 3.14 });
|
|
202
|
+
// Error: 'Element at address "WholeNumber" has a DataType Integer but has a decimal point in the number.'
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### PreciseNumber
|
|
206
|
+
|
|
207
|
+
The `PreciseNumber` type is for numeric values stored as strings to preserve precision (useful for financial calculations). Validation checks that the value is a `string` that matches a valid number pattern:
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
const manifest = new libManyfest({
|
|
211
|
+
Scope: 'Financial',
|
|
212
|
+
Descriptors: {
|
|
213
|
+
'Balance': { DataType: 'PreciseNumber' }
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
manifest.validate({ Balance: '1234567890.12' }); // Valid
|
|
218
|
+
manifest.validate({ Balance: 'not a number' }); // Error: not a valid number
|
|
219
|
+
manifest.validate({ Balance: 1234.56 }); // Error: expects string type
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Unrecognized Data Types
|
|
223
|
+
|
|
224
|
+
If a `DataType` is specified but not recognized, it defaults to `String` checking:
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
const manifest = new libManyfest({
|
|
228
|
+
Scope: 'Test',
|
|
229
|
+
Descriptors: {
|
|
230
|
+
'Value': { DataType: 'CustomType' }
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
manifest.validate({ Value: 'hello' }); // Valid (treated as String)
|
|
235
|
+
manifest.validate({ Value: 42 }); // Error (expected string)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Nested Object Validation
|
|
239
|
+
|
|
240
|
+
Descriptors with dot-notation addresses validate nested properties:
|
|
241
|
+
|
|
242
|
+
```javascript
|
|
243
|
+
const manifest = new libManyfest({
|
|
244
|
+
Scope: 'User',
|
|
245
|
+
Descriptors: {
|
|
246
|
+
'Profile.Name': { DataType: 'String', Required: true },
|
|
247
|
+
'Profile.Age': { DataType: 'Integer' },
|
|
248
|
+
'Contact.Email': { DataType: 'String', Required: true }
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const user = {
|
|
253
|
+
Profile: { Name: 'Alice', Age: 30 },
|
|
254
|
+
Contact: { Email: 'alice@example.com' }
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
manifest.validate(user);
|
|
258
|
+
// { Error: null, Errors: [], MissingElements: [] }
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Use Cases
|
|
262
|
+
|
|
263
|
+
### API Input Validation
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
const requestManifest = new libManyfest({
|
|
267
|
+
Scope: 'CreateUser',
|
|
268
|
+
Descriptors: {
|
|
269
|
+
'email': { DataType: 'String', Required: true },
|
|
270
|
+
'password': { DataType: 'String', Required: true },
|
|
271
|
+
'profile.displayName': { DataType: 'String', Required: true },
|
|
272
|
+
'profile.age': { DataType: 'Integer' }
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
function validateCreateUserRequest(requestBody) {
|
|
277
|
+
let result = requestManifest.validate(requestBody);
|
|
278
|
+
if (result.Error) {
|
|
279
|
+
return { valid: false, errors: result.Errors };
|
|
280
|
+
}
|
|
281
|
+
return { valid: true };
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Configuration Verification
|
|
286
|
+
|
|
287
|
+
```javascript
|
|
288
|
+
const configManifest = new libManyfest({
|
|
289
|
+
Scope: 'AppConfig',
|
|
290
|
+
strict: true,
|
|
291
|
+
Descriptors: {
|
|
292
|
+
'Database.Host': { DataType: 'String' },
|
|
293
|
+
'Database.Port': { DataType: 'Integer' },
|
|
294
|
+
'Database.Name': { DataType: 'String' },
|
|
295
|
+
'Server.Port': { DataType: 'Integer' },
|
|
296
|
+
'Server.LogLevel': { DataType: 'String' }
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
function verifyConfig(config) {
|
|
301
|
+
let result = configManifest.validate(config);
|
|
302
|
+
if (result.Error) {
|
|
303
|
+
console.error('Configuration errors:', result.Errors);
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
if (result.MissingElements.length > 0) {
|
|
307
|
+
console.warn('Optional config missing:', result.MissingElements);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Schema Completeness Checks
|
|
313
|
+
|
|
314
|
+
Use `MissingElements` to identify which properties are absent, even when validation passes:
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
const manifest = new libManyfest({
|
|
318
|
+
Scope: 'Profile',
|
|
319
|
+
Descriptors: {
|
|
320
|
+
'Name': { DataType: 'String', Required: true },
|
|
321
|
+
'Bio': { DataType: 'String' },
|
|
322
|
+
'Avatar': { DataType: 'String' },
|
|
323
|
+
'Website': { DataType: 'String' }
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const profile = { Name: 'Alice', Bio: 'Developer' };
|
|
328
|
+
const result = manifest.validate(profile);
|
|
329
|
+
// result.Error is null (no errors)
|
|
330
|
+
// result.MissingElements is ['Avatar', 'Website']
|
|
331
|
+
|
|
332
|
+
const completeness = 1 - (result.MissingElements.length / 4);
|
|
333
|
+
// 50% complete
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Notes
|
|
337
|
+
|
|
338
|
+
- Validation never throws exceptions; errors are returned as structured data
|
|
339
|
+
- `Error` is `null` when no errors exist, `true` when errors are present
|
|
340
|
+
- `MissingElements` tracks all absent schema elements, not just required ones
|
|
341
|
+
- In strict mode, every missing element is treated as an error
|
|
342
|
+
- Data type checking is case-insensitive (`"String"`, `"string"`, `"STRING"` all work)
|
|
343
|
+
- `undefined` values and non-existent properties are both treated as missing
|
|
344
|
+
- Unrecognized `DataType` values default to `String` validation
|
package/docs/writing.md
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# Writing Values to Objects
|
|
2
|
+
|
|
3
|
+
Manyfest provides safe, address-based value assignment that automatically creates intermediate objects and arrays as needed. Write to any depth in an object without manually building the path.
|
|
4
|
+
|
|
5
|
+
## Access
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
const libManyfest = require('manyfest');
|
|
9
|
+
|
|
10
|
+
// Create a manifest (schema optional for write operations)
|
|
11
|
+
const manifest = new libManyfest();
|
|
12
|
+
|
|
13
|
+
// Or with a schema definition
|
|
14
|
+
const manifest = new libManyfest({
|
|
15
|
+
Scope: 'User',
|
|
16
|
+
Descriptors: {
|
|
17
|
+
'Name': { Hash: 'name', DataType: 'String' },
|
|
18
|
+
'Profile.Email': { Hash: 'email', DataType: 'String' }
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Core Concepts
|
|
24
|
+
|
|
25
|
+
### Automatic Structure Creation
|
|
26
|
+
|
|
27
|
+
When setting a value at a nested address, manyfest creates any intermediate objects that do not yet exist:
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
const data = {};
|
|
31
|
+
|
|
32
|
+
manifest.setValueAtAddress(data, 'user.profile.name', 'Alice');
|
|
33
|
+
// data is now: { user: { profile: { name: 'Alice' } } }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
No need to manually initialize `data.user` or `data.user.profile` first.
|
|
37
|
+
|
|
38
|
+
## Setting Values
|
|
39
|
+
|
|
40
|
+
### setValueAtAddress
|
|
41
|
+
|
|
42
|
+
Set a value at any depth using a dot-notation address:
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
const data = {};
|
|
46
|
+
|
|
47
|
+
manifest.setValueAtAddress(data, 'name', 'Alice');
|
|
48
|
+
// { name: 'Alice' }
|
|
49
|
+
|
|
50
|
+
manifest.setValueAtAddress(data, 'profile.age', 30);
|
|
51
|
+
// { name: 'Alice', profile: { age: 30 } }
|
|
52
|
+
|
|
53
|
+
manifest.setValueAtAddress(data, 'profile.address.city', 'Portland');
|
|
54
|
+
// { name: 'Alice', profile: { age: 30, address: { city: 'Portland' } } }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Returns `true` if the value was set successfully, `false` otherwise.
|
|
58
|
+
|
|
59
|
+
### setValueByHash
|
|
60
|
+
|
|
61
|
+
Set a value using a hash rather than a direct address. When descriptors define hash-to-address mappings, this lets you use friendly short names:
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
const manifest = new libManyfest({
|
|
65
|
+
Scope: 'Animal',
|
|
66
|
+
Descriptors: {
|
|
67
|
+
'MedicalStats.Temps.CET': { Hash: 'ComfET', DataType: 'Float' },
|
|
68
|
+
'MedicalStats.Temps.MaxET': { Hash: 'MaxET', DataType: 'Float' }
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const animal = {};
|
|
73
|
+
|
|
74
|
+
manifest.setValueByHash(animal, 'ComfET', 98.6);
|
|
75
|
+
manifest.setValueByHash(animal, 'MaxET', 104.2);
|
|
76
|
+
// animal is now: { MedicalStats: { Temps: { CET: 98.6, MaxET: 104.2 } } }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
If the hash is not found in the descriptor table, manyfest treats it as a direct address and sets the value that way.
|
|
80
|
+
|
|
81
|
+
### Overwriting Existing Values
|
|
82
|
+
|
|
83
|
+
Setting a value at an address that already contains data replaces it:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
const data = { color: 'blue' };
|
|
87
|
+
|
|
88
|
+
manifest.setValueAtAddress(data, 'color', 'red');
|
|
89
|
+
// { color: 'red' }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Nested values work the same way:
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
const data = { user: { name: 'Alice' } };
|
|
96
|
+
|
|
97
|
+
manifest.setValueAtAddress(data, 'user.name', 'Bob');
|
|
98
|
+
// { user: { name: 'Bob' } }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Array Assignment
|
|
102
|
+
|
|
103
|
+
Set values at specific array indices using bracket notation:
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
const data = { items: [] };
|
|
107
|
+
|
|
108
|
+
manifest.setValueAtAddress(data, 'items[0]', 'first');
|
|
109
|
+
manifest.setValueAtAddress(data, 'items[1]', 'second');
|
|
110
|
+
manifest.setValueAtAddress(data, 'items[2]', 'third');
|
|
111
|
+
// data.items is ['first', 'second', 'third']
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Nested array elements work as expected:
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
const data = { users: [{ name: 'Alice' }, { name: 'Bob' }] };
|
|
118
|
+
|
|
119
|
+
manifest.setValueAtAddress(data, 'users[0].score', 95);
|
|
120
|
+
manifest.setValueAtAddress(data, 'users[1].score', 88);
|
|
121
|
+
// data.users is [{ name: 'Alice', score: 95 }, { name: 'Bob', score: 88 }]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Boxed Properties
|
|
125
|
+
|
|
126
|
+
Write to properties with special characters in their keys using bracket notation with quotes:
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
const data = {};
|
|
130
|
+
|
|
131
|
+
manifest.setValueAtAddress(data, '["my-special-key"]', 'value1');
|
|
132
|
+
manifest.setValueAtAddress(data, 'nested["some.dotted.key"]', 'value2');
|
|
133
|
+
// data is: { 'my-special-key': 'value1', nested: { 'some.dotted.key': 'value2' } }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Populating Defaults
|
|
137
|
+
|
|
138
|
+
### populateDefaults
|
|
139
|
+
|
|
140
|
+
Fill in default values for any descriptor that defines a `Default` property, without overwriting existing data:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
const manifest = new libManyfest({
|
|
144
|
+
Scope: 'Settings',
|
|
145
|
+
Descriptors: {
|
|
146
|
+
'Theme': { DataType: 'String', Default: 'dark' },
|
|
147
|
+
'FontSize': { DataType: 'Integer', Default: 14 },
|
|
148
|
+
'Language': { DataType: 'String', Default: 'en' }
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const settings = { Theme: 'light' };
|
|
153
|
+
|
|
154
|
+
manifest.populateDefaults(settings);
|
|
155
|
+
// settings is now: { Theme: 'light', FontSize: 14, Language: 'en' }
|
|
156
|
+
// Theme was not overwritten because it already existed
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Pass `true` as the second argument to overwrite existing properties:
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
manifest.populateDefaults(settings, true);
|
|
163
|
+
// settings is now: { Theme: 'dark', FontSize: 14, Language: 'en' }
|
|
164
|
+
// Theme was overwritten to its default
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### populateObject
|
|
168
|
+
|
|
169
|
+
Populate all values based on the schema, using type defaults even when no explicit `Default` is defined:
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
const manifest = new libManyfest({
|
|
173
|
+
Scope: 'Record',
|
|
174
|
+
Descriptors: {
|
|
175
|
+
'Name': { DataType: 'String' },
|
|
176
|
+
'Count': { DataType: 'Integer' },
|
|
177
|
+
'Active': { DataType: 'Boolean' },
|
|
178
|
+
'Tags': { DataType: 'Array' }
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const record = manifest.populateObject({});
|
|
183
|
+
// record is: { Name: '', Count: 0, Active: false, Tags: [] }
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
A filter function can be passed to selectively populate:
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
const record = manifest.populateObject({}, false,
|
|
190
|
+
(pDescriptor) => { return pDescriptor.DataType === 'String'; }
|
|
191
|
+
);
|
|
192
|
+
// Only String-typed descriptors are populated
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Deleting Values
|
|
196
|
+
|
|
197
|
+
### deleteValueAtAddress
|
|
198
|
+
|
|
199
|
+
Remove a value at an address:
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
const data = { user: { name: 'Alice', age: 30 } };
|
|
203
|
+
|
|
204
|
+
manifest.deleteValueAtAddress(data, 'user.age');
|
|
205
|
+
// data is now: { user: { name: 'Alice' } }
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### deleteValueByHash
|
|
209
|
+
|
|
210
|
+
The hash-based equivalent:
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
manifest.deleteValueByHash(data, 'email');
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Use Cases
|
|
217
|
+
|
|
218
|
+
### Building Objects from Flat Data
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
function buildUserFromForm(formFields) {
|
|
222
|
+
const manifest = new libManyfest();
|
|
223
|
+
const user = {};
|
|
224
|
+
|
|
225
|
+
for (let tmpField of formFields) {
|
|
226
|
+
manifest.setValueAtAddress(user, tmpField.path, tmpField.value);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return user;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const fields = [
|
|
233
|
+
{ path: 'name.first', value: 'Alice' },
|
|
234
|
+
{ path: 'name.last', value: 'Smith' },
|
|
235
|
+
{ path: 'contact.email', value: 'alice@example.com' },
|
|
236
|
+
{ path: 'contact.phone', value: '555-1234' }
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
buildUserFromForm(fields);
|
|
240
|
+
// { name: { first: 'Alice', last: 'Smith' }, contact: { email: 'alice@example.com', phone: '555-1234' } }
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Data Transformation
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
function transformData(source, mappings) {
|
|
247
|
+
const manifest = new libManyfest();
|
|
248
|
+
const result = {};
|
|
249
|
+
|
|
250
|
+
for (let [targetPath, sourcePath] of Object.entries(mappings)) {
|
|
251
|
+
let value = manifest.getValueAtAddress(source, sourcePath);
|
|
252
|
+
if (value !== undefined) {
|
|
253
|
+
manifest.setValueAtAddress(result, targetPath, value);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const apiData = {
|
|
261
|
+
data: { user: { display_name: 'Alice', contact: { primary_email: 'alice@example.com' } } }
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
transformData(apiData, {
|
|
265
|
+
'userName': 'data.user.display_name',
|
|
266
|
+
'userEmail': 'data.user.contact.primary_email'
|
|
267
|
+
});
|
|
268
|
+
// { userName: 'Alice', userEmail: 'alice@example.com' }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Initializing Records with Schema Defaults
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
const invoiceManifest = new libManyfest({
|
|
275
|
+
Scope: 'Invoice',
|
|
276
|
+
Descriptors: {
|
|
277
|
+
'InvoiceNumber': { DataType: 'String', Default: '' },
|
|
278
|
+
'Status': { DataType: 'String', Default: 'draft' },
|
|
279
|
+
'LineItems': { DataType: 'Array', Default: [] },
|
|
280
|
+
'Total': { DataType: 'Float', Default: 0.0 },
|
|
281
|
+
'Metadata.CreatedBy': { DataType: 'String', Default: 'system' }
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
function createNewInvoice() {
|
|
286
|
+
return invoiceManifest.populateDefaults({});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
createNewInvoice();
|
|
290
|
+
// { InvoiceNumber: '', Status: 'draft', LineItems: [], Total: 0, Metadata: { CreatedBy: 'system' } }
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Notes
|
|
294
|
+
|
|
295
|
+
- Non-existent intermediate objects are created automatically during write operations
|
|
296
|
+
- Setting a value at an address where an intermediate path resolves to a non-object (e.g. a string) will not overwrite that value
|
|
297
|
+
- Array indices use bracket notation: `items[0]`
|
|
298
|
+
- `populateDefaults` does not overwrite existing properties unless the second argument is `true`
|
|
299
|
+
- `populateObject` without a filter populates every descriptor in the schema
|
|
300
|
+
- Paths are case-sensitive
|
package/package.json
CHANGED
|
@@ -244,7 +244,7 @@ class ManyfestObjectAddressResolverCheckAddressExists
|
|
|
244
244
|
{
|
|
245
245
|
let tmpArgumentValues = [];
|
|
246
246
|
|
|
247
|
-
|
|
247
|
+
|
|
248
248
|
|
|
249
249
|
// Now get the value for each argument
|
|
250
250
|
for (let i = 0; i < tmpFunctionArguments.length; i++)
|
|
@@ -351,9 +351,8 @@ class ManyfestObjectAddressResolverCheckAddressExists
|
|
|
351
351
|
}
|
|
352
352
|
else
|
|
353
353
|
{
|
|
354
|
-
//
|
|
355
|
-
|
|
356
|
-
return this.checkAddressExists(pObject[tmpSubObjectName], tmpNewAddress, tmpRootObject);
|
|
354
|
+
// The sub-object doesn't exist, so the address doesn't exist
|
|
355
|
+
return false;
|
|
357
356
|
}
|
|
358
357
|
}
|
|
359
358
|
}
|
|
@@ -5,6 +5,8 @@ let libSimpleLog = require('./Manyfest-LogToConsole.js');
|
|
|
5
5
|
let fCleanWrapCharacters = require('./Manyfest-CleanWrapCharacters.js');
|
|
6
6
|
let fParseConditionals = require(`../source/Manyfest-ParseConditionals.js`)
|
|
7
7
|
|
|
8
|
+
let _MockFable = { DataFormat: require('./Manyfest-ObjectAddress-Parser.js') };
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Object Address Resolver - DeleteValue
|
|
10
12
|
*
|
|
@@ -74,11 +76,11 @@ class ManyfestObjectAddressResolverDeleteValue
|
|
|
74
76
|
tmpParentAddress = pParentAddress;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
//
|
|
78
|
-
let
|
|
79
|
+
// Use enclosure-aware parser to find the first segment separator
|
|
80
|
+
let tmpAddressPartBeginning = _MockFable.DataFormat.stringGetFirstSegment(pAddress);
|
|
79
81
|
|
|
80
82
|
// This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow)
|
|
81
|
-
if (
|
|
83
|
+
if (tmpAddressPartBeginning.length == pAddress.length)
|
|
82
84
|
{
|
|
83
85
|
// Check if the address refers to a boxed property
|
|
84
86
|
let tmpBracketStartIndex = pAddress.indexOf('[');
|
|
@@ -201,8 +203,8 @@ class ManyfestObjectAddressResolverDeleteValue
|
|
|
201
203
|
}
|
|
202
204
|
else
|
|
203
205
|
{
|
|
204
|
-
let tmpSubObjectName =
|
|
205
|
-
let tmpNewAddress = pAddress.substring(
|
|
206
|
+
let tmpSubObjectName = tmpAddressPartBeginning;
|
|
207
|
+
let tmpNewAddress = pAddress.substring(tmpAddressPartBeginning.length+1);
|
|
206
208
|
|
|
207
209
|
// BOXED ELEMENTS
|
|
208
210
|
// Test if the tmpNewAddress is an array or object
|