How to print variable for, in outside for, in golang? - go

I have problem how to print a variable for, in outside for, in Go?
I'm using library GJSON gjson
I have try many way , I just entered the global variable but just appear final index,
like:
datePriodGlobal = DatePeriod.String()
and
datePriodGlobal = DatePeriod.String()
another way I try but appear just final index too, like below:
tempPayments:= "Envelope.Body.GetCustomReportResponse.GetCustomReportResult.ContractSummary.PaymentCalendarList.PaymentCalendar."
resultMapPriodTest := gjson.Get(jsonString,tempPayments + "#.Date")
resultContractsSubmittedTest := gjson.Get(jsonString, tempPayments + "#.ContractsSubmitted")
var datePriodGlobal string
for _, DatePeriod := range resultMapPriodTest.Array()[1:13] {
datePriodGlobal = fmt.Sprintf("%s", DatePeriod.String())
}
var contractsSubmittedGlobal string
for _, ContractsSubmitted := range resultContractsSubmittedTest.Array()[1:13]{
contractsSubmittedGlobal = fmt.Sprintf("%s", ContractsSubmitted.String())
}
fmt.Printf("%s | %s \t|",datePriodGlobal, contractsSubmittedGlobal)
}
I have json like this:

I will suggest just iterate over the PaymentCalendar as a slice of JSON objects rather than querying each field using the indexes as their pseudo-ids.
Here is a simple demonstration:
func main() {
jsonString := `
{
"PaymentCalendarList": {
"PaymentCalendar": [
{"ContractSubmitted": 10,
"Date": "2018-01-01T01:01:01"},
{"ContractSubmitted": 20,
"Date": "2018-01-01T02:02:02"},
{"ContractSubmitted": 30,
"Date": "2018-01-01T03:03:03"}
{"ContractSubmitted": 40,
"Date": "2018-01-01T04:04:04"}
{"ContractSubmitted": 50,
"Date": "2018-01-01T05:05:05"}
]
}
}`
result := gjson.Get(jsonString, "PaymentCalendarList.PaymentCalendar")
for _, paymentCal := range result.Array()[0:3] {
date := paymentCal.Get("Date")
contractSubmit := paymentCal.Get("ContractSubmitted")
fmt.Printf("%s | %s\n", date, contractSubmit)
}
}
Playground

"Cannot use 'DatePeriod' (type Result) as type string in assignment"
So, the variable DatePeriod is a Result type, not a String. You're specifying you want to print a string with %s, but not giving fmt.Sprintf a string, causing that error. The Sprintf is unnecessary if the value given was already a String.
Looking at gjson.go, the Result type has a String() method, so you'd want instead DatePeriod.String().
EDIT:
From your latest edit, I think I see your second issue. Your loops replace the ...Global string variables each time, so you'll only ever get the last value in the slice you've passed to range. Since your slices are identical in length, you might be better off with something like this:
resultMapPriodTest := gjson.Get(jsonString,tempPayments + "#.Date")
resultContractsSubmittedTest := gjson.Get(jsonString, tempPayments + "#.ContractsSubmitted")
dateArray := resultMapPriodTest.Array()[1:13]
contractsArray := resultContractsSubmittedTest.Array()[1:13]
for i := 0; i<len(dateArray); i++ {
d := dateArray[i].String()
c := contractsArray[i].String()
fmt.Printf("%s | %s \t|", d, c)
}

Related

In golang how to loop through string interface

I have an interface that has some strings. I want to print each item one by one. For example, consider the following interface.Here I want to print sud first then man
var x interface{} = []string{"sud", "man"}
You can use something like this:
var x interface{} = []string{"sud", "man"}
res, ok := x.([]string)
if !ok {
fmt.Errorf("error")
}
for i := range res {
fmt.Println(res[i])
}

Get the type of value using cty in hclwrite

am looking for a way to find the type of variable using go-cty package in hclwrite.
My aim is to generate a variables file like below
variable "test_var" {
val1 = bool
val2 = string
val3 = number
}
reference: https://developer.hashicorp.com/terraform/language/values/variables
I am using the below code to generate this.
vars := hclwrite.NewEmptyFile()
vars_root_body := vars.Body()
vars_file, vars_create_err := os.Create("variables.tf")
logErrors(vars_create_err)
vars_block := vars_root_body.AppendNewBlock("variable",[]string{"test_var"})
vars_block_body := vars_block.Body()
vars_block_body.SetAttributeValue("val", cty.Value{})
_, vars_write_err := vars_file.Write(vars.Bytes())
logErrors(vars_write_err)
defer vars_file.Close()
the above code generates this
variable "test_var" {
val = null
}
I want to fetch the type of that variable and set the attribute value based on that type, as show in the reference link above. I tried lot of ways but didn't get anything. Can someone please help me on this?
I tried the above code and lot of other ways like
cty.SetValEmpty(cty.Bool)
but it didn't work.
The expected syntax for a variable block in Terraform includes an argument named type, not an argument named val. From your example I assume that you are intending to populate type.
The type constraint syntax that Terraform uses is not directly part of HCL and so there isn't any built-in way to generate that syntax in only one step. However, type constraint are built from HCL's identifier and function call syntaxes, and hclwrite does have some functions for helping to generate those as individual parts:
TokensForIdentifier
TokensForFunctionCall
f := hclwrite.NewEmptyFile()
rootBody := f.Body()
varBlock := rootBody.AppendNewBlock("variable", []string{"example"})
varBody := varBlock.Body()
varBody.SetAttributeRaw(
"type",
hclwrite.TokensForFunctionCall(
"set",
hclwrite.TokensForIdentifier("string"),
),
)
fmt.Printf("%s", f.Bytes())
The above will generate the following:
variable "example" {
type = set(string)
}
If you already have a cty.Value value then you can obtain its type using the Type method. However, as mentioned above there isn't any ready-to-use function for converting a type into a type expression, so if you want to be able to generate a type constraint for any value then you'd need to write a function for this yourself, wrapping the TokensForFunctionCall and TokensForIdentifier functions. For example:
package main
import (
"fmt"
"sort"
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/zclconf/go-cty/cty"
)
func main() {
f := hclwrite.NewEmptyFile()
rootBody := f.Body()
varBlock := rootBody.AppendNewBlock("variable", []string{"example"})
varBody := varBlock.Body()
varBody.SetAttributeRaw(
"type",
typeExprTokens(cty.Set(cty.String)),
)
fmt.Printf("%s", f.Bytes())
}
func typeExprTokens(ty cty.Type) hclwrite.Tokens {
switch ty {
case cty.String:
return hclwrite.TokensForIdentifier("string")
case cty.Bool:
return hclwrite.TokensForIdentifier("bool")
case cty.Number:
return hclwrite.TokensForIdentifier("number")
case cty.DynamicPseudoType:
return hclwrite.TokensForIdentifier("any")
}
if ty.IsCollectionType() {
etyTokens := typeExprTokens(ty.ElementType())
switch {
case ty.IsListType():
return hclwrite.TokensForFunctionCall("list", etyTokens)
case ty.IsSetType():
return hclwrite.TokensForFunctionCall("set", etyTokens)
case ty.IsMapType():
return hclwrite.TokensForFunctionCall("map", etyTokens)
default:
// Should never happen because the above is exhaustive
panic("unsupported collection type")
}
}
if ty.IsObjectType() {
atys := ty.AttributeTypes()
names := make([]string, 0, len(atys))
for name := range atys {
names = append(names, name)
}
sort.Strings(names)
items := make([]hclwrite.ObjectAttrTokens, len(names))
for i, name := range names {
items[i] = hclwrite.ObjectAttrTokens{
Name: hclwrite.TokensForIdentifier(name),
Value: typeExprTokens(atys[name]),
}
}
return hclwrite.TokensForObject(items)
}
if ty.IsTupleType() {
etys := ty.TupleElementTypes()
items := make([]hclwrite.Tokens, len(etys))
for i, ety := range etys {
items[i] = typeExprTokens(ety)
}
return hclwrite.TokensForTuple(items)
}
panic(fmt.Errorf("unsupported type %#v", ty))
}
This program will generate the same output as the previous example. You can change func main to pass a different type to typeExprTokens to see how it behaves with some different types.

Using "dynamic" key to extract value from map [duplicate]

This question already has answers here:
Access struct property by name
(5 answers)
Golang dynamic access to a struct property
(2 answers)
How to access to a struct parameter value from a variable in Golang
(1 answer)
Closed 9 months ago.
Came from javascript background, and just started with Golang. I am learning all the new terms in Golang, and creating new question because I cannot find the answer I need (probably due to lack of knowledge of terms to search for)
I created a custom type, created an array of types, and I want to create a function where I can retrieve all the values of a specific key, and return an array of all the values (brands in this example)
type Car struct {
brand string
units int
}
....
var cars []Car
var singleCar Car
//So i have a loop here and inside the for-loop, i create many single cars
singleCar = Car {
brand: "Mercedes",
units: 20
}
//and i append the singleCar into cars
cars = append(cars, singleCar)
Now what I want to do is to create a function that I can retrieve all the brands, and I tried doing the following. I intend to have key as a dynamic value, so I can search by specific key, e.g. brand, model, capacity etc.
func getUniqueByKey(v []Car, key string) []string {
var combined []string
for i := range v {
combined = append(combined, v[i][key])
//this line returns error -
//invalid operation: cannot index v[i] (map index expression of type Car)compilerNonIndexableOperand
}
return combined
//This is suppose to return ["Mercedes", "Honda", "Ferrari"]
}
The above function is suppose to work if i use getUniqueByKey(cars, "brand") where in this example, brand is the key. But I do not know the syntaxes so it's returning error.
Seems like you're trying to get a property using a slice accessor, which doesn't work in Go. You'd need to write a function for each property. Here's an example with the brands:
func getUniqueBrands(v []Car) []string {
var combined []string
tempMap := make(map[string]bool)
for _, c := range v {
if _, p := tempMap[c.brand]; !p {
tempMap[c.brand] = true
combined = append(combined, c.brand)
}
}
return combined
}
Also, note the for loop being used to get the value of Car here. Go's range can be used to iterate over just indices or both indices and values. The index is discarded by assigning to _.
I would recommend re-using this code with an added switch-case block to get the result you want. If you need to return multiple types, use interface{} and type assertion.
Maybe you could marshal your struct into json data then convert it to a map. Example code:
package main
import (
"encoding/json"
"fmt"
)
type RandomStruct struct {
FieldA string
FieldB int
FieldC string
RandomFieldD bool
RandomFieldE interface{}
}
func main() {
fieldName := "FieldC"
randomStruct := RandomStruct{
FieldA: "a",
FieldB: 5,
FieldC: "c",
RandomFieldD: false,
RandomFieldE: map[string]string{"innerFieldA": "??"},
}
randomStructs := make([]RandomStruct, 0)
randomStructs = append(randomStructs, randomStruct, randomStruct, randomStruct)
res := FetchRandomFieldAndConcat(randomStructs, fieldName)
fmt.Println(res)
}
func FetchRandomFieldAndConcat(randomStructs []RandomStruct, fieldName string) []interface{} {
res := make([]interface{}, 0)
for _, randomStruct := range randomStructs {
jsonData, _ := json.Marshal(randomStruct)
jsonMap := make(map[string]interface{})
err := json.Unmarshal(jsonData, &jsonMap)
if err != nil {
fmt.Println(err)
// panic(err)
}
value, exists := jsonMap[fieldName]
if exists {
res = append(res, value)
}
}
return res
}

How do we create an empty map and append new data in golang?

I'm having a problem with creating an empty map and append new data to it while looping on another map.
this is the error i'm getting on my IDE.
here's my data struct to be added to the map.
type Outcome struct {
QuestionIndex string
ChoiceIndex int64
Correct bool
}
func createEntryOutcome(e *entry.Entry) map[string]interface{} {
entryPicks := e.Live.Picks
outcomes := make(map[string]interface{})
for idx, pick := range entryPicks {
mappedPick := pick.(map[string]interface{})
outcomes = append(outcomes, Outcome{
QuestionIndex: idx,
ChoiceIndex: mappedPick["index"].(int64),
Correct: mappedPick["correct"].(bool),
})
}
return outcomes
}
i basically want something like below to be saved in the database.
[
{
qIndex: "1",
cIndex: 1,
correct: false,
},
{
qIndex: "1",
cIndex: 1,
correct: false,
},
]
im new to golang and any help is appreciated. thanks
As the error clearly says:
first argument to append must be slice; have map[string]interface{}
which means you need to create a slice before appending the data to outcomes which is actually slice of outcomes, like you have mentioned in the output you want.
The append function appends the elements x to the end of the slice s,
and grows the slice if a greater capacity is needed.
Create a slice of outcomes and then append the data from entryPicks to that slice:
outcomes := make([]map[string]interface{})
for idx, pick := range entryPicks {
mappedPick := pick.(map[string]interface{})
outcomes = append(outcomes, Outcome{
QuestionIndex: idx,
ChoiceIndex: mappedPick["index"].(int64),
Correct: mappedPick["correct"].(bool),
})
}
which will let you provide the outcome you want.
type Outcome struct {
QuestionIndex string
ChoiceIndex int64
Correct bool
}
func createEntryOutcome(e *entry.Entry) map[string]interface{} {
entryPicks := e.Live.Picks
var outcomes []Outcome
for idx, pick := range entryPicks {
mappedPick := pick.(map[string]interface{})
outcomes = append(outcomes, Outcome{
QuestionIndex: idx,
ChoiceIndex: mappedPick["index"].(int64),
Correct: mappedPick["correct"].(bool),
})
}
return outcomes
}
change outcomes := make(map[string]interface{}) to var outcomes []Outcome

How to turn array into nested json object after splitting

I'm trying to deal with some error descriptions from this library because I need them to be nested JSON objects.
The errors seem to be an array originally, like this:
["String length must be greater than or equal to 3","Does not match format 'email'"]
I needed that to also include the field name of the containing error:
["Field1: String length must be greater than or equal to 3","Email1: Does not match format 'email'"]
After that I need to split each array value by colon : so I can have the field name and error description in separate variables like slice[0] and slice[1].
With that I want to make a nested JSON object like so:
{
"errors": {
"Field1": "String length must be greater than or equal to 3",
"Email1": "Does not match format 'email'"
}
}
This is my way of trying to achieve this:
var errors []string
for _, err := range result.Errors() {
// Append the errors into an array that we can use to split later
errors = append(errors, err.Field() + ":" + err.Description())
}
// Make the JSON map we want to append values to
resultMap := map[string]interface{}{
"errors": map[string]string {
"Field1": "",
"Email1": ""
},
}
// So we actually can use the index keys when appending
resultMapErrors, _ := resultMap["errors"].(map[string]string)
for _, split := range errors {
slice := strings.Split(split, ":")
for _, appendToMap := range resultMapErrors {
appendToMap[slice[0]] = slice[1] // append it like so?
}
}
finalErrors, _ := json.Marshal(resultMapErrors)
fmt.Println(string(finalErrors))
But this throws the errors
main.go:59:28: non-integer string index slice[0]
main.go:59:39: cannot assign to appendToMap[slice[0]]
Any clue how I can achieve this?
var errors = make(map[string]string)
for _, err := range result.Errors() {
errors[err.Field()] = err.Description()
}
// Make the JSON map we want to append values to
resultMap := map[string]interface{}{
"errors": errors,
}
finalErrors, _ := json.Marshal(resultMap)
fmt.Println(string(finalErrors))

Resources