@hitsuji_no_shippo/self-referenced-object
v4.0.0
Published
resolves objects with values that self-reference other properties in the same object
Downloads
28
Maintainers
Readme
= self-referenced-object
:author-name: hitsuji no shippo
:!author-email:
:author: {author-name}
:!email: {author-email}
:revnumber: v1.5.2
:revdate: 2019-11-30T11:25:57+0900
:revremark: fix convert for
to convert from
:doctype: article
:description: {doctitle} README
:title-separtor: :
:showtitle:
:!sectnums:
:sectids:
:toc: preamble
:sectlinks:
:sectanchors:
:idprefix:
:idseparator: -
:xrefstyle: full
:!example-caption:
:!figure-caption:
:!table-caption:
:!listing-caption:
// Copyright
:copyright-template: Copyright (c) 2019
:copyright: {copyright-template} {author-name}, alex-e-leon
:code-license: ISC
// Page Attributes
:page-creation-date: 2019-11-12T08:55:49+0900
Build and resolve objects with self-referencing properties.
I've found this especially useful when defining some kinds of config files in JS. Specifically you may sometimes want to define properties that either reference or are calculated from other properties in the same config file. Normally doing so would require adding calculated or self-referencing properties as extra statements after the initial object definition. This package allows you to define all your properties in a single statement in any order.
== Installation
[source, bash]
npm install @hitsuji_no_shippo/self-referenced-object
[source, JavaScript]
const { selfReferencedObject, resolveReferencesInObject, uniteObject, } = require('@hitsuji_no_shippo/self-referenced-object');
== Usage
=== resolveReferencesInObject
self-referenced-object behaves just like regular template literal expressions,
except that inside ${}
expressions it allows the use of this.[key]
to
reference other properties inside the same object, and uses regular strings
('
or "
) instead of template strings (pass:[
]`).
.basic usage [source, JavaScript]
resolveReferencesInObject({ a: 'a', b: '${this.a}', });
.calculated values [source, JavaScript]
resolveReferencesInObject({ a: 'a', b: '${this.a + this.a}', });
.non-primative types [source, JavaScript]
resolveReferencesInObject({ a: [1, 2, 3], b: '${(this.a).concat([4])}', });
.nested values [source, JavaScript]
resolveReferencesInObject({ a: { a: 'a', }, b: { a: '${this.a.a}', } });
==== Refer another object key values
If you use the third arguments, you can refer to the values of another object.
[source, JavaScript]
const object = { referred: { a: 'referred a', b: '${this.a}', c: 'c', d: { e: 'nest', }, same: 'referred', }, refer: { str: '${this.a}', nest: '${this.d.e}', same: 'refer', sameValue: '${this.same}', }, } const allKeyValues = {};
resolveReferencesInObject(object.referred, true, {allKeyValues}) // <1> resolveReferencesInObject(object.refer, true, {anotherObjectKeyValues: allKeyValues} // <2><3>
<1> {blank} +
.Return Object [source, JavaScript]
{ a: 'referred a', b: 'referred a', c: 'c', d: { e: 'nest' },
-- <2> {blank} +
.allKeyValues
[source, JavaScript]
{ a: 'referred a', b: 'referred a', c: 'c', 'd.e': 'nest', same: 'referred' }
-- <3> {blank} +
.Return Object [source, JavaScript]
{ str: 'referred a', nest: 'nest', same: 'refer', sameValue: 'referred' }
--
==== Error object
[source, JavaScript]
// Can not find <1> { a: '${this.c.a}', }
// Circular reference <2> { a: '${this.c}', b: '${this.a}', c: '${this.b}', }
<1> Error message is Can not find self refrencing property c.a in object
<2> Error message is
Circular reference found. Reference order: c => b => a => c
=== uniteObject
Unite objects with *
in key into a single object.
[source, JavaScript]
const obj = { 'github-': { 'my-': { id: 'hitsuji-no-shippo', name: 'hitsuji no shippo', year: 2018, }, 'repository-*': { name: 'self-referenced-object', issues: 'https://github.com/hitsuji-no-shippo/self-referenced-object/issues', }, url: 'https://github.com', }, }
uniteObject(obj); // <1> uniteObject({nest: obj}); // <2>
<1> {blank} +
[source, JavaScript]
{ 'github-my-id': 'hitsuji-no-shippo', 'github-my-name': 'hitsuji no shippo', 'github-my-year': 2018, 'github-repository-name': 'self-referenced-object', 'github-repository-issues': 'https://github.com/hitsuji-no-shippo/self-referenced-object/issues', 'github-url': 'https://github.com', }
-- <2> {blank} +
[source, JavaScript]
{ nest: { 'github-my-id': 'hitsuji-no-shippo', 'github-my-name': 'hitsuji no shippo', 'github-my-year': 2018, 'github-repository-name': 'self-referenced-object', 'github-repository-issues': 'https://github.com/hitsuji-no-shippo/self-referenced-object/issues', 'github-url': 'https://github.com', } }
--
==== Error object
[source, JavaScript]
// union mark multiple <1> { '-github-': { id: 'hitsuji-no-shippo', }, } // object in object to unite <2> { 'github-*': { id: 'hitsuji-no-shippo', obj: { abc: 123, }, }, }
<1> Error message is The \*-github-* exist multiple union marks
<2> Error message is
There is object in objct to unite. Full key path: github-obj
=== Convert references from directory to key
Convert references from directory (e.g. this.(./a)
, this.(../a)
) to
resolveReferencesInObject
usable key (e.g. {this.a}
).
Whether to convert is selected by the
second argument of resolveReferencesInObject
and uniteObject
.
(Default is true
)
[source, JavaScript]
resolveReferencesInObject(objct, shouldConvert) uniteObject(objct, shouldConvert)
.resolveReferencesInObject [source, JavaScript]
resolveReferencesInObject({ a: { a: { a: 'aaa', b: 'aab', }, b: { a: 'aba', b: 'abb', c: 'abc', d: '${this.(./a)}', // <1> e: { a: '${this.(../b)}', // <2> b: '${this.(../../a.a)}', // <3> }, }, }, });
<1> ${this.(./a)}
=> ${this.a.b.a}
=> aba
<3> ${this.(../b)}
=> ${this.a.b.b}
=> abb
<3> ${this.(../../a.a)}
=> ${this.a.a.a}
=> aaa
.uniteObject [source, JavaScript]
const object = { 'github-': { 'my-': { id: 'hitsuji-no-shippo', name: '${this.(./id)} = hitsuji-no-shippo', <1> year: 2018, 'page-url': '${this.(../url)}/${this.(./id)}', <2> }, url: 'https://github.com', }, }
// No nest uniteObject(object)
// Nest uniteObject({ nest: { union: object, } });
<1> {blank} +
[horizontal]
No Nest:: ${this.(./id)}
=> ${this.github-id}
Nest:: ${this.(./id)}
=> ${this.nest.union.github-id}
<2> {blank} +
[horizontal]
No Nest:: ${this.(../url)}
=> ${this.github-url}
Nest:: ${this.(../url)}
=> ${this.nest.union.github-url}
.expressions [source, JavaScript]
a: { b: 'ab', c: 'ac', e: '${(this.(./b) + this.(./c)).repeat(2)}' // <1> }
<1> abacabac
==== Error object
[source, JavaScript]
// directory reference invalid <1> { a: { b: { c: '${this.(../../../../d)}', }, }, d: 'd', }
<1> Error message is
There are invalid directory reference in the value.
value: \${this.(../../../../d)} . directory reference: ../../../../ .
referable keys: a, a.b
=== selfReferencedObject
Executes in order of uniteObject
and resolveReferencesInObject
.
The second and third arguments are same to resolveReferencesInObject
.
[source, JavaScript]
selfReferencedObject({ 'github-': { 'my-': { id: 'hitsuji-no-shippo', name: '${this.(./id)} = hitsuji-no-shippo', year: 2018, 'page-url': '${this.(../url)}/${this.(./id)}', 'profile-': { comment: 'My id is ${this.(../id)}. Join year ${this.(../year)}. ${this.(../../repository-name)} committer. twitter id: ${this.id-twitter}. Live in ${this.country}', }, }, 'repository-': { name: 'self-referenced-object', issues: '${this.(../url)}/${this.(../my-id)}/${this.(./name)}/issues', }, url: 'https://github.com', }, '*-twitter': { id: 'hsp_equal_st', list: [1, 2, 3, 4], }, country: 'Japan', })
.Result [source, JavaScript]
{
'github-my-id': hitsuji-no-shippo
,
'github-my-name': hitsuji-no-shippo = hitsuji-no-shippo
,
'github-my-year': 2018,
'github-my-page-url': https://github.com/hitsuji-no-shippo
,
'github-my-profile-comment': My id is hitsuji-no-shippo. Join year 2018. self-referenced-object committer. twitter id: hsp_equal_st. Live in Japan
,
'github-repository-name': self-referenced-object
,
'github-repository-issues': https://github.com/hitsuji-no-shippo/self-referenced-object/issues
,
'github-url': https://github.com
,
'id-twitter': hsp_equal_st
,
'list-twitter': [1, 2, 3, 4],
country: 'Japan',
}
== resolveReferencesInObject
=== Circular references and undefined references
self-referenced-object will throw errors if circular references or undefined references are found. It uses a backtracking DFS to track circular references.
=== Non-string values
The other way in which self-referenced-object differs from regular template
strings is it's support for returning non-string values. In order to support
self-references that might be numbers, arrays, objects etc.
self-referenced-object will avoid casting the value to a string if the template
is only a single ${}
expression. Ie in { a: [1,2,3], b: "${this.a}" }
b
would be an Array, while in { a: [1,2,3], b: "${this.a} go" }
, b would be
the string "1,2,3 go"
=== Nested objects and values
self-referenced-object supports both nested references (ie ${this.x.x}
) and
nested objects (ie it will recursively traverse any children objects up to any
depth);
=== Escape characters
If you need to use }
inside your template literal expressions, they can be
escaped by adding a \
in front of them just like in regular template literals.
=== Security
self-referenced-object evaluates any expressions inside template literals by
calling pass:[Function('"use strict"; return
' + expression + ";")()]
which
is a marginally safer version of eval
(ie still incredibly unsafe), so you
should avoid passing any untrusted data into an object evaluated by
resolveReferencesInObject
(or at least don't self-reference untrusted data in
an resolveReferencesInObject
evaluated object).
== uniteObject
.Reason for creation
For use with
link:https://asciidoctor-docs.netlify.com/asciidoctor.js/processor/convert-options/[
convert options attributes
of Asciidoctor.js]
== Copyright
.LICENSE Licensed under {code-license}
{copyright-template} link:https://hitsuji-no-shippo.com[{author-name}], alex-e-leon