kit icon indicating copy to clipboard operation
kit copied to clipboard

Using json alternative in [Page] endpoints.

Open Beiri22 opened this issue 1 year ago • 5 comments

Describe the problem

The results of endpoints are transformed to and interpreted as JSON. But JSON does not deserialize some data types properly, like dates or maps.

Describe the proposed solution

Allow other de/serializers like superjson or cbor. It might be a simple configuration option that sets a serializer and deserializer function to be used alternatively.

Alternatives considered

No response

Importance

would make my life easier

Additional Information

No response

Beiri22 avatar Jul 24 '22 20:07 Beiri22

I'm... a little confused.

export const get = () => {
  const myComplicatedObject = getMyComplicatedObject();

  return: {
    headers: {
      'Content-Type': 'application/json'
    },
    body: myCustomSerializer(myComplicatedObject)
  }
}

Does that not work?

tcc-sejohnson avatar Jul 24 '22 22:07 tcc-sejohnson

The manual serialization might work in the endpoints, and you might manually deserialize your fetch result in the frontend. But as far as I understand, the fetching mechanism of page endpoints is not interruptable...

Beiri22 avatar Jul 25 '22 03:07 Beiri22

I'm trying to understand why it needs to be interruptable -- it seems like the worst case for using a custom serializer/deserializer is, what, three or four extra lines of code?:

// index.ts
export const get = () => {
  const myComplicatedObject = getMyComplicatedObject();

  return: {
    headers: {
      'Content-Type': 'application/json'
    },
    body: {
      content: myCustomSerializer(myComplicatedObject)
    }
  }
}
// index.svelte
<script lang="ts>
  export let content: string;
  let deserializedContent = customDeserializer(content);
</script>

Is there a more complex usecase that makes this harder?

tcc-sejohnson avatar Jul 25 '22 04:07 tcc-sejohnson

It's not the worst case, you're right. But it's boilerplate code on every page that I don't consider good style. Allowing the user to set a custom de/serializer to access the own backend seems more elegant to me. It's just my opinion... Let it be the following. At a global config level, you can register a pair of functions ser/deser for a given content-type. Then those are used when the content type is set in the endpoints. Might work like a charm, don't look like a hack and be a useful extension also for external API calls.

Beiri22 avatar Jul 25 '22 04:07 Beiri22

One gotcha this could help avoid is when the data changes between server and client. i.e. the content contains Date objects, but then ISO format strings on the client and ... errors (not a good experience, unclear why for new users).

CaptainCodeman avatar Aug 01 '22 01:08 CaptainCodeman

According to https://github.com/sveltejs/kit/issues/5960#issuecomment-1217964620 and https://github.com/sveltejs/kit/issues/5960#issuecomment-1218494704 it seems likely that Svelte-Kit will end up using https://github.com/rich-harris/devalue for sending data from the server to the client (e.g., in +page.server.ts load functions.

rmunn avatar Aug 18 '22 14:08 rmunn

That's only a solution if access to that server data is always through the matching SK front-end.

Maybe you have other apps or components that would like to fetch data in standard JSON format.

CaptainCodeman avatar Aug 18 '22 14:08 CaptainCodeman

If you're doing that, it sounds to me like it should be your responsibility for just deserializing it yourself. That sounds an awful lot simpler than telling SvelteKit how it should be automatically interpreting certain JSON responses. If you have an endpoint (either internal to the app or external to it) that returns data in a certain format, you just need to interpret it correctly.

Conduitry avatar Aug 18 '22 16:08 Conduitry

I would, if SvelteKit provided the hooks to enable it. But you get one kind of data on the server and different data (because it's been JSON serialized) on the client. If I could override the deserialization or set a reviver function to be used, it wouldn't be a problem. But the only way right now AFAIK is to do double serializing which is horrible.

CaptainCodeman avatar Aug 18 '22 19:08 CaptainCodeman

I was just about to open an issue about that. We have no way of passing a replacer function to JSON.stringify() on the server or a reviver function to JSON.parse() on the client because there are no hooks for that in Svelte Kit. I consider the ability to transform responses a very basic and important feature.

ghost avatar Aug 18 '22 19:08 ghost

Does #6008 address this? I'd be interested to hear if there are cases that devalue wouldn't cover (especially once we add BigInt support).

Rich-Harris avatar Aug 18 '22 21:08 Rich-Harris

That's only a solution if access to that server data is always through the matching SK front-end.

Maybe you have other apps or components that would like to fetch data in standard JSON format.

If you want to be able to return data in a different format than SvelteKit expects for consumption by other types of users, then that sounds like a different endpoint. The endpoints that SvelteKit uses to retrieve data from +page.server.js files aren't a public API anyway, and are subject to change at any time. There's no reason to be stingy about endpoints and only provide data in some other serialized format and then expect to be able to tell SvelteKit how to read that format.

Conduitry avatar Aug 18 '22 21:08 Conduitry

I take it page endpoints are considered "framework" and liable to change at any time / shouldn't be depended on as an external API (is it worth setting headers to prevent external requests so people don't accidentally do so?)

But what about when a page loads data from a server / standalone endpoint? I thought those were also direct (with no serialization) on the server?

That's where the custom reviver would be wanted, but not the non-standard JSON serialization that the page endpoints might use.

CaptainCodeman avatar Aug 18 '22 21:08 CaptainCodeman

If a page loads data from +server.js, it does so with fetch, in which case it would be quite mad for SvelteKit to intercede — we can't mess with await request.json() etc.

I thought those were also direct (with no serialization) on the server?

There's no intermediate HTTP request — our fetch wrapper creates a Request object from your fetch call and passes it back to the SvelteKit server to handle directly — but beyond that, it's a standard Request-Response process. We don't have any privileged access to the response body (which could be expressed as a string or a Uint8Array or a ReadableStream etc), and nor should we.

Rich-Harris avatar Aug 18 '22 22:08 Rich-Harris

Does #6008 address this? I'd be interested to hear if there are cases that devalue wouldn't cover (especially once we add BigInt support).

You will find users with special needs at all time. Someone might want to use a binary format to reduce bandwidth or some special coding to increase performance, maybe some freak wants zod to intercept and check all the values ... ;-) I still suggest a way to customize - with a well chosen global default...

Beiri22 avatar Aug 19 '22 21:08 Beiri22

our fetch wrapper creates a Request object from your fetch call and passes it back to the SvelteKit server to handle directly

That's where the server / client inconsistency may originate. Maybe it could be be JSON serialized so they are always the same?

CaptainCodeman avatar Aug 19 '22 22:08 CaptainCodeman

You will find users with special needs at all time. Someone might want to use a binary format to reduce bandwidth

Sounds like a great reason to use fetch with a +server.js file

That's where the server / client inconsistency may originate

What inconsistency?

Rich-Harris avatar Aug 23 '22 19:08 Rich-Harris

What inconsistency?

That you get JavaScript Date Objects during SSR but ISO 8601 format date strings after client navigation.

CaptainCodeman avatar Aug 23 '22 20:08 CaptainCodeman

But you don't, because we check that values are serializable and throw an error otherwise.

Since we now use devalue to serialize data (#6008) and since we're not going to interfere with people's Response objects, I don't think there's anything to do here - closing

Rich-Harris avatar Sep 03 '22 22:09 Rich-Harris