Skip to content
Mike Bridge edited this page Oct 29, 2015 · 8 revisions

Value Comparisons in Shopify Liquid vs. Liquid.NET

Mostly The Same But Not Quite

Shopify's basic definition of truthiness is fairly straightforward: nil and false are FALSE and everything else is TRUE.

However, Shopify liquid has evolved a proliferation of inconsistent ad-hoc value comparison strategies, many of which confuse most designers and developers. For example, some comparisons will act differently depending on which Ruby libraries are enabled, meaning that liquid code is not portable from one environment to another. Also, some comparisons will return a value which is neither true nor false. Some comparisons are inconsistent when compared with nil. Some liquid types have Liskov violations and depend on underlying object implementations of the object being compared---and sometimes will return an unexpected error. Worse, comparisons may return different results depending on whitespace around the comparison operator. Also, there's a difference between variables which are unset and return something called an EmptyDrop (i.e. "truthy" but "empty"), and values that are nil ("not truthy" but "not empty" either).

Liquid.NET

Ultimately, Liquid.NET is mostly compatible with Shopify Liquid, but greatly simplifies the rules to minimize the number of unpleasant surprises.

Firstly, all variables in Liquid.Net are either string, numeric, boolean, collection, hash, date or the special range type (which won't be discussed here). A variable can take the value nil. There's no such thing as a "Drop" or any sort of custom object, so any object-like thing you encounter will have already been converted to a hash. Unset values are nil.

The simplest comparison uses no operators---it just checks for the truthy value of a variable, i.e. whether it's nil or false.

{% if '1' %}truthy{% else %}falsy{% endif %} 
  --> truthy
{% if false %}truthy{% else %}falsy{% endif %} 
  --> falsy

Comparing two values with == will check to see if the type and value are the same.

{% if 3 == 3 %}truthy{% else %}falsy{% endif %} 
 --> truthy
{% if 3 == '3' %}truthy{% else %}falsy{% endif %} 
 --> falsy

Some keywords in ActiveSupport-enhanced Shopify Liquid are empty, blank, present and nil. You can check to see if a variable IS empty, blank, present or nil by using the comparison operator. To be clear, {% if x == empty %} does not mean if value x equals the value empty, instead it means if value x is empty. You might have noticed some people using the Ruby-ish syntax {% x.empty? %}, {% x.blank? %}, or {% x.present? %}.

In Liquid.NET, the "?" operators (such as .blank? work exactly as do the equivalent "double equals" operators (such as == blank), and they operate on all values. Liquid.NET also allows the use of "!=".

empty, blank, present

The empty, blank, present keywords return true when used with the == operator the following cases:

  • empty: Returns true if 1) a value is nil, OR 2) a value is an empty string without whitespace OR 3) The value is collection with no elements OR 4) The value is a hash with no keys.
  • blank: Returns true if 1) a value is nil, OR 2) a value is an empty string or only whitespace OR 3) The value is collection with no elements OR 4) The value is a hash with no keys. You would use blank where you would use String.IsNullOrWhiteSpace() in C# on strings, or ! x.Any() on collections.
  • present: returns true if the value is not blank. Present is intended to be used as "not nil and not whitespace", i.e. "Is there a value present that can be displayed".

Why does nil != false and false != empty but nil == empty

In my experience, the main source of confusion in Shopify liquid is that the "==" operator is overloaded and does two different things. Whenever you see == empty, == blank or == present, you should think "IS empty", "IS blank" or "IS present". If you see myvar == nil, or myvar == true you are specifically checking if myvar has the value nil or myvar is false.

Another way of looking at it is that in Liquid.NET, == works like JavaScript's === operator in that it does not do type casting, except when used as an alias for checking for blank, empty, or present.

Clone this wiki locally