Cast input to boolean

Is there an easy way to cast a variable to true/false and a reason there is no cast function for booleans available? I would, for instance, like the string "true" (and all its typos like "TRUE", "True" etc.) to be cast as boolean true.

Right now, testing for instance:

  • the string "true" against {{ equal true . }} results in false (because it’s a string, not a boolean)
  • integers seem to generally result in false, too
  • printf %t results in type errors ( %!t(string=true) ) if it’s string or integer.

It currently has a “type safe” true/false for “user safe” inputs. I would have to compare the lowercased trimmed input against a slice of strings and integers that might be my interpretation of true and then set the variable manually to a “non-string” true or false.

Or maybe I am missing some feature(s) that have already solved the issue?

Long story short :wink:

I have this:


{{ $value := partial "func/getTrueFalse.html" "True" }}
{{ define "partials/func/getTrueFalse.html" }}
  {{ $value := false }}
  {{ $true := slice 1 "1" "TRUE" "true" "True" true "y" "Y" "yes" "Yes" "YES" "on" "On" "ON" }}
  {{ $false := slice 0 "0" "FALSE" "false" "False" false "n" "N" "no" "No" "NO" "off" "Off" "OFF" }}
  {{ if collections.In $true . }}
    {{ $value = true }}
  {{ else if not (collections.In $false .) }}
    {{/* TODO send message to CLI about non compliant value */}}
  {{ end }}
  {{ return $value }}
{{ end }}

And would want to do this:

{{ $value := cast.ToBool "True" }}

There was some discussion about this, followed by a PR that’s dying naturally from neglect:
https://github.com/gohugoio/hugo/pull/10666

I’m not thrilled about the proposed naming of at least one of the functions; the intent is not clear.

Function A would return a boolean based on truthyness:

{{ functionA false }} → false
{{ functionA "false" }} → true

We accomplish the above today by double-negating a value (passing if through not | not).

Function B would do what you’re asking about:

{{ functionB false }} → false
{{ functionB "false" }} → false

For now… roll your own. From past experience with YAML 1.0, I would not include yes/no/on/off or their abbreviations.


If we were to implement these at some point, I think “Function A” should be named cast.ToBool (maybe with a bool alias). This would be consistent with things like JS Boolean().

The other one is more difficult to name. Something like isTruthy is, to me, misleading. Go has ParseBool, which is a pretty good name, but none of our existing namespaces are a good fit. Maybe something like cast.ParseToBool or cast.InterpretAsBool.

1 Like

The Go function is in strconv and there are already string functions returning other than strings, so string.ParseToBool or string.ToBool.

According to the section description in the docs the Transform section could be a place:

Transform functions

Use these functions to transform values from one format to another.

transform.ParseBool INPUT → BOOL

different names for true and false values

I think there’s no general thing that would fit all use cases. IMHO this depends on the input which should be consistent in naming within one implementation. Different API’s JSON, YAML use different values. One could consider to have a default (true|on|1 and false|off|0) and allow for specifying addinal arguments for overriding the pattern. (or a site config (?))

  • xxx.ParseBool “true” → "true
  • xxx.ParseBool STRING TRUEPATTERN FALSEPATTERN

would be used as

{{ $value = "invalid" }
{{ xxx.ParseBool $value "valid|good" "invalid|bad" --> false

heres my partial “str2bool”

no default return value in case the string is invalid but raise an error

{{ $bool := false }}
{{ with lower (trim . " \n\r") }}
    {{ if ne "false" (replaceRE `^(false|0|off|no)$` "false" .) }}
        {{ if ne "true" (replaceRE `^(true|1|on|yes)$` "true" .) }}
            {{ errorf "NOT a boolean value '%v'" . }}
        {{ end }}
        {{ $bool = true }}
    {{ end }}
{{ else }}
    {{ errorf "NOT a boolean value '%v'" . }}
{{ end }}
{{ return $bool }}

transform.ParseBool is pretty good. We have a (sort of) precedent with transform.CanHighlight in that it returns a bool.

One of the details with transform.ParseBool is… what should happen if https://pkg.go.dev/strconv#ParseBool returns an error? Should we return an error or false? Returning an error is a bit of dead end, but I’m not sure the other choice is any better.

Regarding optional args… my first inclination is to keep it simple (no optional args), and handle exactly what the Go function handles (1, t, T, TRUE, true, True, 0, f, F, FALSE, false). If we were to allow optional args, both would be required, and should be passed in as slices. For example:

{{ $true := slice "yes, y, Yes, Y, YES" }}
{{ $false := slice "no, n, No, N, No" }}
{{ transform.ParseBool "something" $true $false }}

Having said all that, the use case for both functions is limited, otherwise they would have been implemented a long time ago. In my experience…

  • The primary use case for cast.ToBool would be with the compare.Conditional function, where (under some circumstances) you have to do:

    {{ compare.Conditional ("something" | not | not) "actionIfTrue" "actionIfFalse" }}
    
  • The primary use case for transform.ParseBool would be handling of shortcode and partial arguments. For example, content authors have been known to do both of the following:

    {{< my-shortcode arg=false >}}
    {{< my-shortcode arg="false" >}}
    

One of the reasons the existing PR has lingered is… do we really need these?

We’ve handled the second use case on the docs site with:

I like that one as a general possibility. One could generate the two slices to their needs eg. from a param array. So in case one needs the flexibility he could define that early and have a one shot method to get a boolean. (translation setting could be also a map with keys true/false and slices a values )

This one looks much nicer than the shortcode :wink:

{{- if transform.ToBool $copy }}
   copyStuff
{{- else }}
  createStuff
{{- end }}

I would go for failing in case it cannot be converted. If the method allows variants and the input still does not fit then something is broken. Returning true/false in such cases will often lead to hard trackable errors. I could think of returning nil and have a reflect.IsNil function to check …but…

TL;TR;

Authors dealing with third party or user input write their one-shot shortcode for this kind of conversions. An OOTB function would be less code cluttering if they need to process a lot of these “fuzzy” true/false values and in *different ways for different inputs.

(1, t, T, TRUE, true, True, 0, f, F, FALSE, false) as more Go related. Dealing with stuff from GetRemote, parsing Json,etc from other sources… will mor often have something like on/off yes/no.

But as you said - ROI may not be that high with the generic approach. The simple “Do it like Go” would be a cheap(i guess) win to handle missspelled True,TRUE,true and 0 and 1