Add to array from json and execute data in template - templates

I have a little qiestion!
How add to array data from json and execute template. Simple. But Not working!
package main
import (
"fmt"
"html/template"
"os"
"encoding/json"
)
type Person struct {
Name string
Jobs []*Job
}
type Job struct {
Employer string
Role string
}
const templ = `The name is {{.Name}}.
{{with .Jobs}}
{{range .}}
An employer is {{.Employer}}
and the role is {{.Role}}
{{end}}
{{end}}
`
func main() {
job1 := Job{Employer: "Monash", Role: "Honorary"}
job2 := Job{Employer: "Box Hill", Role: "Head of HE"}
byt := []byte(`{"num":6.13,"Jobs":[{"Employer": "test1", "Role": "test1"},{"Employer": "test2", "Role": "test2"}]}`)
var dat map[string]interface{}
if err := json.Unmarshal(byt, &dat); err != nil {
panic(err)
}
fmt.Println(dat)
// HOW ADD FROM ARRAY 'dat' TO STRUCT 'Job'
// LINE 54
person := Person{
Name: "jan",
Jobs: []*Job{&job1, &job2},
}
t := template.New("Person template")
t, err := t.Parse(templ)
checkError(err)
err = t.Execute(os.Stdout, person)
checkError(err)
}
func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}
Here you can play/test code: http://play.golang.org/p/AB8hGLrLRy
Watch line 46.
Thank you very much!

First thing, your dat var isn't of the right type. You should have something like that:
dat := struct {
Jobs []*Job
}
That way, when unmarshalling your JSON string into dat, the Jobs key will be filled with a array of *Job initialized with your data's array.
I'm using an anonymous struct, but you could also have a named struct (see #RoninDev's comment for an example).
Then, just add them to the person array, something like that:
person.Jobs = append(person.Jobs, jobs.Jobs...)
Note the ... operator, which use an array as a variadic argument.
You can see the full working code on this playground.

You're unmarshalling the json fine, it's just into the most ambiguous type the language offers... You could unbox it with a series of type asserts but I don't recommend it. The json can easily be modeled in Go with a structure like the following;
type Wrapper struct {
Num float64 `json:"num"`
Jobs []*Job `json:"Jobs"`
}
Here's an updated example of your code using that struct; http://play.golang.org/p/aNLK_Uk2km
After you've deserialized them you can just use append to add them to the jobs array in the person object like person.Jobs = append(person.Jobs, dat.Jobs...)

Related

How to write a custom unmarshaller for AWS ION?

I'm using Amazon ION to marshal and unmarshal data received from various AWS services.
I need to write a custom unmarshal function, and I found an example of how thats achieved in Amazon ION's official docs, see here
Using the above example, I have written below code:
package main
import (
"bytes"
"fmt"
"github.com/amzn/ion-go/ion"
)
func main() {
UnmarshalCustomMarshaler()
}
type unmarshalMe struct {
Name string
custom bool
}
func (u *unmarshalMe) UnmarshalIon(r ion.Reader) error {
fmt.Print("UnmarshalIon called")
u.custom = true
return nil
}
func UnmarshalCustomMarshaler() {
ionBinary, err := ion.MarshalBinary(unmarshalMe{
Name: "John Doe",
})
if err != nil {
fmt.Println("Error marshalling ion binary: ", err)
panic(err)
}
dec := ion.NewReader(bytes.NewReader(ionBinary))
var decodedResult unmarshalMe
ion.UnmarshalFrom(dec, &decodedResult)
fmt.Println("Decoded result: ", decodedResult)
}
Problem: The above code does not work as expected. The UnmarshalIon function is not called, but according to docs is should get called. What am I doing wrong?
You are probably using the v1.1.3, the one by default which does not contain the feature.

How to define a validator.Fieldlevel equivalent of a given string variable in Go?

I'm validating my config file using Go's validator package
One of the fields(createTime) from the config file I'm reading as a string, and want to make sure that it is a valid time duration:
type Config struct{
...
CreateTime string `yaml:"createTime" validate:"duration,required"`
...
}
So, I have written a custom validation function as follows:
import (
"time"
"github.com/go-playground/validator/v10"
)
// isValidDuration is a custom validation function, and it will check if the given string is a valid time duration
func isValidDuration(fl validator.FieldLevel) bool {
if _, err := time.ParseDuration(fl.Field().String()); err != nil {
return false
}
return true
}
Which I'm using for validation as follows:
func (configObject *Config) Validate() error {
// Validate configurations
validate := validator.New()
if err := validate.RegisterValidation("duration", isValidDuration); err != nil {
return err
}
return validate.Struct(configObject)
}
The custom validator is working fine and I want to write a unit test for isValidDuration function. Following is the unit test boilerplate generated by IDE:
import (
"testing"
"github.com/go-playground/validator/v10"
)
func Test_isValidDuration(t *testing.T) {
type args struct {
fl validator.FieldLevel
}
var tests = []struct {
name string
args args
want bool
}{
// TODO: Add test cases.
{name: "positive", args: ???????, want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isValidDuration(tt.args.fl); got != tt.want {
t.Errorf("isValidDuration() = %v, want %v", got, tt.want)
}
})
}
}
I'm new to Go and not sure what to pass in the args field of the testcase above. How do I create a struct containing one validator.FieldLevel field?
Ideally, I would like to pass something like "10m" as args and since it is a valid time duration, would expect the output of isValidDuration as true since "10m" is a valid duration.
I'm trying this: {name: "positive", args: struct{ fl validator.FieldLevel }{fl: "10m"}, want: true} but getting this ereor: '"10m"' (type string) cannot be represented by the type validator.FieldLevel
How do I create a validator.FieldLevel variable with value equivalent to "10m"? Could someone please help me out?
problem1
A language semantic problem, fl is not the type String.
{
name: "positive",
args: args{
fl: validator.FieldLevel{
// ....
},
},
want: true,
},
problem2
How to use validator.FieldLevel.
In code base, we can see FieldLevel is a interface, and you need create struct validate, which is not exported and not supposed to be used by user.
type FieldLevel interface
// ...
var _ FieldLevel = new(validate)
answer
So, you'd best to write your UT like UT of validator package. Don't use the code from your IDE!
// have a look at this UT
// github.com/go-playground/validator/v10#v10.10.0/validator_test.go
func TestKeysCustomValidation(t *testing.T) {

Go template can't call method on field

I have a wrapper around net/mail.Address that provides some marshalling logic. I'm trying to use it in a template, but I keep getting can't evaluate field String in type EmailAddress. The template docs say:
The name of a niladic method of the data, preceded by a period,
such as
.Method
The result is the value of invoking the method with dot as the
receiver, dot.Method().
and
Method invocations may be chained and combined with fields and keys
to any depth:
.Field1.Key1.Method1.Field2.Key2.Method2
So with that in mind I've written this:
package main
import (
"bytes"
"fmt"
"html/template"
"net/mail"
"os"
)
type EmailAddress struct{ mail.Address }
type emailFormatter struct {
From EmailAddress
To EmailAddress
}
var tmpl = template.Must(template.New("Sample Text").Parse("From: {{.From.String}}\r" + `
To: {{.To.String}}` + "\r" + `
Content-Type: text/html` + "\r" + `
Subject: Sample Text` + "\r\n\r" + `
<!DOCTYPE html>
<html lang="en">
<head>
<title>Sample Text</title>
<meta charset="utf-8"/>
</head>
<body>
Sample Text
</body>
</html>
`));
func main() {
to := EmailAddress{
mail.Address{
Address: "em#i.l",
Name: "",
},
}
from := EmailAddress{
mail.Address{
Address: "no-reply#test.quest",
Name: "",
},
}
fmt.Println(to.String()) //outputs (as expected) "<em#i.l>"
fmt.Println(from.String()) //outputs (as expected) "<no-reply#test.quest>"
f := emailFormatter{
To: to,
From: from,
}
var buff bytes.Buffer
if err := tmpl.Execute(&buff, f); err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
fmt.Println(buff.String())
}
I've separately verified that calling EmailAddress.String is totally legal, so I can't figure out why the only output of this is:
Error: template: Sample Text:1:13: executing "Sample Text" at <.From.String>: can't evaluate field String in type main.EmailAddress
EDIT
At a commenter's suggestion I changed the calls from .From.String and .To.String to .From.Address.String and .To.Address.String, because
"String isn't defined on EmailAddress, it's defined on EmailAddress.Address"
but the result is the same:
Error: template: Sample Text:1:13: executing "Sample Text" at <.From.Address.String>: can't evaluate field String in type mail.Address
Since String is defined with a pointer receiver you need to pass an "addressable" instance of mail.Address to the template to be able to execute that method.
You can do this by passing in a pointer to f.
if err := tmpl.Execute(&buff, &f); err != nil {
panic(err)
}
Or you can do it by passing in pointer to EmailAddress.
type emailFormatter struct {
From *EmailAddress
To *EmailAddress
}
// ...
f := emailFormatter{
To: &to,
From: &from,
}
// ...
if err := tmpl.Execute(&buff, f); err != nil {
panic(err)
}
Or by passing in a pointer to mail.Address.
type EmailAddress struct{ *mail.Address }
// ...
to := EmailAddress{
&mail.Address{
Address: "em#i.l",
Name: "",
},
}
from := EmailAddress{
&mail.Address{
Address: "no-reply#test.quest",
Name: "",
},
}
f := emailFormatter{
To: to,
From: from,
}
// ...
if err := tmpl.Execute(&buff, f); err != nil {
panic(err)
}
Note that the reason you don't need to do that in the Go code is because there the compiler does it for you.
For example:
fmt.Println(to.String())
becomes:
fmt.Println((&to).String())
A method call x.m() is valid if the method set of (the type of) x
contains m and the argument list can be assigned to the parameter list
of m. If x is addressable and &x's method set contains m, x.m() is
shorthand for (&x).m()

How to access a struct slice element key directly in a template file

I am loading some template files and attempting to compile them with some structs I've defined.
The following example is working. I'd like to know if there is a better way of formatting my templateFile to directly access config.Servers[1].Ip1 without needing two sets of {{}}
templateFile:
{{$a := index .Servers 1 }}{{$a.Ip1}} some extra text
learn.go:
package main
import (
"html/template"
"os"
)
type Server struct {
Ip1 string
Ip2 string
}
type Configuration struct {
Servers []Server
}
func main() {
someServers := []Server{
{
Ip1: "1.1.1.1",
Ip2: "2.2.2.2",
},
{
Ip1: "3.3.3.3",
Ip2: "4.4.4.4",
},
}
config := Configuration{
Servers: someServers,
}
tmpl, err := template.ParseFiles("./templateFile")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, config)
if err != nil {
panic(err)
}
}
Please refer this:
https://golang.org/pkg/html/template/
You have to use {{}} in your HTML template, if you wish to access any Struct variable.

Create item in dynamodb using go

I'm using the follow code to create a item in my dynamodb table:
package main
import (
"fmt"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/endpoints"
"github.com/aws/aws-sdk-go-v2/aws/external"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/dynamodbattribute"
)
type Record struct {
ID string
URLs []string
}
// Using the SDK's default configuration, loading additional config
// and credentials values from the environment variables, shared
// credentials, and shared configuration files
var cfg, err = external.LoadDefaultAWSConfig()
func createNewExtraction() error {
svc := dynamodb.New(cfg)
r := Record{
ID: "ABC123",
URLs: []string{
"https://example.com/first/link",
"https://example.com/second/url",
},
}
item, err := dynamodbattribute.MarshalMap(r)
if err != nil {
panic(fmt.Sprintf("failed to DynamoDB marshal Record, %v", err))
}
req := svc.PutItemRequest(&dynamodb.PutItemInput{
TableName: aws.String("test"), Item: item })
_, err = req.Send(); if err != nil {
return err
}
return nil
}
func main() {
if len(cfg.Region) > 0 {
// Set Region to us-east-1 as default.
cfg.Region = endpoints.UsEast1RegionID
}
err = createNewExtraction(); if err != nil {
panic(err.Error())
}
}
But it's returning the error:
panic: ValidationException: One or more parameter values were invalid: Missing the key id in the item
status code: 400, request id: F3VCQSGRIG5GM5PEJE7T5M4CEVVV4KQNSO5AEMVJF66Q9ASUAAJG
goroutine 1 [running]:
main.main()
/Users/user/go/src/Test2/test.go:56 +0x102
exit status 2
I already tried to declare Id, id and ID in my Record struct, but it doesn't work.
The stranger is: I got this code in the official documentation (I'm updating to work with the aws-sdk-go-v2).
Thank you in advance.
I do not know golang, but I had similar problems in nodejs.
Make sure the item you put in the table contains the 'partition key' and the sorting key, case-sensitive.
EDIT:
• It is a golang issue, the item is not built properly when the DynamoDB column names are lowercase.
• Consider redefining the Record structure (see this link for details):
type Record struct{
ID string `json:"id"`
URLs []string `json:"url"`
}
where id and url are column names in DynamoDB table.