diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fe3cbbdc..068fd8676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - chore: update api-explorer to react 19 [#700](https://github.com/hypermodeinc/modus/pull/700) - fix: resolve warning in `deserializeRawMap` [#692](https://github.com/hypermodeinc/modus/pull/692) +- fix: add json serialization support for neo4j sdk types + [#699](https://github.com/hypermodeinc/modus/pull/699) ## 2025-01-09 - CLI 0.16.6 diff --git a/sdk/assemblyscript/src/assembly/__tests__/neo4j.spec.ts b/sdk/assemblyscript/src/assembly/__tests__/neo4j.spec.ts new file mode 100644 index 000000000..183c7044c --- /dev/null +++ b/sdk/assemblyscript/src/assembly/__tests__/neo4j.spec.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2024 Hypermode Inc. + * Licensed under the terms of the Apache License, Version 2.0 + * See the LICENSE file that accompanied this code for further details. + * + * SPDX-FileCopyrightText: 2024 Hypermode Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { JSON } from "json-as"; +import { expect, it, run } from "as-test"; +import { neo4j } from ".."; + +it("should stringify a simple record", () => { + const record = new neo4j.Record(); + record.Keys = ["name"]; + record.Values = ['"Alice"']; + + expect(JSON.stringify(record)).toBe('{"name":"Alice"}'); +}); + +it("should stringify different types of record values", () => { + const record = new neo4j.Record(); + record.Keys = ["name", "age", "friends"]; + record.Values = ['"Alice"', '"42"', '["Bob","Peter","Anna"]']; + + expect(JSON.stringify(record)).toBe( + '{"name":"Alice","age":"42","friends":["Bob","Peter","Anna"]}', + ); +}); + +run(); diff --git a/sdk/assemblyscript/src/assembly/neo4j.ts b/sdk/assemblyscript/src/assembly/neo4j.ts index 1eec82a7d..21cbcf907 100644 --- a/sdk/assemblyscript/src/assembly/neo4j.ts +++ b/sdk/assemblyscript/src/assembly/neo4j.ts @@ -48,14 +48,16 @@ export function executeQuery( return response; } + +@json export class EagerResult { Keys: string[] = []; Records: Record[] = []; } export class Record { - Values: string[] = []; Keys: string[] = []; + Values: string[] = []; get(key: string): string { for (let i = 0; i < this.Keys.length; i++) { @@ -110,6 +112,33 @@ export class Record { } return map; } + + __INITIALIZE(): this { + return this; + } + + __SERIALIZE(): string { + let result = "{"; + for (let i = 0; i < this.Keys.length; i++) { + const keyJson = JSON.stringify(this.Keys[i]); + result += `${keyJson}:${this.Values[i]}`; + if (i < this.Keys.length - 1) { + result += ","; + } + } + return result + "}"; + } + + /* eslint-disable @typescript-eslint/no-unused-vars */ + __DESERIALIZE( + data: string, + key_start: i32, + key_end: i32, + value_start: i32, + value_end: i32, + ): boolean { + throw new Error("Not implemented."); + } } diff --git a/sdk/assemblyscript/src/tests/neo4j.run.ts b/sdk/assemblyscript/src/tests/neo4j.run.ts new file mode 100644 index 000000000..d21182592 --- /dev/null +++ b/sdk/assemblyscript/src/tests/neo4j.run.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 Hypermode Inc. + * Licensed under the terms of the Apache License, Version 2.0 + * See the LICENSE file that accompanied this code for further details. + * + * SPDX-FileCopyrightText: 2024 Hypermode Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { readFileSync } from "fs"; +import { instantiate } from "../build/neo4j.spec.js"; +const binary = readFileSync("./build/neo4j.spec.wasm"); +const module = new WebAssembly.Module(binary); +instantiate(module, { + env: {}, + modus_models: {}, +}); diff --git a/sdk/go/pkg/neo4j/neo4j.go b/sdk/go/pkg/neo4j/neo4j.go index 6a846ced0..5c36b05bc 100644 --- a/sdk/go/pkg/neo4j/neo4j.go +++ b/sdk/go/pkg/neo4j/neo4j.go @@ -30,13 +30,13 @@ func WithDbName(dbName string) Neo4jOption { } type EagerResult struct { - Keys []string - Records []*Record + Keys []string `json:"Keys"` + Records []*Record `json:"Records"` } type Record struct { - Values []string - Keys []string + Values []string `json:"Values"` + Keys []string `json:"Keys"` } type RecordValue interface { @@ -93,17 +93,17 @@ type PropertyValue interface { // Point2D represents a two dimensional point in a particular coordinate reference system. type Point2D struct { - X float64 - Y float64 - SpatialRefId uint32 // Id of coordinate reference system. + X float64 `json:"X"` + Y float64 `json:"Y"` + SpatialRefId uint32 `json:"SpatialRefId"` // Id of coordinate reference system. } // Point3D represents a three dimensional point in a particular coordinate reference system. type Point3D struct { - X float64 - Y float64 - Z float64 - SpatialRefId uint32 // Id of coordinate reference system. + X float64 `json:"X"` + Y float64 `json:"Y"` + Z float64 `json:"Z"` + SpatialRefId uint32 `json:"SpatialRefId"` // Id of coordinate reference system. } // String returns string representation of this point. @@ -178,6 +178,22 @@ func (r *Record) AsMap() map[string]string { return result } +func (r *Record) JSONMarshal() ([]byte, error) { + result := "{" + for i, k := range r.Keys { + keyBytes, err := utils.JsonSerialize(k) + if err != nil { + return nil, err + } + result += fmt.Sprintf("%s:%s", keyBytes, r.Values[i]) + if i < len(r.Keys)-1 { + result += "," + } + } + result += "}" + return []byte(result), nil +} + func GetProperty[T PropertyValue](e Entity, key string) (T, error) { var val T rawVal, ok := e.GetProperties()[key] diff --git a/sdk/go/pkg/neo4j/neo4j_test.go b/sdk/go/pkg/neo4j/neo4j_test.go index 32989ea17..9b418d34a 100644 --- a/sdk/go/pkg/neo4j/neo4j_test.go +++ b/sdk/go/pkg/neo4j/neo4j_test.go @@ -21,6 +21,25 @@ var ( hostName = "myneo4j" ) +func TestRecordJSONMarshal(t *testing.T) { + record := neo4j.Record{ + Keys: []string{"name", "age", "friends"}, + Values: []string{`"Alice"`, `"42"`, `["Bob","Peter","Anna"]`}, + } + + // Marshal the record to JSON + jsonBytes, err := record.JSONMarshal() + if err != nil { + t.Fatalf("Expected no error, but received: %v", err) + } + + jsonStr := string(jsonBytes) + expectedJSONStr := `{"name":"Alice","age":"42","friends":["Bob","Peter","Anna"]}` + if jsonStr != expectedJSONStr { + t.Errorf("Expected JSON: %s, but received: %s", expectedJSONStr, jsonStr) + } +} + func TestExecuteQuery(t *testing.T) { dbName := "mydb" query := "query"