<p>The reason that you're missing certain attributes when JSON-encoding the raw element instances, is that these aren't real properties on the element classes. In short, when you do something like <code>{{ product.url }}</code> in Twig, what happens under the hood is that the product element class' so-called &quot;magic&quot; <code>__get()</code> method ends up calling a <code>getUrl()</code> method, and returns its value. Which explains why the <code>url</code> attribute is missing when you JSON-encode the products and variants, since only properties and not methods get included when you serialize a class instance.</p>
<p>Rather than JSON-encoding the raw elements, you'll probably have a bit more luck by converting them to arrays first, via the elements' <a href="https://www.yiiframework.com/doc/api/2.0/yii-base-arrayable#toArray()-detail" rel="nofollow noreferrer"><code>toArray()</code></a> method – which is provided by Yii to all models in Craft (elements basically being extravagant models). The <code>toArray()</code> method should drill down into the various getter methods, which should include most of the attributes you're missing (such as the <code>url</code> attribute).</p>
<p>A clean way to add the <code>toArray()</code> call in your current code could be via the <code>|map</code> Twig filter:</p>
<pre><code>{% set variants = variants|map(variant =&gt; variant.toArray()) %}
&lt;script&gt;
    var variants = {{ to_json(variants) }}
    ...
&lt;/script&gt;
</code></pre>
<p>To avoid the JSON being ridiculously huge by outputting the full element arrays with all of their attributes, you can consider passing an array of <em>just the attributes you need</em>, to the <code>toArray()</code> method:</p>
<pre><code>{% set variants = variants|map(variant =&gt; variant.toArray([
    'title', 
    'url', 
    'variants'
])) %}
</code></pre>
<p>This even works recursively, so assuming you just needed the title and URLs for the products, and only the title and URLs for their variants as well, something like this should do it:</p>
<pre><code>{% set variants = variants|map(variant =&gt; variant.toArray([
    'title', 
    'url', 
    'variants.title', 
    'variants.url'
])) %}
</code></pre>

CC BY-SA 4.0
2022-02-18T03:20:20.967Z
Thanks, I suspected something like this was the case, but wasn't sure how to verify or work around it. I actually ended up using the `gql()` function to create a query that got me what I needed, but this way definitely seems more appropriate.
Augustine Calvino
CC BY-SA 4.0
2022-02-17T23:23:40.150Z
2022-02-17T23:42:14.417Z
2022-02-17T23:42:14.417Z
Mats Mikkel Rummelhoff
<p>I ran into a situation similar to this, and without seeing exactly how you're querying these things, you may be running into an eager loading issue. Check out the documentation here: <a href="https://craftcms.com/docs/3.x/dev/eager-loading-elements.html#accessing-eager-loaded-elements" rel="nofollow noreferrer">https://craftcms.com/docs/3.x/dev/eager-loading-elements.html#accessing-eager-loaded-elements</a></p>
<p>You may just need to use the &quot;with&quot; function to load the necessary fields?</p>
<p>I'm new to Craft, but that solved the issue I was having with unexpected &quot;null&quot; fields.</p>

CC BY-SA 4.0
2022-02-13T17:53:54.250Z
I am eager loading that field, so I don't think that's the issue here. I updated my question to include the code doing the eager loading.
Augustine Calvino
CC BY-SA 4.0
2022-02-13T17:35:00.717Z
2022-02-13T17:35:00.717Z
kevindeleon
<p>I am converting certain php data to json for use in my front end javascript.</p>
<p>I have an array of product variants, an array of images from a media field that is defined on my variants, and an array of the url attributes of that asset field. All this on the twig side works as expected.</p>
<p>However, when I log the js objects post-conversion, they seem to be missing some attributes. Notably, the variant objects in js don't have the variantMedia attribute, and the image objects don't have the url attribute, even though on the twig side the map filter is able to access those attributes just fine.</p>
<p>Why is this? Are some element properties &quot;dynamic&quot; in the sense that craft generates them on the fly? Is there a way to make these element fields resolve to properties on the source php array such that json_encode will capture them?</p>
<p>Minimal working example of code:</p>
<pre class="lang-js prettyprint-override"><code>{% do craft.app.elements.eagerLoadElements(
  className(product),
  [product],
  [['variants.variantMedia']]
) %}
{% set variants = product.variants %}
{% set variantMedia = product.variants|map(variant =&gt; variant.variantMedia[0] ?? null)|filter(media =&gt; media is not null) %}
{% set variantMediaURL = variantMedia|map(media =&gt; media.url ?? null)|filter(media =&gt; media is not null) %}

&lt;script&gt;
    var variants = {{ to_json(variants) }}

    console.log(variants.map(v =&gt; v.variantMedia))
    var media = {{ to_json(variantMediae) }}
    console.log(media)

    console.log(media.map(m =&gt; m.url))
    var url = {{ to_json(variantMediaeURL) }}
    console.log(url)
&lt;/script&gt;
</code></pre>
<p><code>to_json</code> is just a twig macro for converting php arrays to json as defined here</p>
<pre><code>{% macro to_json(php_array) %}
  {{ php_array|json_encode|replace(&quot;'&quot;, &quot;&amp;#39;&quot;)|raw }}
{% endmacro %}
</code></pre>
<p>Console logs, where attributes are missing:</p>
<pre class="lang-js prettyprint-override"><code>//  console.log(variants.map(v =&gt; v.variantMedia)) - list is empty when generated through js map function
[null, null]
//  console.log(media) - list is non-empty when generated through twig map
[
    {
        &quot;id&quot;: 51,
        ... // attributes trimmed
        &quot;newFolderId&quot;: null,
        &quot;tempFilePath&quot;: null,
        &quot;avoidFilenameConflicts&quot;: false,
        &quot;suggestedFilename&quot;: null,
        &quot;conflictingFilename&quot;: null,
        &quot;deletedWithVolume&quot;: false,
        &quot;keepFileOnDelete&quot;: false
    }
]
// console.log(media.map(m =&gt; m.url)) - list is empty when generated through js map function
[null]
//  console.log(url) - list is non-empty when generated through twig map
[&quot;https://mystore.com/uploads/images/test-image.jpeg&quot;]

</code></pre>

<templating><json><conversion>
PHP array missing attributes when converted to JSON
# PHP array missing attributes when converted to JSON

(Asked by: username_0 on 2022-02-12)

I am converting certain php data to json for use in my front end javascript.
I have an array of product variants, an array of images from a media field that is defined on my variants, and an array of the url attributes of that asset field. All this on the twig side works as expected.
However, when I log the js objects post-conversion, they seem to be missing some attributes. Notably, the variant objects in js don't have the variantMedia attribute, and the image objects don't have the url attribute, even though on the twig side the map filter is able to access those attributes just fine.
Why is this? Are some element properties "dynamic" in the sense that craft generates them on the fly? Is there a way to make these element fields resolve to properties on the source php array such that json_encode will capture them?
Minimal working example of code:
{% do craft.app.elements.eagerLoadElements(
  className(product),
  [product],
  [['variants.variantMedia']]
) %}
{% set variants = product.variants %}
{% set variantMedia = product.variants|map(variant => variant.variantMedia[0] ?? null)|filter(media => media is not null) %}
{% set variantMediaURL = variantMedia|map(media => media.url ?? null)|filter(media => media is not null) %}



to_json is just a twig macro for converting php arrays to json as defined here
{% macro to_json(php_array) %}
  {{ php_array|json_encode|replace("'", "'")|raw }}
{% endmacro %}

Console logs, where attributes are missing:
//  console.log(variants.map(v => v.variantMedia)) - list is empty when generated through js map function
[null, null]
//  console.log(media) - list is non-empty when generated through twig map
[
    {
        "id": 51,
        ... // attributes trimmed
        "newFolderId": null,
        "tempFilePath": null,
        "avoidFilenameConflicts": false,
        "suggestedFilename": null,
        "conflictingFilename": null,
        "deletedWithVolume": false,
        "keepFileOnDelete": false
    }
]
// console.log(media.map(m => m.url)) - list is empty when generated through js map function
[null]
//  console.log(url) - list is non-empty when generated through twig map
["https://mystore.com/uploads/images/test-image.jpeg"]

**Tags:** templating, json, conversion

---

## 2 answers

### Answer by username_1 on 2022-02-17. Score: 2 | Accepted answer

The reason that you're missing certain attributes when JSON-encoding the raw element instances, is that these aren't real properties on the element classes. In short, when you do something like {{ product.url }} in Twig, what happens under the hood is that the product element class' so-called "magic" __get() method ends up calling a getUrl() method, and returns its value. Which explains why the url attribute is missing when you JSON-encode the products and variants, since only properties and not methods get included when you serialize a class instance.
Rather than JSON-encoding the raw elements, you'll probably have a bit more luck by converting them to arrays first, via the elements' toArray() method – which is provided by Yii to all models in Craft (elements basically being extravagant models). The toArray() method should drill down into the various getter methods, which should include most of the attributes you're missing (such as the url attribute).
A clean way to add the toArray() call in your current code could be via the |map Twig filter:
{% set variants = variants|map(variant => variant.toArray()) %}


To avoid the JSON being ridiculously huge by outputting the full element arrays with all of their attributes, you can consider passing an array of just the attributes you need, to the toArray() method:
{% set variants = variants|map(variant => variant.toArray([
    'title', 
    'url', 
    'variants'
])) %}

This even works recursively, so assuming you just needed the title and URLs for the products, and only the title and URLs for their variants as well, something like this should do it:
{% set variants = variants|map(variant => variant.toArray([
    'title', 
    'url', 
    'variants.title', 
    'variants.url'
])) %}

> **Comments:**
> **username_0**: Thanks, I suspected something like this was the case, but wasn't sure how to verify or work around it. I actually ended up using the `gql()` function to create a query that got me what I needed, but this way definitely seems more appropriate.  

---

### Answer by username_2 on 2022-02-13. Score: 1

I ran into a situation similar to this, and without seeing exactly how you're querying these things, you may be running into an eager loading issue. Check out the documentation here: <https://craftcms.com/docs/3.x/dev/eager-loading-elements.html#accessing-eager-loaded-elements>

You may just need to use the "with" function to load the necessary fields?

I'm new to Craft, but that solved the issue I was having with unexpected "null" fields.

> **Comments:**
> **username_0**: I am eager loading that field, so I don't think that's the issue here. I updated my question to include the code doing the eager loading.  

---

