Parsing ref object mutates it somehow. "Ref expected, Object provided."

I’m using a serverless function, called with axios, on netlify to run a login function that returns a userRef.

When passing the userRef to the client with JSON.Stringify the userRef is mutated somehow.

When using the parsed userRef from the client I get the error “Ref expected, Object provided.”

Using a ref obtained on the client side with CurrentIdentity() works fine.
That is the workaround that I am using right now.

Ok, digging a little more, is this to mean that Refs are non-serializable?
If so storing refs in a redux store causes a problem.

Not exactly. More like, finally shows you the underlying wire protocol. The driver parses the JSON before handing off the result. If you are going to stringify it again to send from a server to the client then you will have to deal with that somehow yourself. The fauna clients I think were meant to be used directly to such an extent that using a server as a proxy is definitely a pain.

Does this post help at all?

Techniques that I’ve seen fall into a few basic categories

  1. Work with “raw” json in the client.
  2. manipulate the data on the server before sending to the client
  3. re-parse the response on the client by copying unexported parseJSON function from the driver
  4. refactor to perform authentication on the server but perform all data queries from the client.

1 - work with raw data

The client can have some helper functions to get the ‘data’ and pull out the ‘id’ of refs, like

const getFaunID = doc => doc.ref['@ref'].id

This probably works well enough if your data is simple and there are no embedded refs in the data. Or I suppose you just have to use the helper functions a lot.

2 - manipulate on the server

You can feed cleaned up objects to the client. But then you have to re-fauna-ify them for writing back to the server.

If you are good with UDFs it can be easier to handle the “serialization” and “deserialization” within the FQL. I.e. create UDFs for each CRUD function/ api point that handles works with the JSON data you client wants.

Whether in FQL or Node on the server this often takes the form of stripping the id values from the raw fauna results and adding them to the result data. Similar to how the GraphQL api works.

3 - re-parse the response

The parseJSON function is not exported from fauna. But it’s not big and you can copy it out of the driver into your client app. And it works quite nicely!

I don’t know what the developers would say about this. Fauna team members have mentioned in various ways that the wire protocol is an unpublished implementation detail that is subject to change. So stay up to date with driver changes.

// parseJSON.js
const { values } = require('faunadb')

function parseJSON(json) {
  return JSON.parse(json, json_parse)
}

function json_parse(_, val) {
  if (typeof val !== 'object' || val === null) {
    return val
  } else if ('@ref' in val) {
    var ref = val['@ref']

    if (!('collection' in ref) && !('database' in ref)) {
      return values.Native.fromName(ref['id'])
    }

    var col = json_parse('collection', ref['collection'])
    var db = json_parse('database', ref['database'])

    return new values.Ref(ref['id'], col, db)
  } else if ('@obj' in val) {
    return val['@obj']
  } else if ('@set' in val) {
    return new values.SetRef(val['@set'])
  } else if ('@ts' in val) {
    return new values.FaunaTime(val['@ts'])
  } else if ('@date' in val) {
    return new values.FaunaDate(val['@date'])
  } else if ('@bytes' in val) {
    return new values.Bytes(val['@bytes'])
  } else if ('@query' in val) {
    return new values.Query(val['@query'])
  } else {
    return val
  }
}

module.exports = parseJSON

4 - query data from the client

There are a few ways to do this. Brecht is working on his skeleton-auth example that would demonstrate how to do this with refresh cookies, but he’s not done yet.

Thanks for the comprehensive answer.

I think the third option works best for me, as I would also want to use JSON.stringify and JSON.parse for localstorage, and a redux store.

I would also like to know what the devs have to say on this matter. Will there be an officially exported function like this? Or is there a better way to handle refs so it’s not required?

@roopsBee and others, FYI the parseJSON method is now exported from the js driver :raised_hands: