Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Convert RecordId from Class to JSON-object while maintaining JSON-structure #301

Open
2 tasks done
OTheNonE opened this issue Jul 4, 2024 · 5 comments
Open
2 tasks done

Comments

@OTheNonE
Copy link

OTheNonE commented Jul 4, 2024

Is your feature request related to a problem?

I have a problem regarding using surrealdb.js on the server and having to send data from the server to the client. I want to send the RecordId of an record to the client in a JSON format instead of a tb:id-string. Though when trying to do this, i stumble upon the following problems:

  • It is not possible to send classes from server to client.
  • When using jsonify, the RecordId is converted into a string of the form tb:id.

Therefore, i have no simple way of returning data from the server to the client while maintaining the JSON-object in the recordId.

Describe the solution

It would be nice to have a utility function or a method implemented on the class that could convert the recordId class into JSON and maintain the JSON-structure:

const rid = new RecordId("recording", { city: "London", data: 123 });
const rid_json = rid.toJSONObject()
rid_json.city // "London"

// Send the rid_json:
fetch("https://example.com/abc", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify(rid_json),
})

Alternative methods

Write my own utility function, or also store the data elsewhere than in the recordId.

SurrealDB version

surreal 1.5.3

SurrealDB.js version

1.0.0-beta-12

Contact Details

[email protected]

Is there an existing issue for this?

  • I have searched the existing issues

Code of Conduct

  • I agree to follow this project's Code of Conduct
@OTheNonE OTheNonE changed the title Feature: Convert RecordId from Class to JSON-object maintaining JSON-structure Feature: Convert RecordId from Class to JSON-object while maintaining JSON-structure Jul 4, 2024
@kearfy
Copy link
Member

kearfy commented Jul 4, 2024

Heya! You should be able to simply obtain the id part by selecting the id property from the RecordId. The jsonify utility simply outputs the JSON-like value that SurrealDB would

@akkie
Copy link

akkie commented Aug 17, 2024

I also have this problem with SvelteKit. Only POJOs can be returned from load functions. Would be great if the SDK would have a function that converts the record ID to a POJO. In the meantime here is a workaround:

function convertRecordIds<T>(obj: T): T {
  if (obj instanceof RecordId) {
    return { tb: obj.tb, id: obj.id } as T;
  } else if (Array.isArray(obj)) {
    return obj.map(convertRecordIds) as unknown as T;
  } else if (typeof obj === 'object' && obj !== null) {
    return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, convertRecordIds(value)])) as T;
  } else {
    return obj;
  }
}

And a function which converts it back:

export function convertToRecordId<T>(obj: T): T {
  if (typeof obj === 'object' && obj !== null && 'tb' in obj && 'id' in obj) {
    return new RecordId(obj.tb as string, obj.id as RecordIdValue) as T;
  } else if (Array.isArray(obj)) {
    return obj.map(convertToRecordId) as unknown as T;
  } else if (typeof obj === 'object' && obj !== null) {
    return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, convertToRecordId(value)])) as T;
  } else {
    return obj;
  }
}

Types could be optimized, but it will do the job for now.

@akkie
Copy link

akkie commented Oct 11, 2024

One thought regarding the jsonify function and an opposite function surrealify. Could we add an additional param to jsonify which acts as a register for keys and types that were transformed? Then the surrealify function could use that register to convert the data back into the source format.

jsonify({
    rid: new RecordId("person", "tobie"),
    foo: {
       bar: new Duration("1d2h"),
       baz: [
           new Uuid("92b84bde-39c8-4b4b-92f7-626096d6c4d9"),
           new Uuid("da3f72a2-261b-468a-b3f6-374cbcbb15b6")
       ]
    }
}, '__register');
{
    rid: "person:tobie",
    foo: {
        bar: "1d2h",
        baz: [
            "92b84bde-39c8-4b4b-92f7-626096d6c4d9",
            "da3f72a2-261b-468a-b3f6-374cbcbb15b6"
        ]
    },
    __register: {
        "rid": "RecordId",
        "foo.bar": "Duration",
        "foo.baz[0]": "Uuid",
        "foo.baz[1]": "Uuid",
    }
}

And then the surrealify could use that register:

surrealify({
    rid: "person:tobie",
    foo: {
        bar: "1d2h",
        baz: [
            "92b84bde-39c8-4b4b-92f7-626096d6c4d9",
            "da3f72a2-261b-468a-b3f6-374cbcbb15b6"
        ]
    },
    __register: {
        "rid": "RecordId",
        "foo.bar": "Duration",
        "foo.baz[0]": "Uuid",
        "foo.baz[1]": "Uuid",
    }
}, '__register');

to create

{
    rid: new RecordId("person", "tobie"),
    foo: {
       bar: new Duration("1d2h"),
       baz: [
           new Uuid("92b84bde-39c8-4b4b-92f7-626096d6c4d9"),
           new Uuid("da3f72a2-261b-468a-b3f6-374cbcbb15b6")
       ]
    }
}

@AlbertMarashi
Copy link

AlbertMarashi commented Oct 11, 2024

I wrote my own Surreal wrapper to convert record IDs into non-class form. I also use this for SvelteKit.

import {
    RecordId as BaseRecordId, Surreal as BaseSurreal, type Prettify, type QueryParameters, type RecordIdValue
} from "surrealdb"

export interface RecordId<Tb extends string = string> {
    tb: Tb
    id: string
}

export function new_record<Tb extends string>(tb: Tb, id: string): RecordId<Tb> {
    return {
        tb,
        id
    }
}

export class Surreal extends BaseSurreal {
    async query<T extends unknown[]>(...args: QueryParameters): Promise<Prettify<T>> {
        if (args[1] instanceof Array) return this.convert_to_record_id(await super.query(...args)) as unknown as Promise<Prettify<T>>
        if (args[1]) args[1] = this.convert_to_record_id_class(args[1]) as Record<string, unknown>
        return this.convert_to_record_id(await super.query(...args)) as unknown as Promise<Prettify<T>>
    }

    private convert_to_record_id(value: unknown): unknown {
        if (value instanceof BaseRecordId) {
            return {
                tb: value.tb,
                id: this.convert_to_record_id(value.id),
            }
        }

        // if (Array.isArray(value)) return value.map(this.convert_to_record_id)
        if (value instanceof Date) return value
        if (typeof value === "object" && value !== null) {
            for (const [key, val] of Object.entries(value)) {
                (value as Record<string, unknown>)[key] = this.convert_to_record_id(val)
            }
            return value
        }
        return value
    }

    private convert_to_record_id_class(value: unknown): unknown {
        if (value instanceof BaseRecordId) return value
        if (Array.isArray(value)) return value.map(this.convert_to_record_id_class)
        if (value instanceof Date) return value
        if (typeof value === "object" && value !== null ) {
            if (is_record_id(value)) return new BaseRecordId(value.tb, this.convert_to_record_id(value.id) as RecordIdValue)
            const obj: Record<string, unknown> = {}
            for (const [key, val] of Object.entries(value)) {
                obj[key] = this.convert_to_record_id_class(val)
            }
            return obj
        }
        return value
    }
}

function is_record_id_value(id: unknown): id is RecordIdValue {
    if (typeof id === "string") return true
    if (typeof id === "number") return true
    if (typeof id === "boolean") return true
    if (typeof id === "bigint") return true
    if (Array.isArray(id)) return true
    if (typeof id === "object" && id !== null) return true
    return false
}

function is_record_id(value: unknown): value is RecordId<string> {
    if (
        typeof value === "object"
        && value !== null
        && "tb" in value
        && typeof value.tb === "string"
        && "id" in value
        && is_record_id_value(value.id)
    ) {
        return true
    }
    return false
}

@phisch
Copy link

phisch commented Nov 1, 2024

Stumbled on that same issue when sending a query result from a back-end to a front-end. Expected to be able to access the tb and id fields on the front-end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants