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

Deserializing relationships to an object #65

Closed
stefanvanherwijnen opened this issue Mar 29, 2019 · 13 comments
Closed

Deserializing relationships to an object #65

stefanvanherwijnen opened this issue Mar 29, 2019 · 13 comments

Comments

@stefanvanherwijnen
Copy link
Contributor

The default behaviour of deserialize() seems to be to return an array of relationships keys. If the top-level key "included" is set, it returns the relationships as an object with an 'id' key however.

For example:

{
	"data": {
		"id": 1,
		"attributes": {
			"name": "randomname"
		},
		"relationships": {
			"roles": {
				"data": {
					"type": "role",
					"id": 1
				}
			}
		}
	}
}

deserializes to { id: 1, name: 'randomname', roles: 1 }.

{
	"data": {
		"id": 1,
		"attributes": {
			"name": "randomname"
		},
		"relationships": {
			"roles": {
				"data": {
					"type": "role",
					"id": 1
				}
			}
		}
	},
	"included": [{
		"type": "role",
		"id": 1
	}]
}

deserializes to: { id: 1, name: 'randomname', roles: { id: 1 } }

Now, I would like to get the second result, but the JSON API spec doesn't seem to require the "included" key on updates: https://jsonapi.org/format/#crud-updating-resource-relationships

So I am wondering, is there a specific reason why the result of deserialization differs when the "included" key exists in the request?

@danivek
Copy link
Owner

danivek commented Apr 1, 2019

The behaviour of serialization is that if the relationship's attributes are populated (not just an id but an object), the data is supposed to be included.

To keep this behaviour on deserialize, if the included key is set, it considers that the relationship's data was populated, so it will set all the attributes in an object.

Maybe a test is missing to check if the included data only contains "resource identifier object" (only type and id attributes). In such case, the result will be the same as if the included data is not set on input data for the relationship.

Exemple:

{ id: 1, name: 'randomname', roles: 1 }

@stefanvanherwijnen
Copy link
Contributor Author

The reason why I am asking is because I would like to use it with ObjectionJS's graphInsert (https://vincit.github.io/objection.js/#graph-inserts). This requires the relation to be an array of resource identifier objects.

Would it make sense to add a check to see if the relationship is an array of "resource identifier objects" on deserialization? (i.e. if it contains type and id it should deserialize the relationship to an array of objects instead of an array of ID's, independent of the included property)

@danivek
Copy link
Owner

danivek commented Apr 1, 2019

Each ORM is different and has specifics needs.

What about having an option transform that takes a function to transform each data after deserialization ?

In such case every specifics needs should be code here...

@danivek
Copy link
Owner

danivek commented Apr 1, 2019

@mattiloh I think you have already worked with Objection.js. Any thougths ?

Thanks

@stefanvanherwijnen
Copy link
Contributor Author

stefanvanherwijnen commented Apr 1, 2019

I understand that the ORM implementation should not be the concern of this serializer. However, imo it does not make sense to deserialize a resource identifier object to a simple ID if it is not in included.

This change already solves the 'problem':
https://github.com/stefanvanherwijnen/json-api-serializer/commit/228f152d3b6a8e349c9cbea46f4e865cdb0d95b4

So the question is: what is the correct way to deserialize a relation? The serialize example in the README also shows this problem: the array of strings in photos and the array of objects in comments are both serialized to the same result (resource identifier objects). So I guess there should be an option to specify the deserialization such that one can get both results.

Edit:
Something like this: https://github.com/stefanvanherwijnen/json-api-serializer/commit/6e09d8ff64086685c759871e6f7d04856bf46122

@mattiloh
Copy link
Collaborator

mattiloh commented Apr 2, 2019

Hi @stefanvanherwijnen!
Yes, we use Objection.js with this deserializer, but I never used the graph-insert so far. We probably should give it a try in the future, because it seems to be a great feature.

Regarding your proposal: I can see that it could make sense for to-many relationships, because they are normally saved as dedicated rows in other tables (at least in relational databases). But belongs-to-one relationships are normally saved as single ids on the resource itself (e.g. a team relation of a user resource might be saved in the foreign key column team_id). Since this deserializer is resolving these values to ids only, it works great in such cases.

If we would just apply your proposed changes, the serialization of all belongs-to-one relationships would break and needed a refactor. A has-one relationship on the other hand could work fine with your proposed changes.

I don't have a definitive opinion on this yet (since I didn't have the time to dive deeper today), but after these first thoughts, I would be careful with hard-coding a behavior that is working fine with one specific ORM.

I think @danivek's idea of a transform option could be a good idea, since it allows to adjust the output to any ORM. Maybe it could also be a deserializer option of a relationship?

E.g.

Serializer.register("user", {
  ...
  relationships: {
    team: {
      type: 'teams',
      deserializer: data => data,
    }
  }
});

@stefanvanherwijnen
Copy link
Contributor Author

@mattiloh
You are right about the toMany relationship. I had not thought of it, but for a belongsTo relation, an array of keys is indeed the right way, in contrast to the toMany relation. So both options should be available in the end.
Did you have a look at:
https://github.com/stefanvanherwijnen/json-api-serializer/commit/6e09d8ff64086685c759871e6f7d04856bf46122
?
My first suggestion would indeed break the correct behaviour of deserializing to an array, but with this one can choose the deserialization behaviour.

Thanks for the feedback.

@mattiloh
Copy link
Collaborator

mattiloh commented Apr 3, 2019

Yes, I had a look at stefanvanherwijnen@6e09d8f.
But I think a generic deserialize option for relationships would be more flexible and provide a clean API.

Serializer.register("user", {
  relationships: {
    team: {
      type: 'teams',
      deserialize: data => ({ id: data.id })
    }
  }
});

It allows you to do what you need without being opinionated about the default output for objects. I think it's possible that somebody else in the future would like the deserialized object to look different from your proposal.

Do you think that would solve your problem?

At @danivek, what's your opinion on this? You imagined a transform option. Did you imagine it to be like the proposed deserialize?

@stefanvanherwijnen
Copy link
Contributor Author

Thanks, I think this would be a good solution. It should solve my problem and the added flexibility is indeed a better choice.
If @danivek agrees I can create a PR for your proposal if you don't have time yourself.

@danivek
Copy link
Owner

danivek commented Apr 3, 2019

I agree with @mattiloh for having a more flexible API.

I imagined a transform option that has the same goal as deserialize, except that to be even more generic, it will apply on the whole data and not only on each relationship's data.

Anyway, deserialize option on relationship is still an acceptable solution for me.

@stefanvanherwijnen
Copy link
Contributor Author

This seems to work:
https://github.com/stefanvanherwijnen/json-api-serializer/commit/f4e3e1b966159a7418e2acb9b5bcd7476fec28e0

With regards to a transform function: wouldn't that collide with the JSON API spec? I think this problem only exists for relationship data, so if one could transform other data as well it might not be compliant with the specification (except for e.g. case conversion, but that is already implemented).

@stefanvanherwijnen
Copy link
Contributor Author

@mattiloh @danivek
Any feedback on the deserialize functionality in my fork? Anything missing or should I make a PR?

@danivek
Copy link
Owner

danivek commented Apr 5, 2019

@stefanvanherwijnen It's ok for me.

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

3 participants