34

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 %}
Community
  • 1
  • 1
Czar Pino
  • 6,258
  • 6
  • 35
  • 60
  • Why not `json_encode()` it in PHP? – Pekka Jan 24 '13 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. – Czar Pino Jan 24 '13 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? – halfer Jan 24 '13 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. – Czar Pino Jan 24 '13 at 12:16
  • just add a new getter in your entity and do the job in your entity, why cant you do that ? – mpm Jan 24 '13 at 12:17
  • 3
    do you know that you can extend twig and write custom filters? http://twig.sensiolabs.org/doc/advanced.html – seferov Jan 24 '13 at 12:21
  • anyway the code shown makes no sense since you are using a future json_decode on a hmtl string returned by form_label , it would be easier to tell us what your datas look like and what result you expect as a form widget – mpm Jan 24 '13 at 12:23
  • Alright thanks. +1 for an interesting question... must read up on Symfony2 sometime! – halfer Jan 24 '13 at 12:27
  • Hi @Ferhad, thanks for that info! I'll be looking into that. I just hope I don't end up "reinventing the wheel". – Czar Pino Jan 24 '13 at 12:30
  • Ah yes, you're right @camus it is HTML! My mistake. Your 2nd comment was truncated the first time I saw it and did not make sense to me (apologies for ignoring). I don't think I can simply add getters since I want access to at least 2 properties on an entity field but configuring an entity field only allows access to 1 (via `property`) in its form builder; which is afterall a label. However, I believe this worked (despite `form_label` supposedly being HTML) as I just did it earlier today and stumbled on decoding. I'll recheck and get back on this. Thanks! – Czar Pino Jan 24 '13 at 13:44

7 Answers7

43

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 %}
lfurini
  • 3,729
  • 4
  • 30
  • 48
ButterDog
  • 5,115
  • 6
  • 43
  • 61
  • 4
    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' }` – Hbksagar Apr 20 '15 at 06:37
  • What happens if you are just using Twig with out a framework :( – Robert Johnstone Jul 06 '15 at 15:50
  • 1
    @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 Jul 05 '16 at 11:37
  • 2
    @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 `` from `Services.xml`, the argument and body of `__construct`, the line `protected $container;` and `use ...containerInterface` – ButterDog Jul 05 '16 at 21:11
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.

azzy81
  • 2,261
  • 2
  • 26
  • 37
  • 1
    A better solution following a similar approach would be to create a View model / DTO which receives the decoded JSON – Jack B Feb 17 '20 at 16:17
6

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 %}
Parag Tyagi
  • 8,780
  • 3
  • 42
  • 47
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'),
        ];
    }
}
Chris Hasiński
  • 2,965
  • 2
  • 25
  • 34
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
Ryan Griggs
  • 2,457
  • 2
  • 35
  • 58
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);
}
S.Alfano
  • 21
  • 9
0

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);
taggartJ
  • 277
  • 2
  • 6