36

Is is possible to decode JSON in twig? Googling doesn't seem to yield anything about this. Does decoding JSON in Twig not make sense?


I'm trying to access 2 entity properties on an Symfony2's entity field type (Entity Field Type).

After coming across 2 previous SO questions ( Symfony2 entity field type alternatives to "property" or "__toString()"? and Symfony 2 Create a entity form field with 2 properties ) which suggested adding an extra method to an entity to retrieve a customized string rather than an entity attribute, I thought of (and did) returning a JSON string representing an object instance.

Somewhere in the entity class:

/**
 * Return a JSON string representing this class.
 */
public function getJson()
{
   return json_encode(get_object_vars($this));
}

And in the form (something like):

$builder->add('categories', 'entity', array (
...
'property' => 'json',
...
));

Afterwards, I was hoping to json_decode it in Twig...

{% for category in form.categories %}
    {# json_decode() part is imaginary #}
    {% set obj = category.vars.label|json_decode() %}
{% endfor %}
10
  • Why not json_encode() it in PHP? Commented Jan 24, 2013 at 11:54
  • 1
    Yes, I do json_encode(get_object_vars($this)). The problem is decoding since it has to be in Twig and not PHP. Commented Jan 24, 2013 at 11:57
  • 1
    I'm not familiar with Twig/Symfony2, but could you decode it in your action and pass the results of that to your Twig template? Commented Jan 24, 2013 at 12:07
  • Hi @halfer, you can't access the entity (a model object in Sf1) in the Controller. The form (built with $builder) queries for categories by itself and all I can do is configure which property will be used to label it in the actual form to be rendered. Commented Jan 24, 2013 at 12:16
  • 3
    do you know that you can extend twig and write custom filters? twig.sensiolabs.org/doc/advanced.html Commented Jan 24, 2013 at 12:21

9 Answers 9

45

That's easy if you extend twig.

First, create a class that will contain the extension:

<?php
 
namespace Acme\DemoBundle\Twig\Extension;

use Symfony\Component\DependencyInjection\ContainerInterface;  
use \Twig_Extension;

class VarsExtension extends Twig_Extension
{
    protected $container;
 
    public function __construct(ContainerInterface $container) 
    {
        $this->container = $container;
    }
      
    public function getName() 
    {
        return 'some.extension';
    }
    
    public function getFilters() {
        return array(
            'json_decode'   => new \Twig_Filter_Method($this, 'jsonDecode'),
        );
    }

    public function jsonDecode($str) {
        return json_decode($str);
    }
}

Then, register that class in your Services.xml file:

<service id="some_id" class="Acme\DemoBundle\Twig\Extension\VarsExtension">
        <tag name="twig.extension" />
        <argument type="service" id="service_container" />
</service>

Then, use it on your twig templates:

{% set obj = form_label(category) | json_decode %}
Sign up to request clarification or add additional context in comments.

4 Comments

Just in case anyone looking for Services.yml setup: acme_demo.twig.extension.vars_extension: class:Acme\DemoBundle\Twig\Extension\VarsExtension arguments: [@service_container] tags: - { name: 'twig.extension' }
What happens if you are just using Twig with out a framework :(
@Xocoatzin, I have a look at your answer which is relevant for what I am looking for. But I also have a question. I did a TWIG extension class in the past and used in the constructor: ` public function __construct(\Twig_Environment $env) { $this->environment = $env; }` , and I do use $this->environment later on using the function loadTemplate('...') on it. In your solution I am a bit confuse by the use ContainerInterface, I don't get where the argument [@service_container] is needed?
@nyluje If you don't need the service_container (if you don't know whether you need it or not, then you don't) you can safely remove it. Delete the line <argument... service_container..> from Services.xml, the argument and body of __construct, the line protected $container; and use ...containerInterface
7

An alternative to all above.
And I don't know whether this is the optimal solution, but it works.

1) Create a helper function and register that function it.

<?php
function twig_json_decode($json)
{
    return json_decode($json, true);
}


2) Use this function in your twig file.

{% set res = twig_json_decode(json) %}
# above will return an array which can be iterated

{% for r is res %}
    {{ r }}
{% endfor %}

Comments

6

I came up with a way of getting to my JSON and I thought I'd share it here in case its usefult to someone else.

so in my case I have maybe 10 records (layouts) returned from a mysql db and each row has a field called properties which is a json string. So, I can easily pull out the records and send them to the template like so:

echo $twig->render('template.html.twig', array(
      "layouts" => $layouts,
));

So far so good, However when I do my {% for layout in layouts %} in twig there is no way to get to the properties field items as they are still a json string.

So just before I passed $layouts to the twig template I did the following:

foreach($layouts as $i => $v)
{
      $layouts[$i]->decoded = json_decode($v->getProperties());
}

by doing this Ive created a variable on the fly within my object called 'decoded' which contains the json decoded object.

So now in my template I can access my json items by {{ layout.decoded.whatever }}

This might be a bit hacky and not to everyones idea of a good solution. I works well for me, very little overhead and means I dont have to mess about with extending twig as Im doing the work before it gets to the template.

1 Comment

A better solution following a similar approach would be to create a View model / DTO which receives the decoded JSON
4

Updated code for Symfony2.8 or Symfony3:

<?php

namespace Acme\DemoBundle\Twig\Extension;

use Symfony\Component\DependencyInjection\ContainerInterface;  
use \Twig_Extension;

class VarsExtension extends Twig_Extension
{
    protected $container;

    public function __construct(ContainerInterface $container) 
    {
        $this->container = $container;
    }

    public function getName() 
    {
        return 'some.extension';
    }

    // Note: If you want to use it as {{ json_decode(var) }} instead of 
    // {{ var|json_decode }} please use getFunctions() and 
    // new \Twig_SimpleFunction('json_decode', 'json_decode') 
    public function getFilters() {
        return [
            // Note that we map php json_decode function to 
            // extension filter of the same name
            new \Twig_SimpleFilter('json_decode', 'json_decode'),
        ];
    }
}

Comments

2

This is an old question but I'm adding my solution for the record... Just extend Twig with a SimpleFunction:

// Return a string of separated values from a JSON string
// Can optionally specify a separator.  If none provided, ", " is used.
$function = new Twig_SimpleFunction('json_to_list', function($json, $separator = ", ")
{
    $result = "";
    $array = json_decode($json, true);
    foreach ($array as $item)
    {
        if ($result != "") { $result .= $separator; }           
        $result .= $item;
    }
    return $result;
});
$twig->addFunction($function);

Usage:

set a_json_variable to the string '["1","2","3","4","5"]' before calling the Twig render.

Twig template:

The values are: {{ json_to_list(a_json_variable) }}

Will produce

The values are: 1, 2, 3, 4, 5

Comments

1

Just going to leave this here:

if some one is trying to pass json to a twig extension .. and they can't phrase it because its escaped ect. this may be just what they are after.

$data_array = json_decode(html_entity_decode($data_string), true);

Comments

0

In my case i have got an JsonArray in my Entity then i've add in my Entity a methode

<?php
namespace ACME\DefaultBundle\Entity;
/*...*/    
public function getDecodedPath()
{
    return json_decode($this->path);
}

Comments

0
// $twig is a \Twig\Environment

$twig->addFunction(new \Twig\TwigFunction('json_decode', json_decode(...)));

While the other answers would mostly work, I find adding an extension to be very heavy-handed, and the custom function example is also a bit verbose. This is a generic way to introduce the json_decode function to twig, using the first-class syntax from PHP 8.1 onwards.

It will expose json_decode as exactly the php function within twig templates rendered from that environment. If you'd like to tailor the json_decode calls, for instance to leverage the throw on error flag, it's possible to wrap that in another callable:

$twig->addFunction(new TwigFunction(
    'json_decode',
    fn (string $value, ?bool $assoc = null) => json_decode($value, $assoc, 512, \JSON_THROW_ON_ERROR),
));

Keeping the ability to cast to an array or stdClass doesn't change much because twig will access properties on both arrays and objects from the dot notation but it's very low effort to keep the ability to ask for an object.

Comments

0

The Twig Service Bundle https://github.com/zenstruck/twig-service-bundle makes this really easy.

composer require zenstruck/twig-service-bundle
cat > config/packages/zenstruck_twig_service.yaml <<END
zenstruck_twig_service:
  functions:
    json_decode: json_decode
END

Now in your twig:

{{ dump(fn_json_decode('{"abbr": "ct", "count": 45}')) }}

enter image description here

A more complete example would be to add fetch as file_get_contents

{{ dump(fn_json_decode(fn_fetch('https://dummyjson.com/products'))) }}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.