From 869a55035c19feca192807a8286cb2449260be5a Mon Sep 17 00:00:00 2001 From: Kyriakos Barbounakis Date: Thu, 16 Jan 2025 13:31:48 +0200 Subject: [PATCH] use uknown json attribute (#189) --- OnJsonAttribute.js | 42 +++++++++++++++++++++++++-- data-model.js | 12 ++++++-- spec/JsonAttribute.spec.ts | 21 ++++++++++++++ spec/test2/config/models/Product.json | 7 ++++- 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/OnJsonAttribute.js b/OnJsonAttribute.js index 4c24635..1de331b 100644 --- a/OnJsonAttribute.js +++ b/OnJsonAttribute.js @@ -168,14 +168,52 @@ class OnJsonAttribute { * @returns void */ static afterSelect(event, callback) { - const jsonAttributes = event.model.attributes.filter((attr) => { - return attr.type === 'Json' && attr.additionalType != null && attr.model === event.model.name; + const anyJsonAttributes = event.model.attributes.filter((attr) => { + return attr.type === 'Json' && attr.model === event.model.name; + }); + if (anyJsonAttributes.length === 0) { + return callback(); + } + const jsonAttributes = anyJsonAttributes.filter((attr) => { + return attr.additionalType != null; }).map((attr) => { return attr.name }); + // if there are no json attributes with additional type if (jsonAttributes.length === 0) { + // get json attributes with no additional type + const unknownJsonAttributes = anyJsonAttributes.filter((attr) => { + return attr.additionalType == null; + }).map((attr) => { + return attr.name + }); + // parse json for each item + if (unknownJsonAttributes.length > 0) { + const parseUnknownJson = (item) => { + unknownJsonAttributes.forEach((name) => { + if (Object.prototype.hasOwnProperty.call(item, name)) { + const value = item[name]; + if (typeof value === 'string') { + item[name] = JSON.parse(value); + } + } + }); + }; + // iterate over result + const {result} = event; + if (result == null) { + return callback(); + } + if (Array.isArray(result)) { + result.forEach((item) => parseUnknownJson(item)); + } else { + // or parse json for single item + parseUnknownJson(result) + } + } return callback(); } + let select = []; const { viewAdapter: entity } = event.model; if (event.emitter && event.emitter.query && event.emitter.query.$select) { diff --git a/data-model.js b/data-model.js index d4e2ba4..1913e95 100644 --- a/data-model.js +++ b/data-model.js @@ -23,7 +23,7 @@ var DataObjectAssociationListener = dataAssociations.DataObjectAssociationListen var {DataModelView} = require('./data-model-view'); var {DataFilterResolver} = require('./data-filter-resolver'); var Q = require('q'); -var {SequentialEventEmitter} = require('@themost/common'); +var {SequentialEventEmitter, Args} = require('@themost/common'); var {LangUtils} = require('@themost/common'); var {TraceUtils} = require('@themost/common'); var {DataError} = require('@themost/common'); @@ -46,6 +46,7 @@ var { OnJsonAttribute } = require('./OnJsonAttribute'); var { isObjectDeep } = require('./is-object'); var { DataStateValidatorListener } = require('./data-state-validator'); var resolver = require('./data-expand-resolver'); +var { isArrayLikeObject } = require('lodash/isArrayLikeObject'); /** * @this DataModel * @param {DataField} field @@ -1507,7 +1508,14 @@ function cast_(obj, state) { if (mapping == null) { var {[name]: value} = obj; if (x.type === 'Json') { - result[x.name] = isObjectDeep(value) ? JSON.stringify(value) : null; + // check if value is an object or an array + if (value == null) { + result[x.name] = null; + } else { + var isObjectOrArray = isObjectDeep(value) || isArrayLikeObject(value); + Args.check(isObjectOrArray, new DataError('ERR_VALUE','Invalid attribute value. Expected a valid object or an array.', null, self.name, x.name)); + result[x.name] = JSON.stringify(value); + } } else { result[x.name] = value; } diff --git a/spec/JsonAttribute.spec.ts b/spec/JsonAttribute.spec.ts index fab9712..678efaa 100644 --- a/spec/JsonAttribute.spec.ts +++ b/spec/JsonAttribute.spec.ts @@ -49,6 +49,27 @@ describe('JsonAttribute', () => { }); }); + it('should update unknown json attribute', async () => { + await TestUtils.executeInTransaction(context, async () => { + const Products = context.model('Product').silent(); + let item = await Products.where('name').equal('Apple MacBook Air (13.3-inch, 2013 Version)').getItem() + expect(item).toBeTruthy(); + item.extraAttributes = { + cpu: 'Intel Core i5', + ram: '8GB' + } + await Products.save(item); + expect(item.dateCreated).toBeTruthy(); + item = await Products.where('name').equal('Apple MacBook Air (13.3-inch, 2013 Version)') + .select('extraAttributes/cpu as cpu').getItem(); + expect(item).toBeTruthy(); + expect(item.cpu).toBe('Intel Core i5'); + item = await Products.where('name').equal('Apple MacBook Air (13.3-inch, 2013 Version)').getItem(); + expect(item).toBeTruthy(); + expect(item.extraAttributes).toBeTruthy(); + }); + }); + it('should update json structured value', async () => { await TestUtils.executeInTransaction(context, async () => { const Products = context.model('Product').silent(); diff --git a/spec/test2/config/models/Product.json b/spec/test2/config/models/Product.json index 9c57073..537d611 100644 --- a/spec/test2/config/models/Product.json +++ b/spec/test2/config/models/Product.json @@ -5,7 +5,7 @@ "hidden": false, "sealed": false, "abstract": false, - "version": "2.2.1", + "version": "2.2.2", "inherits": "Thing", "caching": "conditional", "fields": [ @@ -232,6 +232,11 @@ "type": "Json", "indexed": true, "additionalType": "ProductMetadata" + }, + { + "name": "extraAttributes", + "many": false, + "type": "Json" } ], "constraints": [