Front-end engineer with a passion for learning something new every day

Drupal 8 & 9 Theming: How to Render and Format JSON Data With PHP and Twig Using the JSON Field Module

There's a neat little Drupal module called JSON Field and recently, I had a chance to play around with it. Out of the box, JSON field is a just a plain field where JSON data can be input and output on a web page. On its own, the module does not do much beyond just printing the raw data formatted as JSON. However, I got to thinking it would be ideal to nicely format the data with HTML. In this article, I will show you how I accomplished this with both a preprocess function and some custom code in Twig.

Getting started

First, you'll want a Drupal 8 or 9 instance running. In the root of your project, run:

composer require drupal/json_field

Note, if you get an error, you may need to append a version number, for example:

composer require drupal/json_field:1.0-rc4

Next, enable the module and create a new field on an entity, for example on a page content type. When I created my field, I chose the option, JSON stored as raw JSON in database

Next, input some JSON data, for sample data, I like to use Mockaroo. (At a high level, I could envision using the Drupal Feeds module to import JSON data in bulk and mapping it to a JSON field but I have not tested this.)

An example of the Mockaroo interface showing mock data being generated
An example of the Mockaroo interface showing mock data being generated

Create a preprocess function

We are rendering this data in a node so I have a basic node preprocess function setup below with a sub-theme of Olivero called Oliver. Within this, we will leverage Xdebug to examine the data up close. We write this code in our theme's .theme file.

<?php

/**
 * @file
 * Functions to support theming in the Oliver theme.
 */

/**
 * Prepares variables for node templates.
 */
function oliver_preprocess_node(array &$vars) {
  // Custom code will go here.
}

Define and check for an instance of a node

The first thing we want to do is, since we are working within a node context, we will set some node definitions and check to ensure that we are on a node page.

At the top of our file, we will add

use Drupal\node\NodeInterface;

Then, within the function, we will define the node.

  // Define the node.
  $node = \Drupal::routeMatch()->getParameter('node');

Now we check for an instance of a node:

  // If instance of a node.
  if ($node instanceof NodeInterface) {

Field PHP magic

I named my JSON field field_json_raw and we first want to check to see if the field exists and that it is not empty. For this, I like to use a PHP magic method. A magic method is a short cut of sorts to dig into the field data. In Drupal terms, this looks like:

 if ($node->hasField('field_json_raw') &&
   !$node->get('field_json_raw')->isEmpty()) {
	// Custom code here...
  }

The magic methods above are hasField and get.

Start up Xdebug

Next up, we will use Xdebug to examine the data output to see how we might prepare variables for our Twig template. Within the node preprocess function, I set an Xdebug breakpoint and start listening. Once Xdebug is running we can evaluate the field expression using the magic method again but this time adding the value on. For example, $node->get('field_json_raw')->value. That ends up with the plain value of the field:

[
  {
    "id": 1,
    "country": "Cuba",
    "animal_name": "Cape Barren goose",
    "description": "Curabitur convallis.",
    "animal_scientific": "Cereopsis novaehollandiae"
  },
  {
    "id": 2,
    "country": "Vietnam",
    "animal_name": "Horned puffin",
    "description": "Pellentesque ultrices mattis odio.",
    "animal_scientific": "Fratercula corniculata"
  }
]
etc...
Xdebug showing the plain value of the JSON output
Xdebug showing the plain value of the JSON output

Convert the value into an array

What we need to do now is convert that to a usable PHP array. We use the json_decode function:

 // Set a variable for the plain field value.
 $json_raw = $node->get('field_json_raw')->value;
 // Convert the data into an array using json decode.
 $json_array = json_decode($json_raw);

That ends up looking like this:

Xdebug showing the converted JSON array
Xdebug showing the converted JSON array

Create a template variable

Now we have a nicely formatted array to loop through once inside a twig template. The final piece is to check for valid JSON and set the template variable. Note, JSON field already check for valid json but it's probably good practice to do this anyway.

 // Check for valid JSON.
 if ($json_array !== NULL) {
  // Create a variable for our template.
  $vars['json_data'] = $json_array;
 }

Finished preprocess function

Putting it all together, our entire preprocess function looks like this:

<?php

/**
 * @file
 * Functions to support theming in the Oliver theme.
 */

use Drupal\node\NodeInterface;

/**
 * Prepares variables for node templates.
 */
function oliver_preprocess_node(array &$vars) {
  // Define the node.
  $node = \Drupal::routeMatch()->getParameter('node');
  // If instance of a node.
  if ($node instanceof NodeInterface) {
    // Check for the field and that it is not empty.
    if ($node->hasField('field_json_raw') &&
      !$node->get('field_json_raw')->isEmpty()) {
      // Set a variable for the field value.
      $json_raw = $node->get('field_json_raw')->value;
      // Convert the data into an array.
      $json_array = json_decode($json_raw);
      // Check for valid JSON.
      if ($json_array !== NULL) {
        // Create a variable for our template.
        $vars['json_data'] = $json_array;
      }
    }
  }
}

Render the JSON variable in Twig

Now we'll go into our Twig template and render the variable with a loop. At its very most basic, it will look something like this:


    {% if content.field_json_raw | render %}
      {% for item in json_data %}
      {{ item.animal_name }}
      {{ item.animal_scientific }}
      {{ item.country }}
      {% endfor %}
    {% endif %}

Of course we want to add HTML to this to make it look nicely styled and here is where you can do most anything you want. I opted for a data table:


    {% if content.field_json_raw | render %}
      <h2>{{ 'Animals from around the world'|t }}</h2>
      <table>
        <thead>
        <tr>
          <th>{{ 'Animal Name'|t }}</th>
          <th>{{ 'Scientific Name'|t }}</th>
          <th>{{ 'Country'|t }}</th>
        </tr>
        </thead>
        {% for item in json_data %}
          <tbody>
          <tr>
            <td>{{ item.animal_name }}</td>
            <td>{{ item.animal_scientific }}</td>
            <td>{{ item.country }}</td>
          </tr>
          </tbody>
        {% endfor %}
      </table>
    {% endif %}

The code above ends up looking like this:

The finished styled output of the JSON data Twig loop
The finished styled output of the JSON data Twig loop

Summary

And there you have it, nicely styled JSON data rendered in a Twig template. Using the JSON Field module might not be a common everyday item but it definitely fulfills a specific use case as outlined here.

Resources

Tags

Read other blog posts