How can I retrieve channel data in json format?

32

25

I've played around with a few implementations but I still don't feel like either is right.

I first came across building the JSON itself. This would be in a file at "craft/templates/entries.json".

[
  {% for entry in craft.entries.section('news').find() %}
    {
      "title": "{{ entry.title | e('js') }}",
      "body": "{{ entry.body | e('js') }}"
    } {% if not loop.last %},{% endif %}
  {% endfor %}
]

This worked but I didn't feel right about writing my own JSON so I came up with this approach instead.

{% set results = [] %}
{% for entry in craft.entries.section('news').find() %}
{%
  set results = results | merge([{
    'title': entry.title,
    'body': entry.body
  }])
%}
{% endfor %}
{{ results | json_encode() | raw }}

This works, the JSON will definitely end up valid and as a bonus it'll be condensed but it's not very reusable and when I add fields I need to adjust it.

What approach should I take to make a reliable way to query data from Craft and return json?

Bill Criswell

Posted 2014-06-12T01:17:42.140

Reputation: 697

Haven't tried this, but could you just output the array returned by craft.entries? {{ craft.entries.section('news').find() | json_encode() | raw }} – Jeremy Gimbel – 2014-06-12T01:24:34.713

I gave that a shot at the start but it just returned two empty objects. – Bill Criswell – 2014-06-12T01:27:23.540

Answers

20

Twig is great for outputting HTML/XML markup, but it’s not exactly the best option when you just want to output some data as JSON.

The Element API plugin aims to make outputting entries (and other element types) as JSON as easy as possible, and it’s completely customizable from its PHP-based API configuration file.

To achieve your desired JSON output, you would set your craft/config/elementapi.php file to this:

<?php
namespace Craft;

return [
    'endpoints' => [

        'entries.json' => [
            'elementType' => 'Entry',
            'criteria' => ['section' => 'news'],
            'transformer' => function(EntryModel $entry) {
                return [
                    'title' => $entry->title,
                    'body' => (string) $entry->body
                ];
            }
        ],

    ]
];

Brandon Kelly

Posted 2014-06-12T01:17:42.140

Reputation: 27 245

25

I just threw together a add-on that will provide a Twig filter called prune to allow you to more easily do what you're asking here.

https://github.com/mattstauffer/craftcms-prune

With prune you can now just do the following:

{{ craft.entries.section('news').find() | prune(['title', 'body']) | json_encode() | raw }}

Just pass in an array of the fields you want to pluck out. It's pretty new and could use some love, but it'll get the basics done for sure.

Matt Stauffer

Posted 2014-06-12T01:17:42.140

Reputation: 524

Beautiful. This will give me something to work off of. – Bill Criswell – 2014-06-12T13:33:38.503

That's just an awesome little plugin, gj! – André Elvan – 2014-06-12T15:21:37.007

8

You could also loop through all fields and output it depending on the field-type.

Example:

{% spaceless %}

[
    {% for entry in craft.entries.section('news').limit(1) %}
    {
        {# General Fields #}
        'slug': {{ entry.slug | json_encode() | raw }},
        'postDate': {{ entry.postDate | json_encode() | raw }},
        'title': {{ entry.title | json_encode() | raw }},
        'author': {{ entry.author.name | json_encode() | raw }},


        {# Your Fields #}
        {% for field in entry.getType().getFieldLayout().getFields() %}
            {% set field_attr = attribute(entry, field.field.handle) %}

            {% switch field.field.type %}
                {% case 'Assets' %}
                    "{{ field.field.handle | escape('js') }}": { {% for asset in field_attr %} { 'title': {{ asset.title | json_encode() | raw }}, 'url': {{ asset.url | json_encode() | raw }} }, {% else %}null{% endfor %} },

                {% case 'Categories' %}
                    "{{ field.field.handle | escape('js') }}": { {% for category in field_attr %} { 'title': {{ category.title | json_encode() | raw }} }, {% else %}null{% endfor %} },

                {% case 'Checkboxes' %}
                    "{{ field.field.handle | escape('js') }}": { {% for option in field_attr %} { 'label': {{ option.label | json_encode() | raw }}, 'value': {{ option.value | json_encode() | raw }} }, {% else %}null{% endfor %} },

                {% case 'Date' %}
                    "{{ field.field.handle | escape('js') }}": {{ field_attr | json_encode() | raw }},

                {% case 'DropDown' %}
                    "{{ field.field.handle | escape('js') }}": { 'label': {{ field_attr.label | json_encode() | raw }}, 'value': {{ field_attr.value | json_encode() | raw }} },

                {% case 'Entries' %}
                    "{{ field.field.handle | escape('js') }}": { {% for field_entry in field_attr %} { 'title': {{ field_entry.title | json_encode() | raw }}, 'slug': {{ field_entry.slug | json_encode() | raw }}, 'url': {{ field_entry.url | json_encode() | raw }} }, {% else %}null{% endfor %} },

                {% case 'Matrix' %}
                    "{{ field.field.handle | escape('js') }}": {{ 'This is a matrix-field' | json_encode() | raw }},

                {% case 'MultiSelect' %}
                    "{{ field.field.handle | escape('js') }}": { {% for option in field_attr %} { 'label': {{ option.label | json_encode() | raw }}, 'value': {{ option.value | json_encode() | raw }} }, {% else %}null{% endfor %} },

                {% case 'Radiobutton' %}
                    "{{ field.field.handle | escape('js') }}": { 'label': {{ field_attr.label | json_encode() | raw }}, 'value': {{ field_attr.value | json_encode() | raw }} },

                {% case 'Tags' %}
                    "{{ field.field.handle | escape('js') }}": { {% for tag in field_attr %} { 'name': {{ tag.name | json_encode() | raw }} }, {% else %}null{% endfor %} },

                {% case 'Users' %}
                    "{{ field.field.handle | escape('js') }}": { {% for user in field_attr %} { 'name': {{ user.name | json_encode() | raw }}, 'id': {{ user.id | json_encode() | raw }} }, {% else %}null{% endfor %} },

                {% default %}
                    "{{ field.field.handle | escape('js') }}": {{ field_attr | json_encode() | raw }},
            {% endswitch %}
        {% endfor %}
    }
    {% endfor %}
]

{% endspaceless %}

You might need to customize it, depending on what attributes you need

To make it work with Matrix you'd need to create a Matrix case and copy the whole loop inside that case and set it to use the block var.

Victor In

Posted 2014-06-12T01:17:42.140

Reputation: 6 831

1Thanks! I was hoping to get a little bit of Matt's answer with a little bit of this answer. I'm going to try and combine them both with a "json" route maybe as see how that all goes. – Bill Criswell – 2014-06-12T13:45:16.623

2@BillCriswell I think there may be space for us to make prune smart enough (or add an additional filter) to do special handling for any other types. I thought about building it all out, but figured it would be better to start with just the pruning part. :) – Matt Stauffer – 2014-06-12T13:54:16.743

@Matt Would love to see a plugin which handles all the json stuff :) – Victor In – 2014-06-12T14:15:12.580

1@victorIn Ha, I was actually going to add a second filter that was pruneToJson that was Prune + json_encode + raw, but I ran out of time. I'll do that soon, but the tougher part is making sure it handles all the other fields correctly like you mentioned here. I'll try to dig in on some lunch break soon and will post back here. – Matt Stauffer – 2014-06-12T14:18:32.450

5

You could also do what Victor In has suggested, but deal with everything in arrays and hashes instead of building a big string. That way you only have to encode once at the end, and it's easier (for me) to debug.

{% spaceless %}
  {% set entries = [] %}

  {% for entry in craft.entries.section('people') %}
    {% set thisEntry = {} %}

    {% for fieldModel in entry.getType().getFieldLayout().getFields() %}
      {% set fieldHandle = fieldModel.field.handle %}
      {% set fieldAttr = attribute(entry, fieldHandle) %}

      {% switch fieldModel.field.type %}

        {% case 'Assets' %}
          {% set enumerable = [] %}
          {% for asset in fieldAttr %}
            {% set enumerable = enumerable|merge([{
              'title': asset.title,
              'url': asset.url
            }]) %}
          {% endfor %}
          {% set fieldValue = enumerable %}

        {% default %}
          {% set fieldValue = fieldAttr %}
      {% endswitch %}

      {% set thisEntry = thisEntry|merge({(fieldHandle): fieldValue}) %}
    {% endfor %}

    {% set entries = entries|merge([thisEntry]) %}
  {% endfor %}
  {{ entries | json_encode() | raw }}
{% endspaceless %}

Tim Kelty

Posted 2014-06-12T01:17:42.140

Reputation: 1 871

However, I think expanding on Prune's functionality is the way to go! – Tim Kelty – 2014-09-16T19:21:26.327

1

I found another plugin that returns JSON format.

Reference Link: https://github.com/wesrice/rest-easy

I tested it out. It allows you to configure the parameters you need in the URL.

Sandy D.

Posted 2014-06-12T01:17:42.140

Reputation: 323

Unfortunately this plugin is now deprecated. The Element API plugin is now the best bet.

– Simon East – 2017-01-17T00:01:39.163