From 1b71e360d6596ccb459d7d9c597943fa1f0760ca Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 2 Feb 2023 16:25:28 -0500 Subject: [PATCH 01/15] Fix typo in benchmarks README instructions. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 045325a4..dd7c372e 100644 --- a/README.md +++ b/README.md @@ -476,7 +476,7 @@ Use a command line with a test suite and a benchmark flag: EARL reports with benchmark data can be generated with an optional environment details: - JSONLD_TESTS=`pwd`/../json-ld.org/benchmarks/b001-manifiest.jsonld JSONLD_BENCHMARK=1 EARL=earl-test.jsonld TEST_ENV=1 npm test + JSONLD_TESTS=`pwd`/../json-ld.org/benchmarks/b001-manifest.jsonld JSONLD_BENCHMARK=1 EARL=earl-test.jsonld TEST_ENV=1 npm test See `tests/test.js` for more `TEST_ENV` control and options. From c73dc44429873d0fc609c77683ef02bd2fe26e8c Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 2 Feb 2023 16:31:34 -0500 Subject: [PATCH 02/15] Improve internal `types.isObject` API performance. --- CHANGELOG.md | 5 +++++ lib/types.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f3128b6..f14727cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # jsonld ChangeLog +## 8.1.1 - 2023-02-dd + +### Fixed +- Improved `types.isObject` internal API performance. + ## 8.1.0 - 2022-08-29 ### Fixed diff --git a/lib/types.js b/lib/types.js index d07e4fa1..e04a0ca5 100644 --- a/lib/types.js +++ b/lib/types.js @@ -70,7 +70,7 @@ api.isNumeric = v => !isNaN(parseFloat(v)) && isFinite(v); * * @return true if the value is an Object, false if not. */ -api.isObject = v => Object.prototype.toString.call(v) === '[object Object]'; +api.isObject = v => v !== null && typeof v === 'object' && !Array.isArray(v); /** * Returns true if the given value is a String. From 3adeda8bb7e0ccfe9c149491478ea57238628d76 Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 2 Feb 2023 16:40:34 -0500 Subject: [PATCH 03/15] Improve internal `graphTypes.isValue` API performance. --- CHANGELOG.md | 1 + lib/graphTypes.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f14727cc..db965455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixed - Improved `types.isObject` internal API performance. +- Improved `graphTypes.isValue` internal API performance. ## 8.1.0 - 2022-08-29 diff --git a/lib/graphTypes.js b/lib/graphTypes.js index 3d06d6cf..d7d0bbba 100644 --- a/lib/graphTypes.js +++ b/lib/graphTypes.js @@ -52,7 +52,7 @@ api.isValue = v => // Note: A value is a @value if all of these hold true: // 1. It is an Object. // 2. It has the @value property. - types.isObject(v) && ('@value' in v); + types.isObject(v) && v['@value'] !== undefined; /** * Returns true if the given value is a @list. From dd3c678fb040490199d54386df94103e5ecbb95f Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 2 Feb 2023 16:46:54 -0500 Subject: [PATCH 04/15] Use property access check against undefined instead of `in`. --- CHANGELOG.md | 2 +- lib/graphTypes.js | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db965455..befd95dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixed - Improved `types.isObject` internal API performance. -- Improved `graphTypes.isValue` internal API performance. +- Improved `graphTypes.*` internal API performance. ## 8.1.0 - 2022-08-29 diff --git a/lib/graphTypes.js b/lib/graphTypes.js index d7d0bbba..15b652a4 100644 --- a/lib/graphTypes.js +++ b/lib/graphTypes.js @@ -65,7 +65,7 @@ api.isList = v => // Note: A value is a @list if all of these hold true: // 1. It is an Object. // 2. It has the @list property. - types.isObject(v) && ('@list' in v); + types.isObject(v) && v['@list'] !== undefined; /** * Returns true if the given value is a @graph. @@ -78,7 +78,7 @@ api.isGraph = v => { // 2. It has an `@graph` key. // 3. It may have '@id' or '@index' return types.isObject(v) && - '@graph' in v && + v['@graph'] !== undefined && Object.keys(v) .filter(key => key !== '@id' && key !== '@index').length === 1; }; @@ -93,7 +93,7 @@ api.isSimpleGraph = v => { // 1. It is an object. // 2. It has an `@graph` key. // 3. It has only 1 key or 2 keys where one of them is `@index`. - return api.isGraph(v) && !('@id' in v); + return api.isGraph(v) && v['@id'] === undefined; }; /** @@ -109,12 +109,13 @@ api.isBlankNode = v => { // 2. If it has an @id key that is not a string OR begins with '_:'. // 3. It has no keys OR is not a @value, @set, or @list. if(types.isObject(v)) { - if('@id' in v) { - const id = v['@id']; + const id = v['@id']; + if(id !== undefined) { return !types.isString(id) || id.indexOf('_:') === 0; } return (Object.keys(v).length === 0 || - !(('@value' in v) || ('@set' in v) || ('@list' in v))); + !(v['@value'] !== undefined || v['@set'] !== undefined || + v['@list'] !== undefined)); } return false; }; From 293ab458ed49eb9f237bc2330034e400d7701917 Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 2 Feb 2023 16:47:45 -0500 Subject: [PATCH 05/15] Move slower and less common keys check to the end of `isBlankNode`. --- lib/graphTypes.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/graphTypes.js b/lib/graphTypes.js index 15b652a4..6f608d39 100644 --- a/lib/graphTypes.js +++ b/lib/graphTypes.js @@ -113,9 +113,9 @@ api.isBlankNode = v => { if(id !== undefined) { return !types.isString(id) || id.indexOf('_:') === 0; } - return (Object.keys(v).length === 0 || - !(v['@value'] !== undefined || v['@set'] !== undefined || - v['@list'] !== undefined)); + return (v['@value'] === undefined && + v['@set'] === undefined && + v['@list'] === undefined) || Object.keys(v).length === 0; } return false; }; From cb5882a7731662f2bccb8130e664244a6c1e5e13 Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 2 Feb 2023 17:16:21 -0500 Subject: [PATCH 06/15] Do not use `in` operator when checking options in `addValue`. --- CHANGELOG.md | 1 + lib/util.js | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index befd95dc..e82d2f7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed - Improved `types.isObject` internal API performance. - Improved `graphTypes.*` internal API performance. +- Improved `util.addValue` performance. ## 8.1.0 - 2022-08-29 diff --git a/lib/util.js b/lib/util.js index 57bf9f74..db92938a 100644 --- a/lib/util.js +++ b/lib/util.js @@ -245,16 +245,16 @@ api.hasValue = (subject, property, value) => { */ api.addValue = (subject, property, value, options) => { options = options || {}; - if(!('propertyIsArray' in options)) { + if(options.propertyIsArray === undefined) { options.propertyIsArray = false; } - if(!('valueIsArray' in options)) { + if(options.valueIsArray === undefined) { options.valueIsArray = false; } - if(!('allowDuplicate' in options)) { + if(options.allowDuplicate === undefined) { options.allowDuplicate = true; } - if(!('prependValue' in options)) { + if(options.prependValue === undefined) { options.prependValue = false; } From 31392768249f9353581bf9f17872dc19887349cd Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 2 Feb 2023 17:39:51 -0500 Subject: [PATCH 07/15] Split `addValue` into internal helpers to enable better optimization. --- lib/util.js | 89 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/lib/util.js b/lib/util.js index db92938a..035b2351 100644 --- a/lib/util.js +++ b/lib/util.js @@ -258,43 +258,7 @@ api.addValue = (subject, property, value, options) => { options.prependValue = false; } - if(options.valueIsArray) { - subject[property] = value; - } else if(types.isArray(value)) { - if(value.length === 0 && options.propertyIsArray && - !subject.hasOwnProperty(property)) { - subject[property] = []; - } - if(options.prependValue) { - value = value.concat(subject[property]); - subject[property] = []; - } - for(let i = 0; i < value.length; ++i) { - api.addValue(subject, property, value[i], options); - } - } else if(subject.hasOwnProperty(property)) { - // check if subject already has value if duplicates not allowed - const hasValue = (!options.allowDuplicate && - api.hasValue(subject, property, value)); - - // make property an array if value not present or always an array - if(!types.isArray(subject[property]) && - (!hasValue || options.propertyIsArray)) { - subject[property] = [subject[property]]; - } - - // add new value - if(!hasValue) { - if(options.prependValue) { - subject[property].unshift(value); - } else { - subject[property].push(value); - } - } - } else { - // add new value as set or single value - subject[property] = options.propertyIsArray ? [value] : value; - } + _addValue(subject, property, value, options); }; /** @@ -420,6 +384,57 @@ api.compareShortestLeast = (a, b) => { return (a < b) ? -1 : 1; }; +// internal helper for `api.addValue` +function _addValue(subject, property, value, options) { + if(options.valueIsArray) { + subject[property] = value; + } else if(types.isArray(value)) { + _addToArray(subject, property, value, options); + } else if(subject.hasOwnProperty(property)) { + _addToObject(subject, property, value, options); + } else { + // add new value as set or single value + subject[property] = options.propertyIsArray ? [value] : value; + } +} + +// internal helper for `api.addValue` +function _addToArray(subject, property, value, options) { + if(value.length === 0 && options.propertyIsArray && + !subject.hasOwnProperty(property)) { + subject[property] = []; + } + if(options.prependValue) { + value = value.concat(subject[property]); + subject[property] = []; + } + for(let i = 0; i < value.length; ++i) { + _addValue(subject, property, value[i], options); + } +} + +// internal helper for `api.addValue` +function _addToObject(subject, property, value, options) { + // check if subject already has value if duplicates not allowed + const hasValue = (!options.allowDuplicate && + api.hasValue(subject, property, value)); + + // make property an array if value not present or always an array + if(!types.isArray(subject[property]) && + (!hasValue || options.propertyIsArray)) { + subject[property] = [subject[property]]; + } + + // add new value + if(!hasValue) { + if(options.prependValue) { + subject[property].unshift(value); + } else { + subject[property].push(value); + } + } +} + /** * Labels the blank nodes in the given value using the given IdentifierIssuer. * From a66a29b6acaa63b560db3c78b7fc4df5c317a19b Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 2 Feb 2023 17:40:37 -0500 Subject: [PATCH 08/15] Optimize `compareValues` `@id` comparison. --- lib/util.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/util.js b/lib/util.js index 035b2351..74f9550a 100644 --- a/lib/util.js +++ b/lib/util.js @@ -353,11 +353,10 @@ api.compareValues = (v1, v2) => { } // 3. equal @ids - if(types.isObject(v1) && - ('@id' in v1) && - types.isObject(v2) && - ('@id' in v2)) { - return v1['@id'] === v2['@id']; + if(types.isObject(v1) && types.isObject(v2)) { + const id1 = v1['@id']; + const id2 = v2['@id']; + return id1 !== undefined && v1['@id'] === v2['@id']; } return false; From a2b66ed8062ad840eea4cdfccdc6617d2675fb04 Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 2 Feb 2023 18:02:28 -0500 Subject: [PATCH 09/15] Update changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e82d2f7e..64ef716b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Improved `types.isObject` internal API performance. - Improved `graphTypes.*` internal API performance. - Improved `util.addValue` performance. +- Improved `util.compareValues` performance. ## 8.1.0 - 2022-08-29 From a6cdc730ec2517aab112c3f5b29328c555fb1d4b Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 2 Feb 2023 18:02:44 -0500 Subject: [PATCH 10/15] Simplify `_addToArray` internal helper. --- lib/util.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/util.js b/lib/util.js index 74f9550a..1d7396b1 100644 --- a/lib/util.js +++ b/lib/util.js @@ -402,10 +402,7 @@ function _addToArray(subject, property, value, options) { if(value.length === 0 && options.propertyIsArray && !subject.hasOwnProperty(property)) { subject[property] = []; - } - if(options.prependValue) { - value = value.concat(subject[property]); - subject[property] = []; + return; } for(let i = 0; i < value.length; ++i) { _addValue(subject, property, value[i], options); @@ -421,10 +418,18 @@ function _addToObject(subject, property, value, options) { // make property an array if value not present or always an array if(!types.isArray(subject[property]) && (!hasValue || options.propertyIsArray)) { - subject[property] = [subject[property]]; + if(hasValue) { + // value already present, just convert to an array + subject[property] = [subject[property]]; + } else if(options.prependValue) { + subject[property] = [value, subject[property]]; + } else { + subject[property] = [subject[property], value]; + } + return; } - // add new value + // add new value if not present if(!hasValue) { if(options.prependValue) { subject[property].unshift(value); From 19fda9228b60c507070fef330a026cc35074a4b3 Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 2 Feb 2023 18:08:56 -0500 Subject: [PATCH 11/15] Rename internal helpers. --- lib/util.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/util.js b/lib/util.js index 1d7396b1..7706cf6b 100644 --- a/lib/util.js +++ b/lib/util.js @@ -388,9 +388,9 @@ function _addValue(subject, property, value, options) { if(options.valueIsArray) { subject[property] = value; } else if(types.isArray(value)) { - _addToArray(subject, property, value, options); + _addArrayValue(subject, property, value, options); } else if(subject.hasOwnProperty(property)) { - _addToObject(subject, property, value, options); + _addToExistingProperty(subject, property, value, options); } else { // add new value as set or single value subject[property] = options.propertyIsArray ? [value] : value; @@ -398,11 +398,10 @@ function _addValue(subject, property, value, options) { } // internal helper for `api.addValue` -function _addToArray(subject, property, value, options) { +function _addArrayValue(subject, property, value, options) { if(value.length === 0 && options.propertyIsArray && !subject.hasOwnProperty(property)) { subject[property] = []; - return; } for(let i = 0; i < value.length; ++i) { _addValue(subject, property, value[i], options); @@ -410,7 +409,7 @@ function _addToArray(subject, property, value, options) { } // internal helper for `api.addValue` -function _addToObject(subject, property, value, options) { +function _addToExistingProperty(subject, property, value, options) { // check if subject already has value if duplicates not allowed const hasValue = (!options.allowDuplicate && api.hasValue(subject, property, value)); From 727adab286fb18c97a5ba9bd43e63f84a90852bf Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 2 Feb 2023 18:53:43 -0500 Subject: [PATCH 12/15] Remove some unnecessary comparisons in `addValue` loop. --- lib/util.js | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/lib/util.js b/lib/util.js index 7706cf6b..e810507b 100644 --- a/lib/util.js +++ b/lib/util.js @@ -191,7 +191,7 @@ api.validateTypeValue = (v, isFrame) => { api.hasProperty = (subject, property) => { if(subject.hasOwnProperty(property)) { const value = subject[property]; - return (!types.isArray(value) || value.length > 0); + return !types.isArray(value) || value.length > 0; } return false; }; @@ -206,24 +206,8 @@ api.hasProperty = (subject, property) => { * @return true if the value exists, false if not. */ api.hasValue = (subject, property, value) => { - if(api.hasProperty(subject, property)) { - let val = subject[property]; - const isList = graphTypes.isList(val); - if(types.isArray(val) || isList) { - if(isList) { - val = val['@list']; - } - for(let i = 0; i < val.length; ++i) { - if(api.compareValues(value, val[i])) { - return true; - } - } - } else if(!types.isArray(value)) { - // avoid matching the set of values with an array value parameter - return api.compareValues(value, val); - } - } - return false; + return api.hasProperty(subject, property) && + _hasValue(subject, property, value); }; /** @@ -412,7 +396,7 @@ function _addArrayValue(subject, property, value, options) { function _addToExistingProperty(subject, property, value, options) { // check if subject already has value if duplicates not allowed const hasValue = (!options.allowDuplicate && - api.hasValue(subject, property, value)); + _hasValue(subject, property, value)); // make property an array if value not present or always an array if(!types.isArray(subject[property]) && @@ -438,6 +422,26 @@ function _addToExistingProperty(subject, property, value, options) { } } +// internal helper that assumes `subject` has `property` +function _hasValue(subject, property, value) { + let val = subject[property]; + const isList = graphTypes.isList(val); + const isArray = types.isArray(val); + if(isArray || isList) { + if(isList) { + val = val['@list']; + } + for(let i = 0; i < val.length; ++i) { + if(api.compareValues(value, val[i])) { + return true; + } + } + } else if(!isArray) { + // avoid matching the set of values with an array value parameter + return api.compareValues(value, val); + } +} + /** * Labels the blank nodes in the given value using the given IdentifierIssuer. * From a6c4a7fab63aa37db62f1426a230ba09ac6d5492 Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 2 Feb 2023 20:11:33 -0500 Subject: [PATCH 13/15] Update copyrights. --- lib/graphTypes.js | 4 ++-- lib/types.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/graphTypes.js b/lib/graphTypes.js index 6f608d39..ac8ca410 100644 --- a/lib/graphTypes.js +++ b/lib/graphTypes.js @@ -1,5 +1,5 @@ -/* - * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. +/*! + * Copyright (c) 2017-2023 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; diff --git a/lib/types.js b/lib/types.js index e04a0ca5..38cc4402 100644 --- a/lib/types.js +++ b/lib/types.js @@ -1,5 +1,5 @@ -/* - * Copyright (c) 2017 Digital Bazaar, Inc. All rights reserved. +/*! + * Copyright (c) 2017-2023 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; From 2e8b8057e48a035ac55ebd938fc3afbd60a04c4e Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Thu, 2 Feb 2023 20:22:56 -0500 Subject: [PATCH 14/15] Refactor `addValue` code to eliminate additional checks. --- lib/util.js | 148 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 103 insertions(+), 45 deletions(-) diff --git a/lib/util.js b/lib/util.js index e810507b..f4c8820f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,5 +1,5 @@ -/* - * Copyright (c) 2017-2019 Digital Bazaar, Inc. All rights reserved. +/*! + * Copyright (c) 2017-2023 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; @@ -206,8 +206,16 @@ api.hasProperty = (subject, property) => { * @return true if the value exists, false if not. */ api.hasValue = (subject, property, value) => { - return api.hasProperty(subject, property) && - _hasValue(subject, property, value); + if(!api.hasProperty(subject, property)) { + return false; + } + let target = subject[property]; + const isList = graphTypes.isList(target); + if(isList) { + target = target['@list']; + } + const isArray = types.isArray(target); + return _hasValue(target, isArray, value); }; /** @@ -340,7 +348,7 @@ api.compareValues = (v1, v2) => { if(types.isObject(v1) && types.isObject(v2)) { const id1 = v1['@id']; const id2 = v2['@id']; - return id1 !== undefined && v1['@id'] === v2['@id']; + return id1 !== undefined && id1 === id2; } return false; @@ -369,11 +377,23 @@ api.compareShortestLeast = (a, b) => { // internal helper for `api.addValue` function _addValue(subject, property, value, options) { + // if value is an array, assume no special checks, just set it if(options.valueIsArray) { subject[property] = value; - } else if(types.isArray(value)) { - _addArrayValue(subject, property, value, options); - } else if(subject.hasOwnProperty(property)) { + return; + } + + // handle adding multiple values + const multipleValues = types.isArray(value); + if(multipleValues) { + // array of length `1` is handled as a single value below + if(value.length !== 1) { + return _addValues(subject, property, value, options); + } + value = value[0]; + } + + if(subject.hasOwnProperty(property)) { _addToExistingProperty(subject, property, value, options); } else { // add new value as set or single value @@ -381,65 +401,103 @@ function _addValue(subject, property, value, options) { } } -// internal helper for `api.addValue` -function _addArrayValue(subject, property, value, options) { - if(value.length === 0 && options.propertyIsArray && - !subject.hasOwnProperty(property)) { - subject[property] = []; +// internal helper for `api.addValue`; `value.length !== 1` +function _addValues(subject, property, value, options) { + // handle empty array + if(value.length === 0) { + // ensure property is set to an array + if(options.propertyIsArray && !subject.hasOwnProperty(property)) { + subject[property] = []; + } + return; } - for(let i = 0; i < value.length; ++i) { - _addValue(subject, property, value[i], options); + + // add each element of `value` to the `target`, which may start out as + // `undefined` if `property` is not yet set in `subject` + let target = subject[property]; + let isArray = types.isArray(target); + for(const nextValue of value) { + // if no target set yet... + if(target === undefined) { + if(options.propertyIsArray) { + target = [nextValue]; + isArray = true; + } else { + target = nextValue; + } + continue; + } + + // if duplicates not allowed, skip if `nextValue` is a dupe + if(!options.allowDuplicate) { + if(_hasValue(target, isArray, nextValue)) { + continue; + } + } + + // add `nextValue` to target + if(isArray) { + if(options.prependValue) { + target.unshift(nextValue); + } else { + target.push(nextValue); + } + } else { + if(options.prependValue) { + target = [nextValue, target]; + } else { + target = [target, nextValue]; + } + isArray = true; + } } + // ensure subject property value is updated to `target` + subject[property] = target; } // internal helper for `api.addValue` function _addToExistingProperty(subject, property, value, options) { - // check if subject already has value if duplicates not allowed - const hasValue = (!options.allowDuplicate && - _hasValue(subject, property, value)); + const target = subject[property]; + const isArray = types.isArray(target); - // make property an array if value not present or always an array - if(!types.isArray(subject[property]) && - (!hasValue || options.propertyIsArray)) { + // consider subject has having value if duplicates are allowed or if target + // has no matching value + const hasValue = !options.allowDuplicate && + _hasValue(target, isArray, value); + + if(!isArray) { + // make property value an array if value not present or always an array if(hasValue) { - // value already present, just convert to an array - subject[property] = [subject[property]]; + if(options.propertyIsArray) { + // value already present, just convert to an array + subject[property] = [target]; + } } else if(options.prependValue) { - subject[property] = [value, subject[property]]; + subject[property] = [value, target]; } else { - subject[property] = [subject[property], value]; + subject[property] = [target, value]; } return; } - // add new value if not present + // add new value to target array if not present if(!hasValue) { if(options.prependValue) { - subject[property].unshift(value); + target.unshift(value); } else { - subject[property].push(value); + target.push(value); } } } -// internal helper that assumes `subject` has `property` -function _hasValue(subject, property, value) { - let val = subject[property]; - const isList = graphTypes.isList(val); - const isArray = types.isArray(val); - if(isArray || isList) { - if(isList) { - val = val['@list']; - } - for(let i = 0; i < val.length; ++i) { - if(api.compareValues(value, val[i])) { - return true; - } - } - } else if(!isArray) { - // avoid matching the set of values with an array value parameter - return api.compareValues(value, val); +// internal helper to see if `target` has `value`; `target` can be an array +// to look for `value` in or a simple value / undefined to compare against +// `value` +function _hasValue(target, isArray, value) { + if(isArray) { + return target.some(t => api.compareValues(value, t)); } + return api.compareValues(value, target); } /** From 9460289ea6c2dfec20f0193b6e826d597b4d9ff4 Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Sun, 5 Feb 2023 12:10:05 -0500 Subject: [PATCH 15/15] Fix comment. --- lib/nodeMap.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/nodeMap.js b/lib/nodeMap.js index 9e61d5e0..e62c957b 100644 --- a/lib/nodeMap.js +++ b/lib/nodeMap.js @@ -201,6 +201,7 @@ api.createNodeMap = (input, graphs, graph, issuer, name, list) => { {propertyIsArray: true, allowDuplicate: false}); api.createNodeMap(o, graphs, graph, issuer, id); } else if(graphTypes.isValue(o)) { + // handle @value util.addValue( subject, property, o, {propertyIsArray: true, allowDuplicate: false}); @@ -213,7 +214,7 @@ api.createNodeMap = (input, graphs, graph, issuer, name, list) => { subject, property, o, {propertyIsArray: true, allowDuplicate: false}); } else { - // handle @value + // handle remaining cases api.createNodeMap(o, graphs, graph, issuer, name); util.addValue( subject, property, o, {propertyIsArray: true, allowDuplicate: false});