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

getServerSideProps cannot be serialized as JSON. Please only return JSON serializable data types #11993

Closed
chrisbrantley opened this issue Apr 18, 2020 · 32 comments

Comments

@chrisbrantley
Copy link

chrisbrantley commented Apr 18, 2020

Edit from @leerob: See updated answer: #11993 (comment)


Bug report

Describe the bug

I'm returning a row set from MySQL (using the mysql2 library) in my getServerSideProps. Getting the following error:

SerializableError: Error serializing .data[0]returned fromgetServerSidePropsin "/reports/[report]/[start]/[end]". Reason:object ("[object Object]") cannot be serialized as JSON. Please only return JSON serializable data types.

But calling JSON.stringify(data) works just fine.

If I do a JSON.parse(JSON.stringify(data)) it works just fine, but that seems very odd.

To Reproduce

  1. Execute a query using the mysql2 library
  2. Return the row set as a prop in getServerSideProps
  3. Get Error.

Expected behavior

I expect the data to be serialized without an error since JSON.stringify() works just fine.

Additional context

Appears others are having this problem: https://stackoverflow.com/questions/61188494/getserversideprops-and-mysql-rowdatapacket

@nicholaschiang
Copy link
Contributor

I'm having the same problem with a custom Timeslot object that's pretty straight forward:

interface TimeslotInterface {
  from: Date;
  to: Date;
}
class Timeslot implements TimeslotInterface {
  public constructor(public from: Date, public to: Date) {}
  public toString(): string {
    return this.from.toLocaleTimeString() + ' - ' + this.to.toLocaleTimeString();
  }
}

Same as above, JSON.stringify() works just fine, but Next.js seems to be throwing the same error:

Error serializing `.filters.availability[0]` returned from `getServerSideProps` in "/search". Reason: `object` ("[object Object]") cannot be serialized as JSON. Please only return JSON serializable data types.

Any suggestions?

@nicholaschiang
Copy link
Contributor

Update: while it makes sense that passing an Object (like a Timeslot) would cause errors (because method's can't be serialized), I've implemented toJSON() and fromJSON() methods that should make this work (but don't):

public toJSON(): TimeslotInterface {
  return { from: this.from, to: this.to };
}
public static fromJSON(timeslot: TimeslotInterface): Timeslot {
  return new Timeslot(new Date(timeslot.from), new Date(timeslot.to));
}

Still doesn't work.

@jeromemeichelbeck
Copy link

I have the exact same problem with mongoose. I thought it was because of the ObjectId format, but even if I "stringify" the _id property before it gets sent to getServerSideProps() I get the same error.

I've also had the idea to use JSON.parse(JSON.stringify(data)) and it works. But it's a silly solution if you ask me...

@heymartinadams
Copy link

heymartinadams commented Apr 22, 2020

Newbie here to NextJS. It seems strange, especially since querying directly from a PostgreSQL db (via Prisma) usually brings up createdAt and updatedAt and other date fields.

For now, doing what @jeromemeichelbeck recommends by parsing results through a converter:

const dateStripped = obj => {
	let newObj = {}
	Object.keys(obj).forEach(key => {
		let value = obj[key]
		if (value !== null) {
			// If array, loop...
			if (Array.isArray(value)) {
				value = value.map(item => dateStripped(item))
			}
			// ...if property is date/time, stringify/parse...
			else if (typeof value === 'object' && typeof value.getMonth === 'function') {
				value = JSON.parse(JSON.stringify(value))
			}
			// ...and if a deep object, loop.
			else if (typeof value === 'object') {
				value = dateStripped(value)
			}
		}
		newObj[key] = value
	})
	return newObj
}

@chrisbrantley
Copy link
Author

Hi Folks. I think this was a red herring. I'm using the mysql2 library and I did not realize that it is doing some magic conversions behind the scenes to convert dates and some other mysql types to native javascript objects that cannot be serialized without conversion. JSON.stingify() attempts to convert these automatically but it appears Next.JS is using a different serialization method that does not do this conversion.

Now that I know more about what's going on I think the approach the Next.JS team is taking is the correct one. It's better to be explicit and give the developer full control than to do magical conversions that may or may not be surprising. Maybe the Next.JS folks could add a "strict" configuration that allows people to opt in to the JSON.stringify() behavior?

For those of you still dealing with this you can continue with the JSON.parse(JSON.stringify) hack or you can make sure your data consists of easily-serializable primitives.

I'm closing this issue now as it appears to be working as designed and is not a bug. If someone wants to open a feature request go for it.

@timneutkens
Copy link
Member

The main reasoning behind this limitation is that with getInitialProps we used to allow anything that can be stringified and that resulted in some really hard to track down UX bugs when hydrating client-side. Eg the hydration would fail because suddenly the Date object you had server-side is a string client-side.

We need to serialize the result of getStaticProps/getServerSideProps because it has to be sent to the browser to do hydration.

So we basically went with a strict checking mechanism (only runs in development) in order to prevent this from happening. We can potentially add an option to bypass in the future if there's enough need for it 👍

@jeromemeichelbeck
Copy link

Thank you for the answer.

@ericclemmons
Copy link

@timneutkens @chrisbrantley I came across this recently while working on Next.js support for Amplify JS.

This throws an error (relevant code):

// pages/index.tsx
export async function getServerSideProps(context) {
	const posts = await DataStore.query(Post);
   // [Post {...}, Post {...}, ...]

	return {
		props: {
			posts
		},
	};
}

Whereas our /api/posts route works (due to res.json):

// pages/api/posts.tsx
export default async (req: NextApiRequest, res: NextApiResponse) => {
  try {
    const posts = await DataStore.query(Post);

    return res.status(200).json(posts)
  } catch (error) {
    console.error(error)
    return res.status(500).json({ error })
  }
}

I understand the undesirable impact of using JSON.stringify, but would you be open to a PR that checked for .toJSON as a final check/casting before throwing?

https://github.com/zeit/next.js/blob/a4ab0887465d43ad3081e142924145a495b5727f/packages/next/lib/is-serializable-props.ts#L118-L132

Example: https://codesandbox.io/s/httpsgithubcomzeitnextjsissues11993-sumki?file=/pages/index.js:405-681

@chrisbrantley
Copy link
Author

chrisbrantley commented Apr 23, 2020

+1 for an option to bypass.

I've disabled all type casting in mysql2 but the objects returned are still of type TextRow even though they are just plain javascript objects consisting of nothing but primitives. I have to do rows.map(row => ({...row})) to keep from triggering that error.

Given that the primary use case of this feature is to fetch data from some data store and shove it into props, I think this will become quite a pain.

Even if it was just a warning and not a hard error it could still be helpful in development.

@timneutkens
Copy link
Member

@timneutkens @chrisbrantley I came across this recently while working on Next.js support for Amplify JS.

This throws an error (relevant code):

// pages/index.tsx
export async function getServerSideProps(context) {
	const posts = await DataStore.query(Post);
   // [Post {...}, Post {...}, ...]

	return {
		props: {
			posts
		},
	};
}

Whereas our /api/posts route works (due to res.json):

// pages/api/posts.tsx
export default async (req: NextApiRequest, res: NextApiResponse) => {
  try {
    const posts = await DataStore.query(Post);

    return res.status(200).json(posts)
  } catch (error) {
    console.error(error)
    return res.status(500).json({ error })
  }
}

I understand the undesirable impact of using JSON.stringify, but would you be open to a PR that checked for .toJSON as a final check/casting before throwing?

https://github.com/zeit/next.js/blob/a4ab0887465d43ad3081e142924145a495b5727f/packages/next/lib/is-serializable-props.ts#L118-L132

Example: codesandbox.io/s/httpsgithubcomzeitnextjsissues11993-sumki?file=/pages/index.js:405-681

In API routes it's expected that you're casting the values to JSON and they're JSON client-side. With getStaticProps/getServerSideProps that is not the case and caused really tricky to debug hydration issues (we've seen these in production Next.js applications). As said I'm not opposed to adding an explicit option to bypass but the default behavior prevents production issues down the line.

@ericclemmons
Copy link

@timneutkens We're on the same page.

My proposal is to automatically handle serialization via .toJSON.

I'll open a PR for it, since code > words.

@ericclemmons
Copy link

Alright, check out #12156 👀

@IRediTOTO
Copy link

:| After hours to think this ~ I fixed this :) Some data in query return is object like mongoID and it make JSON.parse not work. Yah I know I so nood.
image

@ericclemmons
Copy link

@warcraft14115 I have a PR to fix it, but there were some rogue file-size tests that got it out of date.

I'll have to get #12156 passing again and approved (I don't think anyone has looked at it yet).

@fimbault
Copy link

Any news on the PR?

@StanRodriguez
Copy link

For people using mongoose, I solved it using the .lean() method to convert in a plain javascript object and removing the object Id from the query as it follows:
image

Credit to this post in StackOverflow

@ItayTur
Copy link

ItayTur commented Nov 14, 2020

if anyone works with axios, the object returned from the api call is in data field, so if all the response of the axios is passed, next will alert that it's not serializable and function isn't allowed. my solution:

export async function getServerSideProps() {
    const { data: products } = await axios.get(
        `${SERVER_URL}\\products`);
    return { props: { products } };
}

const productsPage = ({ products }) => {
 \\do something with products
}

@lfender6445
Copy link

if anyone here is getting this warning with mongo, you need to supress the _id field bc json does not support hex https://stackoverflow.com/a/52250461/1426788

@Paratron
Copy link

I had the same issue today with objects returned from a mysql2 query.

This can be resolved without converting the result to json and back again by either using object assign or the spread operator.

So instead of return myRetrievedObject;, you can do return Object.assign({}, myRetrievedObject); or return {...myRetrievedObject};.

Hope this helps!

@Azayzel
Copy link

Azayzel commented Feb 13, 2021

Mongo + Next:

_id property name and other non-string property values throw this error

DB method

export function getFeed(db, urlSlug) {
  return db
    .collection('feeds')
    .findOne({
      slug: urlSlug,
    })
    .then((feed) => serialize(feed))
}

Helper func to serialize data once it's returned from the db method

export async function serialize({ _id, image, title, slug, details, subtitle, author, avatar, article, createdAt }) {
  return {
    id: _id.toString(), // for some reason next doesn't like the `_id` property + non-string
    image: image,
    title: title,
    slug: slug,
    details: details,
    subtitle: subtitle,
    author: author,
    avatar: avatar,
    article: article,
    createdAt: createdAt.toString(), // cast as string
  }
}

@Hijazi313
Copy link

i am getting data from firestore. faced the same issue because of timestamp in my response. i converted the result from firestore into a string with JSON.stringify() and then again from my props parsed it. working fine

@icflorescu
Copy link

I stumbled across a similar problem and got it working with by using the superjson-next plugin.
See more here: https://github.com/blitz-js/superjson#using-with-nextjs

@ondrej-langr
Copy link

i am getting data from firestore. faced the same issue because of timestamp in my response. i converted the result from firestore into a string with JSON.stringify() and then again from my props parsed it. working fine

Same here!

const res = JSON.parse(
    JSON.stringify(
      someObject,
     //I had a problem with a bigint value - and heres the solution for it too
      (key, value) => (typeof value === "bigint" 
          ? value.toString() 
          : value) 
    )
  );

@mayrsascha
Copy link

@timneutkens I don't get why this issue is closed. It seems quite a lot of people are still stumbling upon this and there is demand for a better solution. A warning in development mode seems to be sufficient to help users track down hydration errors and it wouldn't require everyone to use hacks to serialize dates and such.

@jackzbazuka
Copy link

jackzbazuka commented Jun 22, 2021

the _id and _v0 are object types. So according to MongoDB docs, we need to call a toString() method on these properties to get string values

@deniivanov
Copy link

deniivanov commented Jul 14, 2021

Hopefully this helps someone struggling like me with the issue.

const posts = await model.find().lean();
	return {
		props: {
			posts: JSON.parse(JSON.stringify(posts)),
		},
	};
}

@dhatGuy
Copy link

dhatGuy commented Jul 22, 2021

Superjson worked for me

@mmrahmad
Copy link

I have the exact same problem with mongoose. I thought it was because of the ObjectId format, but even if I "stringify" the _id property before it gets sent to getServerSideProps() I get the same error.

I've also had the idea to use JSON.parse(JSON.stringify(data)) and it works. But it's a silly solution if you ask me...

Yes I also use JSON.parse(JSON.stringify(result) to solve this problem

@kallydev
Copy link

This is my solution for Prisma. Maybe this is a better way to show what the problem is.

export const getServerSideProps: GetServerSideProps<PostProps> = async () => {
    const posts = await prisma.post.findMany();
    return {
        props: {
            posts: posts.map((post: post) => ({
                ...post,
                created_at: post.updated_at.toISOString(),
                updated_at: post.updated_at.toISOString(),
                deleted_at: post.deleted_at === null ? post.deleted_at : post.deleted_at.toISOString(),
            } as unknown as post)),
        }
    };
};

@IRediTOTO
Copy link

This is my solution for Prisma. Maybe this is a better way to show what the problem is.

export const getServerSideProps: GetServerSideProps<PostProps> = async () => {

    const posts = await prisma.post.findMany();

    return {

        props: {

            posts: posts.map((post: post) => ({

                ...post,

                created_at: post.updated_at.toISOString(),

                updated_at: post.updated_at.toISOString(),

                deleted_at: post.deleted_at === null ? post.deleted_at : post.deleted_at.toISOString(),

            } as unknown as post)),

        }

    };

};

This is solution https://github.com/blitz-js/superjson#using-with-nextjs

zoul added a commit to cesko-digital/app that referenced this issue Jan 14, 2022
Next.js insists on not having explicit undefineds in page data:

vercel/next.js#11993

So we filter them out (just leaving the undefined props out). This
change moves the filtering trick to a single one, less visible place.
zoul added a commit to cesko-digital/app that referenced this issue Jan 14, 2022
Next.js insists on not having explicit undefineds in page data:

vercel/next.js#11993

So we filter them out (just leaving the undefined props out). This
change moves the filtering trick to a single one, less visible place.
zoul added a commit to cesko-digital/app that referenced this issue Jan 14, 2022
Next.js insists on not having explicit undefineds in page data:

vercel/next.js#11993

So we filter them out (just leaving the undefined props out). This
change moves the filtering trick to a single one, less visible place.
KiwiKid added a commit to KiwiKid/LocationOfInterestExplorer that referenced this issue Jan 22, 2022
@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 27, 2022
@leerob
Copy link
Member

leerob commented Apr 12, 2023

For those landing here from Google, good news – this is no longer an issue with the Next.js App Router + React Server Components. From the docs:

No serialization of data between fetching function and page: You can fetch directly in components, layouts, and pages on the server. This data does not need to be serialized (converted to a string) to be passed to the client side for consumption in React. Instead, since app uses Server Components by default, we can use values like Date, Map, Set, and more without any extra steps. Previously, you needed to manually type the boundary between server and client with Next.js-specific types.

async function getData() {
  const res = await fetch('https://api.example.com/...');
  // The return value is *not* serialized
  // You can return Date, Map, Set, etc.
  return res.json();
}

export default async function Page() {
  const name = await getData();

  return '...';
}

Source: https://beta.nextjs.org/docs/configuring/typescript#end-to-end-type-safety

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

Successfully merging a pull request may close this issue.