diff --git a/packages/binding-http/src/http-server.ts b/packages/binding-http/src/http-server.ts index 27cfd5e34..414bd4ba6 100644 --- a/packages/binding-http/src/http-server.ts +++ b/packages/binding-http/src/http-server.ts @@ -320,7 +320,7 @@ export default class HttpServer implements ProtocolServer { for (const [inUri, toUri] of Object.entries(this.urlRewrite)) { const endsWithToUri: boolean = form.href.endsWith(toUri); if (endsWithToUri) { - const form2 = structuredClone(form); + const form2 = Helpers.structuredClone(form); form2.href = form2.href.substring(0, form.href.lastIndexOf(toUri)) + inUri; forms.push(form2); debug(`HttpServer on port ${this.getPort()} assigns urlRewrite '${form2.href}' for '${form.href}'`); diff --git a/packages/binding-opcua/test/opcua-codec-test.ts b/packages/binding-opcua/test/opcua-codec-test.ts index deaa9cfd8..e751606db 100644 --- a/packages/binding-opcua/test/opcua-codec-test.ts +++ b/packages/binding-opcua/test/opcua-codec-test.ts @@ -16,7 +16,7 @@ import { exist } from "should"; import { expect } from "chai"; -import { ContentSerdes, createLoggers } from "@node-wot/core"; +import { ContentSerdes, Helpers, createLoggers } from "@node-wot/core"; import { ObjectSchema } from "@node-wot/td-tools"; import { DataValue } from "node-opcua-data-value"; @@ -49,13 +49,9 @@ const dataValue3 = new DataValue({ }, }); -function jsonify(a: unknown): unknown { - return JSON.parse(JSON.stringify(a)); -} - -const dataValue1Json = jsonify(opcuaJsonEncodeDataValue(dataValue1, true)); -const dataValue2Json = jsonify(opcuaJsonEncodeDataValue(dataValue2, true)); -const dataValue3Json = jsonify(opcuaJsonEncodeDataValue(dataValue3, true)); +const dataValue1Json = Helpers.structuredClone(opcuaJsonEncodeDataValue(dataValue1, true)); +const dataValue2Json = Helpers.structuredClone(opcuaJsonEncodeDataValue(dataValue2, true)); +const dataValue3Json = Helpers.structuredClone(opcuaJsonEncodeDataValue(dataValue3, true)); describe("OPCUA Binary Serdes ", () => { [dataValue1Json, dataValue2Json, dataValue3Json].forEach((dataValue, index) => { @@ -65,7 +61,7 @@ describe("OPCUA Binary Serdes ", () => { it("should encode and decode a dataValue with application/opcua+binary codec " + index, () => { const payload = theOpcuaBinaryCodec.valueToBytes(dataValue as DataValue | DataValueJSON, schema); const dataValueReloaded = theOpcuaBinaryCodec.bytesToValue(payload, schema); - expect(dataValue).to.eql(jsonify(dataValueReloaded)); + expect(dataValue).to.eql(Helpers.structuredClone(dataValueReloaded)); }); }); }); @@ -77,7 +73,7 @@ describe("OPCUA JSON Serdes ", () => { it("should encode and decode a dataValue with application/opcua+json codec " + index, () => { const payload = theOpcuaJSONCodec.valueToBytes(dataValue, schema); const dataValueReloaded = theOpcuaJSONCodec.bytesToValue(payload, schema); - expect(dataValue).to.eql(jsonify(dataValueReloaded)); + expect(dataValue).to.eql(Helpers.structuredClone(dataValueReloaded)); }); }); const expected1 = [ diff --git a/packages/binding-opcua/test/schema-validation-test.ts b/packages/binding-opcua/test/schema-validation-test.ts index 7f069df3a..1d5e1496c 100644 --- a/packages/binding-opcua/test/schema-validation-test.ts +++ b/packages/binding-opcua/test/schema-validation-test.ts @@ -13,7 +13,7 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import { createLoggers } from "@node-wot/core"; +import { Helpers, createLoggers } from "@node-wot/core"; import { expect } from "chai"; import { DataType, DataValue, StatusCodes, VariantArrayType } from "node-opcua-client"; @@ -87,7 +87,7 @@ describe("schemas", () => { Object.entries(data).forEach(([name, obj1]) => { it("DataValue " + name, () => { - const dataValueJSON = JSON.parse(JSON.stringify(opcuaJsonEncodeDataValue(obj1, true))); + const dataValueJSON = Helpers.structuredClone(opcuaJsonEncodeDataValue(obj1, true)); const isValid = validate(dataValueJSON); if (!isValid) { diff --git a/packages/core/src/consumed-thing.ts b/packages/core/src/consumed-thing.ts index dd40b6797..5df153678 100644 --- a/packages/core/src/consumed-thing.ts +++ b/packages/core/src/consumed-thing.ts @@ -365,7 +365,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { this.actions = {}; this.events = {}; - const deepClonedModel = structuredClone(thingModel); + const deepClonedModel = Helpers.structuredClone(thingModel); Object.assign(this, deepClonedModel); this.extendInteractions(); } diff --git a/packages/core/src/exposed-thing.ts b/packages/core/src/exposed-thing.ts index 5a9894b5d..57074c84d 100644 --- a/packages/core/src/exposed-thing.ts +++ b/packages/core/src/exposed-thing.ts @@ -124,7 +124,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { // Deep clone the Thing Model // without functions or methods - const deepClonedModel = JSON.parse(JSON.stringify(thingModel)); + const deepClonedModel = Helpers.structuredClone(thingModel); Object.assign(this, deepClonedModel); // unset "@type":"tm:ThingModel" ? diff --git a/packages/core/src/helpers.ts b/packages/core/src/helpers.ts index 1dfd7e84b..20f93651c 100644 --- a/packages/core/src/helpers.ts +++ b/packages/core/src/helpers.ts @@ -152,6 +152,14 @@ export default class Helpers implements Resolver { } } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + static structuredClone(value: T): T { + // TODO built-in structuredClone() still seems to cause issues + // see for example https://github.com/eclipse-thingweb/node-wot/issues/1252 + // return structuredClone(value); // Note: use in future + return JSON.parse(JSON.stringify(value)); + } + // TODO: specialize fetch to retrieve just thing descriptions // see https://github.com/eclipse-thingweb/node-wot/issues/1055 public fetch(uri: string): Promise { @@ -220,7 +228,7 @@ export default class Helpers implements Resolver { * Helper function to remove reserved keywords in required property of TD JSON Schema */ static createExposeThingInitSchema(tdSchema: unknown): SomeJSONSchema { - const tdSchemaCopy = structuredClone(tdSchema) as SomeJSONSchema; + const tdSchemaCopy = Helpers.structuredClone(tdSchema) as SomeJSONSchema; if (tdSchemaCopy.required !== undefined) { const reservedKeywords: Array = [ diff --git a/packages/core/src/servient.ts b/packages/core/src/servient.ts index 1a9111dfc..a92bd238e 100644 --- a/packages/core/src/servient.ts +++ b/packages/core/src/servient.ts @@ -21,6 +21,7 @@ import { ProtocolClientFactory, ProtocolServer, ProtocolClient } from "./protoco import ContentManager, { ContentCodec } from "./content-serdes"; import { v4 } from "uuid"; import { createLoggers } from "./logger"; +import { Helpers } from "./core"; const { debug, warn } = createLoggers("core", "servient"); @@ -49,7 +50,7 @@ export default class Servient { debug(`Servient exposing '${thing.title}'`); // What is a good way to to convey forms information like contentType et cetera for interactions - const tdTemplate: WoT.ThingDescription = JSON.parse(JSON.stringify(thing)); + const tdTemplate: WoT.ThingDescription = Helpers.structuredClone(thing) as WoT.ThingDescription; // initializing forms fields thing.forms = [];