I installed the latest dev release of the Metatag module and it caused errors on the jsonapi pages due to how metatags are normalized.

I created a patch for the metatag module, but it was proposed that the patch should live in this module.
- https://www.drupal.org/node/2636852#comment-12215100

So I think this should really be added to the jsonapi module, which should take care of normalizing its own known entities.
- https://www.drupal.org/node/2636852#comment-12215416

After exploring the \Normalizer\ classes within the jsonapi module, it seems that you are not supporting normalization for other contrib modules. So that led me to submitting my patch to the metatag module first.

Any guidance on how we should approach this would be great. I'm happy to refactor my patch so it applies to jsonapi if that is deemed the best approach.

Comments

adamzimmermann created an issue. See original summary.

wim leers’s picture

Status: Active » Closed (duplicate)
Related issues: +#2901253: Add normalization support for metatag module

Thanks for reporting this, this confirms #2901253: Add normalization support for metatag module.

adamzimmermann’s picture

@Wim Leers ah, yes this is definitely related to that. There are subtle differences though that I want to make sure we address in some ticket. I don't care which one we lump it with.

A quick summary of the issue is as follows:

  • The metatag module has a normalization base class that returns an array.
  • This works fine for the HAL format that doesn't require the data to be wrapped in a FieldNormalizerValue object.
  • The jsonapi module expects data to be wrapped in a FieldNormalizerValue object.
  • The jsonapi module is looking to remove its normalization overrides and instead lean on the functionality core offers as a part of #2860350: Document why JSON API only supports @DataType-level normalizers.
  • Once that is complete the metatag module will need to determine how its format agnostic normalizer Drupal\metatag\Normalizer\MetatagNormalizer will work with jsonapi.

Depending on how the jsonapi refactor goes, it may handle normalization for the metatag field, but I doubt one of the core normalizer classes will work with the metatag data. If that is the case, the array that MetatagNormalizer returns will still cause an error. Perhaps the answer is that this is still something the jsonapi module needs to address by adding a normalizer for metatag fields, i.e. a class that declares the following:

protected $supportedInterfaceOrClass = 'Drupal\metatag\Plugin\Field\MetatagEntityFieldItemList';

If that is the case, would that effort be a part of #2860350: Document why JSON API only supports @DataType-level normalizers? It seems that issue or more about removing the jsonapi custom overrides and using core.

This problem will only grow bigger over time: as core adds more sensible normalizers (see "serialization gap" issues listed in #2852860: REST: top priorities for Drupal 8.4.x: REST: top priorities for Drupal 8.4.x), and as contrib modules add normalizers (for example:

In the metatag contrib case, the contrib module doesn't provide the needed mechanisms to normalize its data, at least without throwing errors. Should adding that normalizer be the responsibility of jsonapi or metatag? If it is the responsiblity of jsonapi, perhaps we do that in this ticket?

I would love to come to a consensus for how contrib module data normalization is handled, as I'm hoping to solidify a metadata API "contract" between Drupal and a headless front-end for my current project and others who will invariable need to do this also. My "hack/patch" in #2636852: Make Metatag fields available as JSON is working for now, but isn't ideal moving forward.

Hope this helps!

damienmckenna’s picture

As the lead maintainer of Metatag, I am interested in helping to resolve the problem but have no understanding of the best way to help fix it; if it'll take changes to the Metatag module that's a-ok with me :)

e0ipso’s picture

Once that is complete the metatag module will need to determine how its format agnostic normalizer Drupal\metatag\Normalizer\MetatagNormalizer will work with jsonapi.

I'm not sure I understand how a normalizer can be format agnostic. A normalizer is inseparable of a format. You normalize to a format.

I'm pretty sure that if a normalizer decides to take precedence and respond to the reserved api_json format, then it should play by the rules in the JSON API module. This is because it will need to be put in a JSON API document output, and it needs to be valid JSON API format and follow the module expectations.

e0ipso’s picture

Thanks for reporting this, this confirms […]

Can you elaborate on that? From my point of view these two issues are not related. This one is caused by a normalizer from a separate contrib with high precedence acting on the api_json format. The other one is JSON API not picking up automatically the new core's normalizer.

adamzimmermann’s picture

I'm not sure I understand how a normalizer can be format agnostic. A normalizer is inseparable of a format. You normalize to a format.

The Drupal\metatag\Normalizer\MetatagNormalizer class doesn't declare protected $format = ['the_target_format']; like other classes do, such as Drupal\metatag\Normalizer\MetatagHalNormalizer. However, both are registered as services with the normalizer tag in their definition.

So perhaps "format agnostic" is the wrong way to describe, but that is how it appears to someone new to D8 normalization.

e0ipso’s picture

The Drupal\metatag\Normalizer\MetatagNormalizer class doesn't declare protected $format = ['the_target_format']; like other classes do, such as Drupal\metatag\Normalizer\MetatagHalNormalizer.

That is the core of the problem. A normalizer changes the shape of data into a given format. Therefore Drupal\metatag\Normalizer\MetatagHalNormalizer should not be greedy and should not respond TRUE in supportsNormalization when the $format provided in that call is set to 'api_json'.

hanoii’s picture

The Drupal\metatag\Normalizer\MetatagNormalizer class doesn't declare protected $format = ['the_target_format']; like other classes do, such as Drupal\metatag\Normalizer\MetatagHalNormalizer.

That's really how core is setup, look at any normalizer in the core's serialization module. It's as if `json` is the default. It's how it is and it's not bad per se.. so there's really not an agnostic normalizer but rather a default, and that's always 'json'.

Therefore Drupal\metatag\Normalizer\MetatagHalNormalizer should not be greedy and should not respond TRUE in supportsNormalization when the $format provided in that call is set to 'api_json'.

It's not MetatagHalNormalizer that's being greedy. What's happening is that there's also the regular Drupal\metatag\Normalizer\MetatagNormalizer which defaults to TRUE if no format is set to the normalizer, but that's only because it's based on Drupal\serialization\Normalizer (core). Which is the case in most core's normalizers.

See:

  /**
   * Checks if the provided format is supported by this normalizer.
   *
   * @param string $format
   *   The format to check.
   *
   * @return bool
   *   TRUE if the format is supported, FALSE otherwise. If no format is
   *   specified this will return TRUE.
   */
  protected function checkFormat($format = NULL) {
    if (!isset($format) || !isset($this->format)) {
      return TRUE;
    }

    return in_array($format, (array) $this->format);
  }

i.e. $this->format is not set.

I am not sure again what's the proper fix here. Also I haven't reviewed the fixes and comments on the mentioned core issue to assess if that will really fix the problem.

wim leers’s picture

#6 + #8 + #9 + #10 are duplicating the discussion at #2860350-19: Document why JSON API only supports @DataType-level normalizers through #2860350-23: Document why JSON API only supports @DataType-level normalizers. It's really the same problem. Let's all discuss it there, so we don't have a fragmented discussion.