在golang结构中转换字符串

I've got a json file of AES encrypted secrets. The structure is:

{
    "username": "asdf123ASLdf3",
    "password": "elisjdvo4etQW"
}

And a struct to hold these values

type Secrets struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

It's easy to load the encrypted json values into the struct, but what I really want is a struct with the unencrypted values.

So, for each value, I'd like to run it though a function:

aesDecrypt(key string, value string) string

I'm happy to have this done on the first load, or to move everything over into a new struct.

I would like to avoid repeating the json keys or the field names.

What's the best way to do this?

(Also open to other ways to manage encrypted secrets in Go)

One option is to define a custom JSON Unmarshaler. Another is, as you mention, copy it to another struct.

  1. Implementing the Unmarshaler interface

    The key insight is knowing that you can override json.Unmarshal's behaviour by implementing the Unmarshaler interface. In our case, that means defining a function func (ss *Secrets) UnmarshalJSON(bb []byte) error that will do the AES Decryption when you try to unmarshal any JSON to a Secrets.

    package main
    
    import "fmt"
    import "encoding/json"
    
    type Secrets struct {
        Username string `json:"username"`
        Password string `json:"password"`
    }
    
    func main() {
        jj := []byte(`{
            "username": "asdf123ASLdf3",
            "password": "elisjdvo4etQW"
        }`)
        var ss Secrets
        json.Unmarshal(jj, &ss)
        fmt.Println(ss)
    }
    
    func aesDecrypt(key, value string) string {
        return fmt.Sprintf("'%s' decrypted with key '%s'", value, key)
    }
    
    func (ss *Secrets) UnmarshalJSON(bb []byte) error {
        var objmap map[string]*string
        err := json.Unmarshal(bb, &objmap)
        ss.Username = aesDecrypt("my key", *objmap["password"])
        ss.Password = aesDecrypt("my key", *objmap["username"])
        return err
    }
    

    This outputs a Secrets struct:

    {'elisjdvo4etQW' decrypted with key 'my key'
     'asdf123ASLdf3' decrypted with key 'my key'}
    

    See it in action at the Go Playground.

  2. Copying to another struct

    You could simply make a new Secrets struct every time you need to decrypt the JSON. This could be tedious if you do it alot, or if you have no need for the intermediate state.

    package main
    
    import "fmt"
    import "encoding/json"
    
    type Secrets struct {
        Username string `json:"username"`
        Password string `json:"password"`
    }
    
    func main() {
        jj := []byte(`{
            "username": "asdf123ASLdf3",
            "password": "elisjdvo4etQW"
        }`)
        var ss Secrets
        json.Unmarshal(jj, &ss)
        decoded := Secrets{
            aesDecrypt(ss.Username, "my key"),
            aesDecrypt(ss.Password, "my key")}
        fmt.Println(decoded)
    }
    
    func aesDecrypt(key, value string) string {
        return fmt.Sprintf("'%s' decrypted with key '%s'", value, key)
    }
    

    Check it out at Go Playground.

    This has the same output as above:

    {'elisjdvo4etQW' decrypted with key 'my key'
     'asdf123ASLdf3' decrypted with key 'my key'}
    

Obviously, you would use a different version of aesDecrypt, mine's just a dummy. And, as always, you should actually be checking the returned errors in your own code.