35

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?

  • 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 Jun 12 '14 at 1:24
  • I gave that a shot at the start but it just returned two empty objects. – Bill Criswell Jun 12 '14 at 1:27
24

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
                ];
            }
        ],

    ]
];
|improve this answer|||||
  • That is the ultimate best answer. – Gfra54 Feb 18 at 6:34
26

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.

|improve this answer|||||
  • Beautiful. This will give me something to work off of. – Bill Criswell Jun 12 '14 at 13:33
  • That's just an awesome little plugin, gj! – André Elvan Jun 12 '14 at 15:21
9

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.

|improve this answer|||||
  • 1
    Thanks! 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 Jun 12 '14 at 13:45
  • 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 Jun 12 '14 at 13:54
  • @Matt Would love to see a plugin which handles all the json stuff :) – Victor In Jun 12 '14 at 14:15
  • 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 Jun 12 '14 at 14:18
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 %}
|improve this answer|||||
  • However, I think expanding on Prune's functionality is the way to go! – Tim Kelty Sep 16 '14 at 19:21
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.

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.