twpayne / chezmoi

Manage your dotfiles across multiple diverse machines, securely.
https://www.chezmoi.io/
MIT License
13.35k stars 493 forks source link

fromJson regression in 2.42.x (it can no longer unmarshal arrays) #3379

Closed halostatue closed 11 months ago

halostatue commented 11 months ago

Describe the bug

fromJson can no longer unmarshal arrays.

To reproduce

$  ./chezmoi2.41.0 execute-template '{{ fromJson "[{\"a\": 1}]" }}'
[map[a:1]]
$ ./chezmoi2.42.0 execute-template '{{ fromJson "[{\"a\": 1}]" }}'
chezmoi: template: arg1:1:3: executing "arg1" at <fromJson "[{\"a\": 1}]">: error calling fromJson: json: cannot unmarshal array into Go value of type map[string]interface {}

Expected behavior

The above should return [map[a:1]].

Additional context

The problem appears to be https://github.com/twpayne/chezmoi/blob/c68ddac75c2c0e8964cc6fc30cc61afbf5d7ffba/internal/cmd/templatefuncs.go#L183, but replacing that with any results in everything falling back to the old (float64-only) behaviour.

I tried this:

func (formatJSON) Unmarshal(data []byte, value any) error {
    var result interface{}

    decoder := json.NewDecoder(bytes.NewReader(data))
    decoder.UseNumber()
    if err := decoder.Decode(&result); err != nil {
        return err
    }

    if _, err := decoder.Token(); !errors.Is(err, io.EOF) {
        return errExpectedEOF
    }

    switch result := result.(type) {
    case *[]any:
        value = replaceJSONNumbersWithNumericValuesSlice(*result)
        return nil
    case *map[string]any:
        value = replaceJSONNumbersWithNumericValuesMap(*result)
        return nil
    default:
        value = result
        return nil
    }
}

But that causes other failures that I don't have time to investigate right now.

This breaks my dotfiles.

twpayne commented 11 months ago

Ah, sorry about this. Indeed #3361 had the side effect of restricting fromJson to deserializing JSON objects only, not arrays or values. I'll work on a fix.