manyfest
Version:
JSON Object Manifest for Data Description and Parsing
340 lines (258 loc) • 9.38 kB
Markdown
# Reading Values from Objects
Manyfest provides safe, flexible access to values within complex object structures using dot-notation address paths. No more chaining `&&` checks or catching exceptions from deeply nested property access.
## Access
```javascript
const libManyfest = require('manyfest');
// Create a manifest (schema optional for read operations)
const manifest = new libManyfest();
// Or with a schema definition
const manifest = new libManyfest({
Scope: 'User',
Descriptors: {
'Name': { Hash: 'name', DataType: 'String' },
'Profile.Email': { Hash: 'email', DataType: 'String', Default: 'none' }
}
});
```
## Core Concepts
### Address Notation
Addresses use dot-separated paths to navigate nested objects:
```javascript
const data = {
user: {
profile: {
name: 'Alice',
scores: [95, 88, 72]
}
}
};
manifest.getValueAtAddress(data, 'user.profile.name'); // 'Alice'
manifest.getValueAtAddress(data, 'user.profile.scores'); // [95, 88, 72]
manifest.getValueAtAddress(data, 'user.profile.scores[1]'); // 88
```
Paths are case-sensitive and follow the exact structure of the object.
## Getting Values
### getValueAtAddress
Resolve a value at any depth using a dot-notation address:
```javascript
const data = { a: { b: { c: 'deep value' } } };
manifest.getValueAtAddress(data, 'a.b.c'); // 'deep value'
manifest.getValueAtAddress(data, 'a.b'); // { c: 'deep value' }
manifest.getValueAtAddress(data, 'a'); // { b: { c: 'deep value' } }
manifest.getValueAtAddress(data, 'x.y.z'); // undefined
```
When a descriptor exists for the address and defines a `Default` value, the default is returned instead of `undefined`:
```javascript
const manifest = new libManyfest({
Scope: 'Config',
Descriptors: {
'Theme': { DataType: 'String', Default: 'light' }
}
});
manifest.getValueAtAddress({}, 'Theme'); // 'light'
```
### getValueByHash
Retrieve a value using a hash rather than a direct address. When descriptors define hash-to-address mappings, this lets you use friendly short names:
```javascript
const manifest = new libManyfest({
Scope: 'Animal',
Descriptors: {
'MedicalStats.Temps.CET': { Hash: 'ComfET', DataType: 'Float' },
'MedicalStats.Temps.MaxET': { Hash: 'MaxET', DataType: 'Float' }
}
});
const animal = {
MedicalStats: {
Temps: { CET: 98.6, MaxET: 104.2 }
}
};
manifest.getValueByHash(animal, 'ComfET'); // 98.6
manifest.getValueByHash(animal, 'MaxET'); // 104.2
```
If the hash is not found in the descriptor table, manyfest treats it as a direct address and attempts resolution that way. This means `getValueByHash` works even without a schema defined.
### checkAddressExists
Check whether a value exists at an address without retrieving it:
```javascript
const data = { a: { b: 'value' } };
manifest.checkAddressExists(data, 'a.b'); // true
manifest.checkAddressExists(data, 'a.c'); // false
manifest.checkAddressExists(data, 'x.y.z'); // false
```
### checkAddressExistsByHash
The hash-based equivalent:
```javascript
manifest.checkAddressExistsByHash(data, 'ComfET'); // true or false
```
## Array Access
Access array elements with bracket notation:
```javascript
const data = {
students: [
{ name: 'Alice', grade: 95 },
{ name: 'Bob', grade: 88 },
{ name: 'Carol', grade: 72 }
]
};
manifest.getValueAtAddress(data, 'students[0]'); // { name: 'Alice', grade: 95 }
manifest.getValueAtAddress(data, 'students[1].name'); // 'Bob'
manifest.getValueAtAddress(data, 'students[2].grade'); // 72
```
### Set Access
Empty brackets return the full array, optionally filtered:
```javascript
manifest.getValueAtAddress(data, 'students[]');
// Returns all student objects
```
## Boxed Properties
Properties with special characters in their keys (dots, spaces, dashes) can be accessed using bracket notation with quotes:
```javascript
const data = {
'my-special-key': 'value1',
'another key': 'value2',
nested: {
'some.dotted.key': 'value3'
}
};
manifest.getValueAtAddress(data, '["my-special-key"]'); // 'value1'
manifest.getValueAtAddress(data, "['another key']"); // 'value2'
manifest.getValueAtAddress(data, 'nested["some.dotted.key"]'); // 'value3'
```
Single quotes, double quotes and backticks are all supported inside brackets.
## Back-Navigation
Navigate backward through the object hierarchy using `..` sequences:
```javascript
const data = {
Bundle: {
Contract: {
IDContract: 500,
Project: {
IDProject: 42
}
}
}
};
// From IDContract, go back up and into Project
manifest.getValueAtAddress(data, 'Bundle.Contract.IDContract...Project.IDProject');
// Navigates: IDContract -> back to Contract -> back to Bundle -> Project.IDProject
```
Each `.` in the `..` sequence navigates one level up from the current position.
## Object Set Access
Access properties across all keys of an object using `{}`:
```javascript
const data = {
departments: {
engineering: { budget: 50000 },
marketing: { budget: 30000 },
sales: { budget: 40000 }
}
};
manifest.getValueAtAddress(data, 'departments{}.budget');
// Returns: { 'departments.engineering.budget': 50000, 'departments.marketing.budget': 30000, 'departments.sales.budget': 40000 }
```
## Function Resolution
Address paths can include function calls on the object:
```javascript
const data = {
items: [3, 1, 4, 1, 5],
getTotal: function() { return this.items.reduce((a, b) => a + b, 0); }
};
manifest.getValueAtAddress(data, 'getTotal()'); // 14
```
Functions can also receive arguments resolved from addresses:
```javascript
const data = {
multiplier: 10,
scale: function(value) { return value * this.multiplier; },
baseValue: 5
};
manifest.getValueAtAddress(data, 'scale(baseValue)'); // 50
```
## Default Values
When a descriptor includes a `Default` property, that value is returned if the address resolves to `undefined`:
```javascript
const manifest = new libManyfest({
Scope: 'Settings',
Descriptors: {
'Theme': { DataType: 'String', Default: 'dark' },
'FontSize': { DataType: 'Integer', Default: 14 },
'Language': { DataType: 'String', Default: 'en' }
}
});
const settings = { Theme: 'light' };
manifest.getValueAtAddress(settings, 'Theme'); // 'light' (exists in object)
manifest.getValueAtAddress(settings, 'FontSize'); // 14 (default)
manifest.getValueAtAddress(settings, 'Language'); // 'en' (default)
```
When no explicit `Default` is defined but a `DataType` is set, the type's built-in default is used:
| DataType | Default Value |
|----------|--------------|
| String | `""` |
| Number | `0` |
| Integer | `0` |
| Float | `0.0` |
| PreciseNumber | `"0.0"` |
| Boolean | `false` |
| Binary | `0` |
| DateTime | `0` |
| Array | `[]` |
| Object | `{}` |
| Null | `null` |
## Use Cases
### Safe Deep Property Access
```javascript
function getUserCity(userData) {
const manifest = new libManyfest();
return manifest.getValueAtAddress(userData, 'profile.address.city');
}
// No error even if profile or address is missing
getUserCity({}); // undefined
getUserCity({ profile: { address: { city: 'Portland' } } }); // 'Portland'
```
### Configuration with Fallbacks
```javascript
const configManifest = new libManyfest({
Scope: 'AppConfig',
Descriptors: {
'Server.Port': { DataType: 'Integer', Default: 8080 },
'Server.Host': { DataType: 'String', Default: 'localhost' },
'Database.ConnectionString': { DataType: 'String', Default: 'mongodb://localhost/app' }
}
});
function getConfig(config, key) {
return configManifest.getValueByHash(config, key);
}
const config = { Server: { Port: 3000 } };
getConfig(config, 'Server.Port'); // 3000
getConfig(config, 'Server.Host'); // 'localhost'
getConfig(config, 'Database.ConnectionString'); // 'mongodb://localhost/app'
```
### Data Extraction with Hash Mapping
```javascript
const apiManifest = new libManyfest({
Scope: 'APIResponse',
Descriptors: {
'data.user.display_name': { Hash: 'UserName', DataType: 'String' },
'data.user.contact.primary_email': { Hash: 'Email', DataType: 'String' },
'data.metadata.created_at': { Hash: 'Created', DataType: 'DateTime' }
}
});
const response = {
data: {
user: {
display_name: 'Alice',
contact: { primary_email: 'alice@example.com' }
},
metadata: { created_at: '2024-01-15' }
}
};
apiManifest.getValueByHash(response, 'UserName'); // 'Alice'
apiManifest.getValueByHash(response, 'Email'); // 'alice@example.com'
apiManifest.getValueByHash(response, 'Created'); // '2024-01-15'
```
## Notes
- Address paths are case-sensitive
- Accessing a non-existent path returns `undefined` (no exceptions thrown)
- Array indices are zero-based
- `getValueByHash` falls back to treating the hash as a direct address if no mapping is found
- Back-navigation (`..`) resolves relative to the root object
- Functions in address paths are called with `apply`, bound to their containing object