Accessing values through bracket notation with a variable in bracket after looping through struct for field names - go

I have 2 JSON files one containing users, and another containing email templates
I'm looping through the code in the email templates, and when there is a key as a value, like this:
"keyToFind": "Username"
Then I want to get the value for Username in the other JSON file:
"Username": "ssmith"
KeyToFind can be a few different things, like Password or Group and I want to avoid writing a specific if statement
I'm trying to do this in my loop but it appears that I can't use a variable in bracket notation
for _, emailElements := range emailTemplates.EmailSpecification {
for _, fieldName := range structs.Names(&User{}) {
if emailElements.KeyToFind == fieldName {
EmailBody.WriteString(user[fieldName])
}
}
What the above is trying to do is loop through the elements in the email template, and then the fields in the Users struct; where an emailElement in the template JSON file of type KeyToFind is gotten, and this is the same as a field name in the struct; look up the user value for the KeyToFind
I could do this in Python without a problem
How can I rewrite line 4 to work in Go? --> user[FieldName]
The error I get is:
user[fieldName] (type User does not support indexing)
But if I write line 4 again to this:
user.Username
It will work fine, but that's obviously only for usernames, they could be Password or Group for the value in KeyToFind
Here are the JSON files:
Email template:
"emailName": "customer",
"emailSpecification": [
{
"emailSubject": "Hi"
},
{
"text": "Username: "
},
{
"keyToFind": "Username"
}
]
I want to get the value of KeyToFind and search the properties in the User file and return the value from that property
User file:
[
{
"UserType": "customer",
"Username": "ssmith",
"Password": "sophie"
}
]

I got it by converting the User struct to a map, once it's a map you can use the bracket notation with dot notation inside
In my context, I then had to convert to a string to pass into WriteString function that buffer needs
Here is the final version:
for _, emailElements := range emailTemplates.EmailSpecification {
for _, fieldName := range structs.Names(&User{}) {
if emailElements.KeyToFind == fieldName {
EmailBody.WriteString(structs.Map(user)[emailElements.KeyToFind].(string))
}
}
}
It's using the package:
"github.com/fatih/structs"

Related

How to use a struct field into another struct without referring to it as a key

I want to insert a struct field into another struct without having to use the struct name.
I know that I can do this:
type Person struct {
Name string
}
type User struct {
Person
Email, Password string
}
But It results in this struct:
user := User{Person: Person{Name: ""}, Email: "", Password: ""}
How can I do something like this:
type Person struct {
Name string
}
type User struct {
Name Person.Name // Here
Email, Password string
}
To use it like this
user := User{Name: "", Email: "", Password: ""}
Is it possible?
Simply put, with the current language implementation you can't.
When initialising a literal you need to be explicit (or, put another way: literal! [sic]). Since a User contains a Person, a literal User must contain a literal Person, as you illustrate:
u := User{
Person: Person{
Name: "Bob",
},
Email: "bob#bobspot.com",
Password: "you're kidding right?",
}
However, once you have a variable of type User, you can then leverage the anonymous field to set (or get) the Name of the anonymous Person with the User:
u := User{}
u.Name = "Bob"
u.Email = "bob#bobspot.com",
u.Password = "you're kidding right?",
Why Does Go Make Me Do All This Work?
Let us imagine that it was possible to initialise the inner Person in the way you are looking for:
u := User{ Name: "Bob" }
Now let us further imagine that we later modify the User struct and add its own Name field:
type User struct {
Person
Name string
Email string
Password string
}
And now you can obviously initialise the new Name field:
u := User{ Name: "Bob" }
Notice that this is identical to the previous code that initialised User.Person.Name but now it is initialising User.Name. Not good.
More Gotchas
There are further traps lurking with code like this.
First, the addition of a Name field in User already similarly "breaks" unqualified references to Name on User variables:
u.Name = "Bob" // used to set User.Person.Name, now sets User.Name
Also, with only an anonymous Person field, the User.Person.Name field is marshalled to JSON by default as a "Name" field:
{
"Name": "",
"Email": "",
"Password": ""
}
If a Name field is added, then this is the field that is marshalled as "Name" and the User.Person.Name field is not marshalled at all.
You might think you can add a json tag for the User.Person.Name, e.g.
type User struct {
Person `json:"PersonName"`
Name string
Email string
Password string
}
But now the Person is marshalled as an object with a Name field:
{
"PersonName": {
"Name": ""
},
"Name": "",
"Email": "",
"Password": ""
}
This also happens if you try to change the marshalled field name of the anonymous Person even if User does not have a Name field.
In short: using anonymous structs within structs as a way of "adding fields" is potentially problematic and fragile and should probably be avoided.

GO is a complex nested structure

I wanted to clarify how to set values for
type ElkBulkInsert struct {
Index []struct {
_Index string `json:"_index"`
_Id string `json:"_id"`
} `json:"index"`
}
to make json.Marshall
there were no problems for the usual structure
package main
import (
"encoding/json"
"fmt"
)
type ElkBulkInsert struct {
Index []struct {
_Index string `json:"_index"`
_Id string `json:"_id"`
} `json:"index"`
}
type ElkBulIsertUrl struct {
Url string `json:"url"`
}
func main() {
sf := ElkBulIsertUrl{
Url: "http://mail.ru",
}
dd, _ := json.Marshal(sf)
fmt.Println(string(dd))
}
It's really unclear what you're asking here. Are you asking why the JSON output doesn't match what you expect? Are you unsure how to initialise/set values on the Index field of type []struct{...}?
Because it's quite unclear, I'll attempt to explain why your JSON output may appear to have missing fields (or why not all fields are getting populated), how you can initialise your fields, and how you may be able to improve the types you have.
General answer
If you want to marshal/unmarshal into a struct/type you made, there's a simple rule to keep in mind:
json.Marshal and json.Unmarshal can only access exported fields. An exported field have Capitalised identifiers. Your Index fieldin the ElkBulkInsert is a slice of an anonymous struct, which has no exported fields (_Index and _Id start with an underscore).
Because you're using the json:"_index" tags anyway, the field name itself doesn't have to even resemble the fields of the JSON itself. It's obviously preferable they do in most cases, but it's not required. As an aside: you have a field called Url. It's generally considered better form to follow the standards WRT initialisms, and rename that field to URL:
Words in names that are initialisms or acronyms (e.g. "URL" or "NATO") have a consistent case. For example, "URL" should appear as "URL" or "url" (as in "urlPony", or "URLPony"), never as "Url". Here's an example: ServeHTTP not ServeHttp.
This rule also applies to "ID" when it is short for "identifier," so write "appID" instead of "appId".
Code generated by the protocol buffer compiler is exempt from this rule. Human-written code is held to a higher standard than machine-written code.
With that being said, simply changing the types to this will work:
type ElkBulkInsert struct {
Index []struct {
Index string `json:"_index"`
ID string `json:"_id"`
} `json:"index"`
}
type ElkBulIsertUrl struct {
URL string `json:"url"`
}
Of course, this implies the data for ElkBulkInsert looks something like:
{
"index": [
{
"_index": "foo",
"_id": "bar"
},
{
"_index": "fizz",
"_id": "buzz"
}
]
}
When you want to set values for a structure like this, I generally find it easier to shy away from using anonymous struct fields like the one you have in your Index slice, and use something like:
type ElkInsertIndex struct {
ID string `json:"_id"`
Index string `json:"_index"`
}
type ElkBulkInsert struct {
Index []ElkInsertIndex `json:"index"`
}
This makes it a lot easier to populate the slice:
bulk := ElkBulkInsert{
Index: make([]ElkInsertIndex, 0, 10), // as an example
}
for i := 0; i < 10; i++ {
bulk.Index = append(bulk.Index, ElkInsertIndex{
ID: fmt.Sprintf("%d", i),
Index: fmt.Sprintf("Idx#%d", i), // wherever these values come from
})
}
Even easier (for instance when writing fixtures or unit tests) is to create a pre-populated literal:
data := ElkBulkInsert{
Index: []ElkInsertIndex{
{
ID: "foo",
Index: "bar",
},
{
ID: "fizz",
Index: "buzz",
},
},
}
With your current type, using the anonymous struct type, you can still do the same thing, but it looks messier, and requires more maintenance: you have to repeat the type:
data := ElkBulkInsert{
Index: []struct{
ID string `json:"_id"`
Index string `json:"_index"`
} {
ID: "foo",
Index: "bar",
},
{ // you can omit the field names if you know the order, and initialise all of them
"fizz",
"buzz",
},
}
Omitting field names when initialising in possible in both cases, but I'd advise against it. As fields get added/renamed/moved around over time, maintaining this mess becomes a nightmare. That's also why I'd strongly suggest you use move away from the anonymous struct here. They can be useful in places, but when you're representing a known data-format that comes from, or is sent to an external party (as JSON tends to do), I find it better to have all the relevant types named, labelled, documented somewhere. At the very least, you can add comments to each type, detailing what values it represents, and you can generate a nice, informative godoc from it all.

Terraform list object in Golang

I am trying to represent Terraform list of objects in Go i.e.
variable "map_roles" {
description = "Additional IAM roles to add to the aws-auth configmap."
type = list(object({
rolearn = string
username = string
groups = list(string)
}))
Are rolearn and others basic types or composite ones e.g. a map? And so is map_roles just a struct of strings and slice of string (list), or is it a struct of maps?
Terratest converts variable values given in terraform.Options to -var command line arguments using its internal function toHclString.
From reading through the implementation of that function and the other functions it calls, it seems like it will convert a Go []interface{} value into Terraform tuple syntax and a Go map[string]interface{} into Terraform object syntax, so a valid value for the type constraint shown might look like this:
[]interface{}{
map[string]interface{}{
"rolearn": "foo",
"username": "bar",
"groups": []interface{"baz"},
},
map[string]interface{}{
"rolearn": "boop",
"username": "beep",
"groups": []interface{"blurp"},
},
}
Based on my read of the code (note: I didn't actually test it 😖) I would expect that to generate a -var argument value like this:
-var map_roles='[{"rolearn" = "foo", "username" = "bar", "groups" = ["baz"]},{"rolearn" = "boop", "username" = "beep", "groups" = ["blurp"]}]'

How to pass GraphQLEnumType in mutation as a string value

I have following GraphQLEnumType
const PackagingUnitType = new GraphQLEnumType({
name: 'PackagingUnit',
description: '',
values: {
Carton: { value: 'Carton' },
Stack: { value: 'Stack' },
},
});
On a mutation query if i pass PackagingUnit value as Carton (without quotes) it works. But If i pass as string 'Carton' it throws following error
In field "packagingUnit": Expected type "PackagingUnit", found "Carton"
Is there a way to pass the enum as a string from client side?
EDIT:
I have a form in my front end, where i collect the PackagingUnit type from user along with other fields. PackagingUnit type is represented as a string in front end (not the graphQL Enum type), Since i am not using Apollo Client or Relay, i had to construct the graphQL query string by myself.
Right now i am collecting the form data as JSON and then do JSON.stringify() and then remove the double Quotes on properties to get the final graphQL compatible query.
eg. my form has two fields packagingUnitType (An GraphQLEnumType) and noOfUnits (An GraphQLFloat)
my json structure is
{
packagingUnitType: "Carton",
noOfUnits: 10
}
convert this to string using JSON.stringify()
'{"packagingUnitType":"Carton","noOfUnits":10}'
And then remove the doubleQuotes on properties
{packagingUnitType:"Carton",noOfUnits:10}
Now this can be passed to the graphQL server like
newStackMutation(input: {packagingUnitType:"Carton", noOfUnits:10}) {
...
}
This works only if the enum value does not have any quotes. like below
newStackMutation(input: {packagingUnitType:Carton, noOfUnits:10}) {
...
}
Thanks
GraphQL queries can accept variables. This will be easier for you, as you will not have to do some tricky string-concatenation.
I suppose you use GraphQLHttp - or similar. To send your variables along the query, send a JSON body with a query key and a variables key:
// JSON body
{
"query": "query MyQuery { ... }",
"variables": {
"variable1": ...,
}
}
The query syntax is:
query MyMutation($input: NewStackMutationInput) {
newStackMutation(input: $input) {
...
}
}
And then, you can pass your variable as:
{
"input": {
"packagingUnitType": "Carton",
"noOfUnits": 10
}
}
GraphQL will understand packagingUnitType is an Enum type and will do the conversion for you.

Inserting ISODate field using mgo error

I'm really new in using go, mgo and gin gonic ...I've been creating a mini app and I have a problem inserting a new register into mongoDB using mgo. My error says:
"PANIC: error parsing element 0 of field documents :: caused by ::
wrong type for '0' field, expected object, found 0: [ { date: new
Date(1441051152939), from: "11", to: "12", office: "2", client_id:
"1368465545" } ]_"
My struct is the next one:
type Reservation struct {
ID bson.ObjectId `bson:"_id,omitempty" json:"_id"`
Date time.Time `bson:"date" json:"date"`
From string `bson:"from" json:"from"`
To string `json:"to"`
Office string `json:"office"`
Client_id string `json:"client_id"` }
And I'm trying to insert it as follows using gin-gonic and mgo:
func addReservation(c *gin.Context) {
x := session.DB("projXXXX").C("reservation")
var reservations []Reservation
c.Bind(&reservations)>
err := x.Insert(&reservations)
if err != nil {
panic(err)
}
c.String(200,"whatever")
}
My collection in mongoDB is like this:
{
"_id" : ObjectId("55ba2e611cb87b9a6d75e94b"),
"date" : ISODate("2015-10-22T00:00:00.000Z"),
"from" : "9",
"to" : "10",
"office" : "4",
"client_id" : "1123456469797"
}
Thanks a lot for your help
From the look of the error, MongoDB is seeing an array where it expects to see a single object. It looks like the problem is that you're trying to insert the []Reservation slice as a single object.
Rather than taking a slice of objects to insert, Collection.Insert takes each object to insert as a separate argument. You probably want to use the special ... syntax for calling a variadic function:
err := x.Insert(reservations...)

Resources