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

HAL serializer doesn't respect skip_null_values=false for relations #4372

Open
carlobeltrame opened this issue Aug 2, 2021 · 3 comments
Open
Labels

Comments

@carlobeltrame
Copy link
Contributor

carlobeltrame commented Aug 2, 2021

API Platform version(s) affected: 2.6.5, and any other API platform version using Symfony 4.2 (since the introduction of the skip_null_values flag)

Description
The HAL format normalizer does things a little differently from the rest of the normalizers, especially when it comes to normalizing relations. In ItemNormalizer#populateRelation, an empty() check determines whether to leave out a relation. This also affects optional ToOne relations which can have a null value. In the rest of the framework, the skip_null_values flag in the context determines what to do with such null relations. In HAL, this flag is not respected.

I'd be glad to provide a PR to fix this if requested.

How to reproduce
Create two entities like the following:

/**
 * @ORM\Entity
 */
#[ApiResource(normalizationContext: ['skip_null_values' => false])]
class Dummy {
  public ?RelatedDummy $relatedDummy = null;
}

/**
 * @ORM\Entity
 */
#[ApiResource]
class RelatedDummy {
}

Activate support for HAL in the config:

api_platform:
    formats:
        jsonhal: [ 'application/hal+json' ]
        jsonld: [ 'application/ld+json' ]

Create a Dummy instance in the database without any RelatedDummy, and fetch it using the format Accept: application/hal+json.
Expected result:

{
  "_links": {
    "self": { "href": "/some/iri" },
    "relatedDummy": null
  }
}

Actual result:

{
  "_links": {
    "self": { "href": "/some/iri" }
  }
}

Possible Solution
Use a check like the following:

if (empty($attributeValue)) {
    $skipNullValues = $context[self::SKIP_NULL_VALUES] ?? $this->defaultContext[self::SKIP_NULL_VALUES] ?? false;
    if (null !== $attributeValue || $skipNullValues) {
        continue;
    }
}
@dunglas
Copy link
Member

dunglas commented Aug 2, 2021

The fix you propose looks good to me. Would you like to work on a patch?

@carlobeltrame
Copy link
Contributor Author

I have created a PR #4373 for demonstration, but I have run into problems. The hyperschema does not allow null entries in _links and _embedded. I have seen many places on the internet where people have been confused by what is and isn't allowed in HAL concerning null values:

  • An unresolved conversation from 2011 on hal-discuss where the HAL spec author didn't reply anymore, but it kind of sounds like it should be allowed (?)
  • Zend Expressive / Mezzio had an issue on this topic, and they serialize the null value outside of the _links and _embedded, next to the primitive properties
  • Spring Data Rest circumvents the problem by specifying any relations as subresource links, which are always rendered but may return a 404
  • Nevertheless, the SDR maintainer Oliver Drotbohm makes an important argument from a HATEOAS perspective: If the null links are simply omitted, how would a client know of the possibility to assign something to that relation? In the case of API platform, we have the OpenAPI spec, but in a broader sense, the HAL specification kind of needs to allow null links for that reason, doesn't it?
  • Some other unknown API implementations also use null in links and embedded, but are "aware" of the "inconsistencies"
  • The HAL spec itself doesn't mention null a single time, and mentions that entries in _links should be "either a Link Object or an array of Link Objects", and entries in _embedded should be "either a Resource Object or an array of Resource Objects".

So, to proceed, I see multiple options.

  1. We could stay with the old implementation and just ignore skip_null_values: false, effectively pushing the null checking responsibility to any API clients:
{
  "_links": {
    "self": { "href": "/some/iri" }
  }
}
  1. We could deviate from the upstream hyperschema and use the format I implemented in fix: Respect skip_null_values in the HAL normalizer #4373, which would follow the rest of API platform's supported formats the closest:
{
  "_links": {
    "self": { "href": "/some/iri" },
    "relatedDummy": null
  },
  "_embedded": {
    "relatedDummy": null
  }
}
  1. We could choose to only null the href of the null links, which is technically also incorrect but may be a little less problematic for clients:
{
  "_links": {
    "self": { "href": "/some/iri" },
    "relatedDummy": { "href": null }
  },
  "_embedded": {
    "relatedDummy": null
  }
}
  1. We could render the null value outside of _links and _embedded, like Mezzio does, assuming that clients will merge the three categories together anyways:
{
  "_links": {
    "self": { "href": "/some/iri" }
  },
  "relatedDummy": null
}

To summarize:

Option 1 2 3 4
Compatible with spec ✔️ ✔️
Compatible with hyperschema ✔️ ✔️
Respects skip_null_values ✔️ ✔️ ✔️
Effort in API client to consume; amount of out-of-band information needed high low low medium

What do you think? Which way would you choose?

@stale
Copy link

stale bot commented Nov 5, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Nov 5, 2022
@stale stale bot removed the wontfix label Nov 5, 2022
@soyuka soyuka added bug and removed enhancement labels Oct 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants