Writing a custom serializer for Plone REST API

The following code shows two custom FieldSerializer adapters which handle the values of Choice and MultiChoice (Collection) fields. For doing this, we register one adapter for the IChoice interface and one adapter for ICollection interface. Both also support vocabularies provided by collective.taxonomy.

By default the Plone REST API would give us just the plain tokens back, because that's what is actually stored when you use a Selection or Multiselection field. But on the client side we want the token and also the title, so that we can easily show it. Therefor we want something like this as the value for a Choice field.

Expected response

IChoice field

"solution_category": {
    "title": "A nice title",
    "token": "a-nice-title"
},

ICollection field

"solution_category": [
    {
        "title": "A nice title",
        "token": "a-nice-title"
    },
    {
        "title": "An awesome title",
        "token": "an-awesome-title"
    },
]

Implementation

restapi.py

from plone.dexterity.interfaces import IDexterityContent
from plone.restapi.interfaces import IFieldSerializer
from plone.restapi.serializer.converters import json_compatible
from zope.component import adapter
from zope.component import getUtility
from zope.interface import implementer
from zope.interface import Interface
from zope.schema.interfaces import IChoice
from zope.schema.interfaces import ICollection
from zope.schema.interfaces import IVocabularyFactory


def _get_vocab_term(context, field, value):
    """ Get vocab term dict
        returns: {'token': token, 'title': title}
    """
    vocab_term = {
        'token': None,
        'title': None,
    }
    vocab_term['token'] = value
    factory = getUtility(IVocabularyFactory, field)
    if not factory:
        return vocab_term

    # collective.taxonomy support:
    if hasattr(factory, 'translate'):
        vocab_term['title'] = _get_taxonomy_vocab_title(
            context,
            factory,
            value,
        )
    elif IVocabularyFactory.providedBy(factory):
        vocab_term['title'] = _get_vocab_title(
            context,
            factory,
            value,
        )
    return vocab_term


def _get_taxonomy_vocab_title(context, factory, value):
        vocab_title = factory.translate(
            value,
            context=context,
        )
        return vocab_title


def _get_vocab_title(context, factory, value):
    vocab = factory(context)
    vocab_title = vocab.getTermByToken(value).title
    return vocab_title


class BaseFieldSerializer(object):

    def __init__(self, field, context, request):
        self.context = context
        self.request = request
        self.field = field

    def __call__(self):
        return json_compatible(self.get_value())


@adapter(IChoice, IDexterityContent, Interface)
@implementer(IFieldSerializer)
class ChoiceFieldSerializer(BaseFieldSerializer):
    """
    """
    def get_value(self, default=None):
        value = getattr(
            self.field.interface(self.context),
            self.field.__name__,
            default,
        )
        if not value:
            return

        term = _get_vocab_term(
            self.context,
            self.field.vocabularyName,
            value,
        )
        return term


@adapter(ICollection, IDexterityContent, Interface)
@implementer(IFieldSerializer)
class CollectionFieldSerializer(BaseFieldSerializer):
    """
    """
    def get_value(self, default=None):
        terms = []
        values = getattr(
            self.field.interface(self.context),
            self.field.__name__,
            default,
        )
        if not values:
            return
        if not IChoice.providedBy(self.field.value_type):
            return

        for value in values:
            term = _get_vocab_term(
                self.context,
                self.field.value_type.vocabularyName,
                value,
            )
            terms.append(term)
        return terms

Registration

To activate this, we need to register the two adapters:

configure.zcml

<adapter factory=".restapi.ChoiceFieldSerializer" />
<adapter factory=".restapi.CollectionFieldSerializer" />
By @MrTango in