Golang template and testing for Valid fields
Asked Answered
O

1

5

In Go's database/sql package, there are a bunch of Null[Type] structs that help map database values (and their possible nulls) into code. I'm trying to figure out how to test whether a struct field is null, or in other words, when its Valid property is false.

The recommended way to print a SQL field is to use the .Value property, like this:

<div>{{ .MyStruct.MyField.Value }}</div>

This works great.

But suppose I have something slightly more complicated, where I need to test the value against something else, for example:

<select name="y">
   {{ range .SomeSlice }}
       <option value="{{ . }}" {{ if eq $.MyStruct.MyField.Value .}}selected="selected"{{ end }}>{{ . }}</option>
   {{ end }}
</select>

As it happens, this works great, too, unless .MyField is not Valid, in which case I get the error, "error calling eq: invalid type for comparison". The error makes sense, because Go can't compare a nil Field against another value (or something like that).

I would have thought the 'easy' solution would be to test first whether the Value is nil, and then compare it against what I need, like this:

<select name="y">
   {{ range .SomeSlice }}
       <option value="{{ . }}" {{ if and ($.MyStruct.MyField) (eq $.MyStruct.MyField.Value .)}}selected="selected"{{ end }}>{{ . }}</option>
   {{ end }}
</select>

In this case, I get the same "error calling eq: invalid type for comparison". I guess that means .MyField "exists" even though the value of .MyField is not Valid. So, then I tried a half dozen other versions, mostly with the same error, for example:

<select name="y">
   {{ range .SomeSlice }}
       <option value="{{ . }}" {{ if and ($.MyStruct.MyField.Valid) (eq $.MyStruct.MyField.Value .)}}selected="selected"{{ end }}>{{ . }}</option>
   {{ end }}
</select>

At this point, I'm realizing I really don't understand how to test for the existence of a valid field at all. I'd appreciate any help you might have.

Thanks.

Obsecrate answered 4/7, 2018 at 4:11 Comment(0)
E
9

Update: The following answer predates Go 1.18 where the argument evaluation for and and or template function changed to stop early if the results is know. The answer is only valid for prior Go versions.

Starting with Go 1.18, the following doc states the changed behavior:

and
  Returns the boolean AND of its arguments by returning the
  first empty argument or the last argument. That is,
  "and x y" behaves as "if x then y else x."
  Evaluation proceeds through the arguments left to right
  and returns when the result is determined.

The and function in Go templates is not short-circuit evaluated (unlike the && operator in Go), all its arguments are evaluated always. Quoting from text/template package doc:

and
    Returns the boolean AND of its arguments by returning the
    first empty argument or the last argument, that is,
    "and x y" behaves as "if x then y else x". All the
    arguments are evaluated.

This means that the {{if}} action of yours:

{{ if and ($.MyStruct.MyField) (eq $.MyStruct.MyField.Value .)}}

Even though the condition would be evaluated to false if $.MyStruct.MyField is nil, but eq $.MyStruct.MyField.Value . will also be evaluated and result in the error you get.

Instead you may embed multiple {{if}} actions, like this:

{{if $.MyStruct.MyField}}
    {{if eq $.MyStruct.MyField.Value .}}selected="selected"{{end}}
{{end}}

You may also use the {{with}} action, but that also sets the dot, so you have to be careful:

<select name="y">
   {{range $idx, $e := .SomeSlice}}
       <option value="{{.}}" {{with $.MyStruct.MyField}}
               {{if eq .Value $e}}selected="selected"{{end}}
           {{end}}>{{.}}</option>
   {{end}}
</select>

Note:

You were talking about nil values in your question, but the sql.NullXX types are structs which cannot be nil. In which case you have to check its Valid field to tell if its Value() method will return you a non-nil value when called. It could look like this:

{{if $.MyStruct.MyField.Valid}}
    {{if eq $.MyStruct.MyField.Value .}}selected="selected"{{end}}
{{end}}
Ellette answered 4/7, 2018 at 6:51 Comment(2)
Perfect, thanks icza! The only small change I needed to make -- and I'm not sure why -- is adding .Valid to the first if condition, like this: {{ if $.MyStruct.MyField.Valid }} {{if eq $.MyStruct.MyField.Value .}}selected="selected"{{end}}{{end}}. With that, this template is finally working.Obsecrate
@Obsecrate Then it looks you are not using pointers to structs but struct values. In your question you were referring to nil values, and only pointers have nil values, not structs. See edited answer.Ellette

© 2022 - 2024 — McMap. All rights reserved.