I’ve put a file test.json in the data folder.
Here is the content of “test.json”:
{"a":"1",
"b":1.0,
"c":1}
If I run
{{range $testkey, $testvalue := getJSON "/data/test.json"}}
{{ $testkey }} has value {{ $testvalue }} of type {{ printf "%T" $testvalue }}
<br>
{{ end }}
I get a has value 1 of type string b has value 1 of type float64 c has value 1 of type float64
Shouldn’t it be c has value 1 of type int as @pointyfar explained here ?
No. That’s sadly a limitation of the current Go JSON package, it treats all numbers as floats (at least when the target is unknown). They’re working on a full rewrite of the Go JSON handling.
If you try the same with YAML or TOML, you will get more sensible result.
Unfortunately, I’m stuck with JSON (accessing a jsonapi of a CMS endpoint)
So, in this case, is there an alternative way to spot an integer value in a json?
You could remarshal to YAML, then unmarshal. The float 1.0 will become an int, and the int 1 will stay an int. It’s a bit janky, but might do what you need.
Unfortunately unmarshall doesn’t work either, as the json contains also maps as values, among others.
I’ll create another issue explaining it a bit more in detail.
But getting back to the current issue:
Perhaps resources.Get or resources.GetRemote?
I’m still on the steep part of the learning curve…
Found a sort of workaround that addresses the symptoms, not the cause.
Forcing int type for float64 numbers that do not have the decimal separator, hence no decimal part.
{{range $testkey, $testvalue := getJSON "/data/test.json"}}
{{ if and (eq (printf "%T" $testvalue) "float64") (not (strings.Contains (string $testvalue) "." )) }}
{{ $type := "int" }}
{{ $testkey }} has value {{ $testvalue }} of type {{ $type }} <br>
{{ else}}
{{ $type := printf "%T" $testvalue }}
{{ $testkey }} has value {{ $testvalue }} of type {{ $type }} <br>
{{ end }}
{{ end }}
However, this forces int also for "b":1.0, because getJSON truncates it.
Oh well… not such a big loss, though, depending on the use case. @bep , forcing int for numbers without decimal part would be a valid idea to use under the hood in the Hugo version of getJSON ?
First, the data.GetJSON template function will be deprecated in the next minor release. Instead, capture the data as a resource (page, global, or remote), then pass it through transform.Unmarshal.
Second, Hugo uses Go’s encoding/json package whenever it reads JSON into a data structure. This process is called unmarshaling. From the Go docs for json.Unmarshal:
To unmarshal JSON into an interface value, Unmarshal stores one of these in the interface value:
bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null
So, by the time Hugo has access to the data, integers are already floats. To do something else “under the hood” would require us to rewrite this portion of Go’s encoding/json package… that’s not going to happen. As bep mentioned, this will ultimately be handled upstream.
Hugo uses Go’s json.Unmarshal function when:
Reading JSON front matter
Accessing JSON files via .Site.Data
Accessing JSON files via the soon-to-be-deprecated data.GetJSON template function
Using the transform.Unmarshal template function with JSON data
{{ range $testkey, $testvalue := getJSON "/data/test.json" | transform.Remarshal "YAML" | transform.Unmarshal }}
{{ $testkey }} has value {{ $testvalue }} of type {{ printf "%T" $testvalue }}
<br>
{{ end }}
produces the expected result: a has value 1 of type string b has value 1 of type int c has value 1 of type int
However, in the case of the json files stored locally in the data folder, .Site.Data.<name_of_the_file_without_the_json_extension> is prefered, as getJSON is soon to be deprecated as mentioned above.
So this is the alternative:
{{ range $testkey, $testvalue := .Site,Data.test | transform.Remarshal "YAML" | transform.Unmarshal }}
{{ $testkey }} has value {{ $testvalue }} of type {{ printf "%T" $testvalue }}
<br>
{{ end }}