1

I would like to store a json object in a custom fieldtype, and be able to traverse the object's attributes using twig dot syntax in my template. I am currently storing the raw json object as a string, but I'm not sure what I need to do to be able to reference the stored string as an object.

Here is a shortened example of the raw json string that is being stored:

"Stripe_Customer JSON: {
    "id": "cus_4v68MrZu3za8XT",
    "object": "customer",
    "cards": { "object": "list", "total_count": 0, "has_more": false, "url": "/v1/customers/cus_4v68MrZu3za8XT/cards", "data": [ ] },
    "subscriptions": { "object": "list", "total_count": 0, "has_more": false, "url": "/v1/customers/cus_4v68MrZu3za8XT/subscriptions", "data": [ ] },
    "etc": "etcetcetc"
}"

In my template I would like to be able to reference the objects properties in some way (i.e. {{ fieldHandle.id }}) for display only; and on the back-end within a service, I would like to set object properties before saving back to the DB (i.e fieldhandle->id = value).

I am not sure whether defineContentAttribute should return AttributeType String or Mixed in this case.

public function defineContentAttribute()
{
    return AttributeType::String;
    //return AttributeType::Mixed;
}

And if it should be a string, how would one define the column type as "Text" vs the default VARCHAR(255)?

Update: I see that there is a similar question regarding custom fieldtype with multiple values, however, in this case, the end-user will not be manipulating the data directly in any way — they will be filling in dummy fields that will be send to a third party service via javascript/ajax, a result object returned, and then forwarded (again via ajax) to a back-end service method to be processed and saved to the DB.

3 Answers 3

4

You already mentioned the related answer in custom fieldtype with multiple values, and it actually is perfect for your usecase.

It doesn't really matter how you transport the data over to Craft, because if the content is set to AttributeType::Mixed, then Craft will JSON encode/decode it when requested.

So, to save your data, send over an array, like so:

$stripeCustomer = [
    "id" => "cus_4v68MrZu3za8XT",
    "object" => "customer",
    "etc" => "etcetcetc"
    ];

// Transport $stripeCustomer to Craft with Ajax

In the context of Javascript, you'd send over the serialized form data.

2
  • I guess what I was looking for is a way to send it over as a single object with object attributes, rather than having to define the object before I send it. It's a rather complicated object, with sub arrays of additional objects, etc. coming from a third party service. And some of the attributes are optional and will not always be defined, etc. Commented Oct 12, 2014 at 21:57
  • I changed the attribute type to mixed and am sending the field value as value and trying to reference it as {{ value.id }} or {{ value['id'] }} but obviously getting an error — Impossible to access a key ("id") on a string variable ("Stripe_Customer JSON:... Commented Oct 12, 2014 at 22:07
3

Completely untested, but a possibility would be to define your content attribute like so:

return array(AttributeType::Mixed, 'model' => 'MyPluginContentModel');

MyPluginContentModel would have every possible value that you would want to expose to your template ('id', 'obj', 'etc') defined as an attribute.

When Craft pulls the JSON from the database, it should decode it and populate your model with any matching attributes it finds.

That gets returned to your template and you can access it via {{ value.id }}.

5
  • Is this possible without an actual Model Class file with all the attributes pre-defined? Or are you suggesting I create a plugin Model with value as one of the attributes? Commented Oct 12, 2014 at 23:41
  • You'd need a model class file for this to work with all of the possible attributes you want to expose to the template defined. Updated the answer.
    – Brad Bell
    Commented Oct 12, 2014 at 23:45
  • How would that work when one of the properties is another object (i.e. a list with its own properties and an array of card objects), would I have to also create models of each of those sub-objects, etc, etc? Ugh. That was exactly was I was trying to avoid. Commented Oct 12, 2014 at 23:55
  • Afraid so... the best I've got right now.
    – Brad Bell
    Commented Oct 12, 2014 at 23:56
  • I'll keep working on it, see if I can come up with something easier. Thank you though!!! Much appreciated. Commented Oct 13, 2014 at 0:19
2

With a highly complex hierarchical object with many layers of sub-objects, etc., I found that it was much easier in the end to just store the raw JSON in the database in a text-field, and decode it manually before passing it to the template to allow full access to the object hierarchy using dot or array syntax.

In the plugin's fieldtype class definition, define the column type as String with no text limit (using the additional column declaration):

public function defineContentAttribute()
{
    return array(AttributeType::String, 'column' => ColumnType::Text);
}

In the fieldType template file the string can then be decoded as a json object manually to make it available to the template using JsonHelper::decode():

public function getInputHtml($name, $value)
{
    $jsonObj = JsonHelper::decode($value);
    return craft()->templates->render('myPlugin/fieldtypes/Myfield/input', array(
        'name'          => $name,
        'value'         => $value,
        'jsonObj'       => $jsonObj
    ));
}

And in the template, (assuming the json was properly formatted) the full json object can now be traversed using dot syntax:

{# access first level attribute #}
<p>{{ jsonObj.id }}</p>
{# access deeper object array level attribute #}
<p>{{ jsonObj.objArray.data[0].id }}</p>

Note: In php, for some unexplained reason, JsonHelper::decode($jsonStr) doesn't seem to allow access to the attributes, so instead the php equivalent php_decode($jsonStr) can be used:

$jsonObj = json_decode($entry->jsonStr);
$Id = $jsonObj->id;

Your Answer

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

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