Shopify / liquid

Liquid markup language. Safe, customer facing template language for flexible web apps.
https://shopify.github.io/liquid/
MIT License
11.15k stars 1.4k forks source link

json_parse filter #432

Closed reggi closed 6 years ago

reggi commented 10 years ago

I'd really love a json_parse filter. The below code does not work to convert a json file to a server-side liquid object. It is however, very possible to convert a json file to a javascript variable, but it's sadly not server-side, and bad for SEO to have content rendered by the client. I was going to attempt to break down the string using liquid itself but It's pretty hard. I've created objects with liquid in the past but they're quite crude you can check that out here, the tweet, the gist example.

This is so simple for you guys to implement into production and it's a no brainer.

{% capture quotes %}
  {% include "json.quotes" %}
{% endcapture %}

{% assign quotes = quotes.quotes[0].author | json_parse %}
{{ quotes.quotes[0].author }}

<script>
var quotes = JSON.parse({{ quotes | json }});
document.write(quotes.quotes[0].author);
</script>

I just tried to use the new theming internationalization feature, I couldn't get it to work.

Ideally this type of thing could just be done with locales. But it would still be nice to have a json_parse.

evulse commented 6 years ago

New json_string metafield is out and all I have to say is

image image image image

I did get a little nervous as the example and wording seemed like it could possibly just be a single level object

For metafields with a value_type of 'json_string', the metafield is converted to an iterable hash:

Storage instructions:
<ul>
  {% for key_value in product.metafields.instructions['storage'] %}
    <li>{{ key_value[0] }}: {{ key_value[1] }}</li>
  {% endfor %}
</ul>

But testing proves it is the real deal

🍻string = hello
🍻int = 9
🍻int_array = 8910
    🍻 I can loop through value - 8
    🍻 I can loop through value - 9
    🍻 I can loop through value - 10
🍻string_array = onetwothree
    🍻 I can loop through value - one
    🍻 I can loop through value - two
    🍻 I can loop through value - three
🍻object = {"string"=>"hello", "int"=>9, "int_array"=>[8, 9, 10], "string_array"=>["one", "two", "three"], "object"=>{"string"=>"hello", "int"=>9, "int_array"=>[8, 9, 10], "string_array"=>["one", "two", "three"]}}
    🍻 I can loop through key/value - string = hello
    🍻 I can loop through key/value - int = 9
    🍻 I can loop through key/value - int_array = 8910
    🍻 I can loop through key/value - string_array = onetwothree
    🍻 I can loop through key/value - object = {"string"=>"hello", "int"=>9, "int_array"=>[8, 9, 10], "string_array"=>["one", "two", "three"]}
        🍻string = hello
        🍻int = 9
        🍻int_array = 8910
            🍻 I can loop through value - 8
            🍻 I can loop through value - 9
            🍻 I can loop through value - 10
        🍻string_array = onetwothree
            🍻 I can loop through value - one
            🍻 I can loop through value - two
            🍻 I can loop through value - three
🍻object_array = {"string"=>"hello"}{"int"=>9}
    🍻 I can loop through value - {"string"=>"hello"}
        🍻string = hello
    🍻 I can loop through value - {"int"=>9}
        🍻int = 9

Just a copy of my really dodgy test snippet json-loop.liquid

{% assign new_indent = indent | plus:20 %}
{% for key_value in json %}
  {% if key_value[0] %}
    <li style='margin-left:{{new_indent}}px'>🍻{{ key_value[0] }} = {{ key_value[1] }}</li>

  {% endif %}
  {% assign new_indent = new_indent | plus:20 %}
  {% for inner_key_value in key_value[1] %}
  {% if inner_key_value[1] or inner_key_value[0]  %}
  <li style='margin-left:{{new_indent}}px'>🍻 I can loop through key/value - {{ inner_key_value[0] }} = {{ inner_key_value[1] }}</li>
    {% include 'json-loop' json: inner_key_value[1] indent: new_indent  %}  
    {% elsif inner_key_value and key_value[0] != 'string' %}
  <li style='margin-left:{{new_indent}}px'>🍻 I can loop through value - {{ inner_key_value }}</li>
    {% include 'json-loop' json: inner_key_value indent: new_indent  %}
  {% endif %}
  {% endfor %}
 {% assign new_indent = new_indent | minus:20 %}
{% endfor %}
{% assign new_indent = new_indent | minus:20 %}

Plus test product metafield API call

{
  "metafield": {
    "namespace": "test",
    "key": "json",
    "value": "{\"string\":\"hello\",\"int\":9,\"int_array\":[8,9,10],\"string_array\":[\"one\",\"two\",\"three\"],\"object\":{\"string\":\"hello\",\"int\":9,\"int_array\":[8,9,10],\"string_array\":[\"one\",\"two\",\"three\"],\"object\":{\"string\":\"hello\",\"int\":9,\"int_array\":[8,9,10],\"string_array\":[\"one\",\"two\",\"three\"]}},\"object_array\":[{\"string\":\"hello\"},{\"int\":9}]}",
    "value_type": "json_string"
  }
}
evulse commented 6 years ago

In further good news...

<ul>
  <li>Try to access product.metafields.test['json']['object']['string_array'][0]</li>
  {% if product.metafields.test['json']['object']['string_array'][0]  %}
     <li>🍻 {{product.metafields.test['json']['object']['string_array'][0]}}</li>
  {% else %}
     <li>😢 No direct access</li>
  {% endif %}

  <li>Try to access product.metafields.test['json']['object_array'][0]['string']</li>
  {% if product.metafields.test['json']['object_array'][0]['string']  %}
    <li>🍻 {{product.metafields.test['json']['object_array'][0]['string']}}</li>
  {% else %}
     <li>😢 No direct access</li>
  {% endif %}

  <li>Try to access product.metafields.test.json.object.string_array[0]</li>
  {% if product.metafields.test.json.object.string_array[0] %}
     <li>🍻 {{product.metafields.test.json.object.string_array[0]}}</li>
  {% else %}
     <li>😢 No direct access</li>
  {% endif %}

  <li>Try to access product.metafields.test.json.object_array[0].string</li>
  {% if product.metafields.test.json.object_array[0].string  %}
     <li>🍻 {{product.metafields.test.json.object_array[0].string}}</li>
  {% else %}
     <li>😢 No direct access</li>
  {% endif %}
</ul>

Outcome

Try to access product.metafields.test['json']['object']['string_array'][0]
🍻 one
Try to access product.metafields.test['json']['object_array'][0]['string']
🍻 hello
Try to access product.metafields.test.json.object.string_array[0]
🍻 one
Try to access product.metafields.test.json.object_array[0].string
🍻 hello
Thibaut commented 6 years ago

Shopify has introduced a json_string metafield value type, which you can read about here. This makes it easy to store arbitrary structured data within the Shopify platform and access it inside Liquid templates.

As a result, we will not add a JSON parse filter to Liquid. Our worry is that developers would end up storing JSON strings in various places that are not properly modeled as JSON (e.g. string metafields), or worse, inline in the Liquid code (Liquid is a markup language designed to be simple and approachable).

chrisallick commented 6 years ago

For anyone who arrives here like myself, and is in my situation (ill explain), i found an answer.

Even if you use the JSON Parser add on linked to above, it doesn't impact notification email templates. Because that just impacts your theme, not the Shopify email notifications like "order confirmed."

Situation: I tag my products with actual tags, but the tags are JSON. Editing the metafields was not working and proving to cumbersome, but adding tags that are json strings, and duplicating the products worked well.

I use JavaScript API to build my shopify pages, I just like to use Shopify as a headless ecom solution, and it works perfectly for all my client projects.

Problem: In the email to the client after purchase I need the values in the tags, but they are json and i cannot parse JSON in liquid. I don't care why not, it should be fixed, but I'm tired of arguing with SDKs for features.

Solution: Just do a series of splits. If you control your data, than this is a fine solution. If you are afraid of the JSON being malformed, it's not an ok solution. https://stackoverflow.com/questions/40598976/access-json-lumps-in-metafields-with-shopify-liquid-templates

keidarcy commented 4 years ago

For those who do not want to use json_string metafield value.

EinfachHans commented 3 years ago

This does not work for me. I have a metafield declared as json_string. If i add this (<script>console.log({{ product.metafields.custom.ingredients }});</script>) to my code, it successfully logs:

{base: "Test"}

but if i try to use console.log({{ product.metafields.custom.ingredients.base }}); or use {{ product.metafields.custom.ingredients.base }} it just shows/prints nothing 🤔

Any help would be appreciated!

mrpunkin commented 3 years ago

@EinfachHans I think you might be looking for the json filter. Give this a try:

<script>console.log({{ product.metafields.custom.ingredients | json }});</script>

EinfachHans commented 3 years ago

No i'm not, i want to access the values of my json via the key

mrpunkin commented 3 years ago

@EinfachHans In that case simply use your JSON object to set a variable, then call the properties on that new object stored to the variable.

var obj = {{ product.metafields.custom.ingredients }};
console.log(obj.base);
EinfachHans commented 3 years ago

That does not work. Also i want to use it in my Liquid Template at the end like:

{{ product.metafields.custom.ingredients.base }}
mrpunkin commented 3 years ago

@EinfachHans

If it doesn't work it's not a properly formatted JSON string. Your given example of what you are storing is indeed not a valid JSON string as the key base isn't quoted like it should be in JSON.

However, that being said, what you are trying to do seems to require the use of Shopify's json_string metafield type. See this thread regarding the announcement for examples: https://community.shopify.com/c/API-Announcements/New-json-string-value-type-for-Metafield-object/td-p/465561

EinfachHans commented 3 years ago

I appreciate your help, but did you read what i write? My first sentence here was:

I have a metafield declared as json_string

It definitely is one and it is definitely a valid JSON string, as otherwise i wouldn't be able to save it in the Shopify Admin Overview. The Log output i added was from <script>console.log({{ product.metafields.custom.ingredients }});</script> which logs it correctly as an object

agentfitz commented 1 year ago

Here we are in 2023. I am new to Liquid, but I am baffled that we cannot readily convert JSON objects to Liquid objects, and vice-versa. Even in the ancient language I come from, ColdFusion, we can easily do this:

<cfset jsonString = '{ "firstName": "Frank", "lastName": "The-Tank" }'>
<cfset serverObj = deserializeJson(jsonString)>
<cfset jsonString = serializeJson(serverObj)>

This is useful in an infinite number of use cases.

============

In liquid...

{% assign jsonString =  '{ "firstName": "Frank", "lastName": "The-Tank" }' %}
{% assign serverObj = jsonString | json_parse %}
{% assign jsonString = serverObj | json %}

THIS WOULD BE INCREDIBLY USEFUL. SHOPIFY DEVELOPER NEED THIS.

danieltroger commented 1 year ago

THIS WOULD BE INCREDIBLY USEFUL. SHOPIFY DEVELOPER NEED THIS.

I 100% agree. I'm trying to use metafields rn just to get an object into liquid and it's a pain, especially because chatGPT isn't super good at helping. My biggest hurdle has been figuring out where a backend can be ran that writes to the metafields, the documentation is super unclear about who will host/run a backend (it seems like shopify doesn't host any backend at all and the official app starter CLI hosts it for oneself for development using ngrok but on prod one has to do ??????).

Maybe as a workaround you could post a step to step guides for dummies that never have heard about shopify on how to do the whole backend, api communication, graphql metafield shenanigans

isaacbowen commented 1 year ago

Y'all mayyyy find some utility in Mechanic - https://apps.shopify.com/mechanic. It's a development platform that I built, because yeah, it should not be hard to write to a metafield in response to a customer creation event (or what have you). I wanted a plug-and-play automation backend that was geared toward Shopify work from its core.

Mechanic runs on Liquid, with additional extensions that allow it to build up complex objects - including a parse_json filter, and an assign tag that supports assigning into hashes and arrays.

More at https://learn.mechanic.dev/, if that sounds useful.

I'm offering this out of genuine interest in helping - it seems relevant here. I'm not looking for an opportunity to advertise.


Semi-separately, I don't think a JSON-parsing filter would feel consistent with how Shopify handles the Online Store theme layer. To me, anyway! There's a nuanced balance between increasing developer freedom and encouraging a system toward order/consistency/sanity. It's a balance that exists to serve developer experience and platform sustainability - it's super important to any system. It feels to me like a JSON-parsing filter would upset that balance here a bit (since it opens the door to nearly arbitrary datasources), and Shopify has a looooonnnnggg track record of caring for that balance very intentionally.

Lastly, it's worth noting that this repo is for generic Liquid, and not specifically for Shopify-flavored Liquid. Like, this repo isn't specifically geared toward Shopify themes anymore, because Liquid is used in many more places than that. This repo is geared toward the base implementation of Liquid itself, with no specializations. Because of that, I doubt that Shopify will add anything to this repo that they wouldn't want to also allow on admin.shopify.com, particularly since Liquid is easy to extend for other (non-Shopify) environments.