Decoding JSON in Twig
Asked Answered
S

9

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 %}
Silverfish answered 24/1, 2013 at 11:50 Comment(10)
Why not json_encode() it in PHP?Wifely
Yes, I do json_encode(get_object_vars($this)). The problem is decoding since it has to be in Twig and not PHP.Silverfish
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?Putamen
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.Silverfish
just add a new getter in your entity and do the job in your entity, why cant you do that ?Works
do you know that you can extend twig and write custom filters? twig.sensiolabs.org/doc/advanced.htmlFishtail
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 widgetWorks
Alright thanks. +1 for an interesting question... must read up on Symfony2 sometime!Putamen
Hi @Ferhad, thanks for that info! I'll be looking into that. I just hope I don't end up "reinventing the wheel".Silverfish
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!Silverfish
P
44

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 %}
Pentarchy answered 24/1, 2013 at 15:33 Comment(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' }Foley
What happens if you are just using Twig with out a framework :(Cleptomania
@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?Dang
@Dang 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 ...containerInterfacePentarchy
V
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.

Vd answered 7/6, 2013 at 14:25 Comment(1)
A better solution following a similar approach would be to create a View model / DTO which receives the decoded JSONCasein
E
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 %}
Elburt answered 16/1, 2015 at 11:35 Comment(0)
I
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'),
        ];
    }
}
Isolt answered 7/1, 2016 at 18:27 Comment(0)
M
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
Moffatt answered 29/11, 2017 at 19:12 Comment(0)
E
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);
Ewaewald answered 26/5, 2022 at 10:37 Comment(0)
T
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);
}
Tobias answered 4/4, 2016 at 7:42 Comment(0)
P
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.

Pectase answered 11/1, 2024 at 18:43 Comment(0)
B
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'))) }}
Baseman answered 21/6, 2024 at 10:46 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.