Printf "$T" retrieving wrong type via getJSON

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.

1 Like

Thanks @bep

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.

1 Like

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…

No, because you still need to unmarshal the resource.

I’d be interested to see a real example (small) of the data.

I’ll create another issue for this, as I have other questions to ask, thanks.
Prepare for the doomsday of the iterations… :wink:

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 ?

With the sample data you provided via DM, this works fine.

{{ $obj := site.Data.nodepage }}
{{ $data := $obj | transform.Remarshal "YAML" | transform.Unmarshal }}

{{ (index $obj.data 0).attributes.drupal_internal__nid | warnf "%[1]v (%[1]T)" }} --> 4 (float64)
{{ (index $data.data 0).attributes.drupal_internal__nid | warnf "%[1]v (%[1]T)" }} --> 4 (int)

As noted above in a previous comment:

JSON Unmarshal Remarshal to YAML + Unmarshal
1 (int) 1.0 (float64) 1 (int)
1.0 (float) 1.0 (float64) 1 (int)
67.42 (float) 67.42 (float64) 67.42 (float64)
"1.0" (string) "1.0" (string) "1.0" (string)
1 Like

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
  • etc.
1 Like

So, to get to the bottom of the first post:

{{ 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 }}

Thank you @bep and @jmooring !

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.