How do I properly use nested for loop to range over rows in golang?

Issue

I am trying to iterate over nested loop as below
but the variables declared in first loop like varOne, varTwo, varThree do not get to the second loop

Matter of fact nothing works after the line for rowTwo := range rowsTwo {

Can anyone please point me to what am doing wrong or how to

func StudentScore() {

    var appendedScores []interface{}

    for rowOne := range rowsOne {
        varOne := rowOne.FirstName
        varTwo := rowOne.LastName
        varThree := rowOne.UserName

        req, _ := http.NewRequest("GET", fmt.Sprintf("https://example.com/users/%s/score", varThree), nil)
    
        client := &http.Client{}
        resp, _ := client.Do(req)


        type responseData struct {
            FirstName   string `json:"first_name"`
            LastName     string  `json:"last_name"`
            Score       float64 `json:"score"`

        }

        type StudentData struct {
            UserName   string `json:"username_name"`
            Score      float64 `json:"score"`
        }

        var rowsTwo []responseData
        
        respBody, _ := ioutil.ReadAll(resp.Body)
        err = json.Unmarshal(respBody, &rowsTwo) 
        fmt.Println("response: ", rowsTwo)
        
//         var appendedScores []interface{}
        studentData := &StudentData{}

        for rowTwo := range rowsTwo {

            fmt.Println("print vars from first loop: ", varOne, varTwo, varThree)
            fmt.Println("api response: ", resp)

            studentData.UserName=string(varThree)
            studentData.Score=float64(rowTwo.Score)

            appendedScores = append(appendedScores, *studentData)
        }

    }
    fmt.Println("student scores: ", appendedScores)

}

Pretty much what am trying to do is use the values from the rows in the first range of rows and use to generate new values to use in the second for loop so i can have a final value to print. So the only reason for the 2 nested for loops is because i need values from first for loop

Is there something am missing or a better way to do this?

Solution

type StudentScore struct {
    UserName string  `json:"user_name"`
    Score    float64 `json:"score"`
}

func GetStudentScore() ([]StudentScore, error) {
    var scores []StudentScore

    for _, row := range rowsOne {
        s := StudentScore{UserName: row.UserName}

        // request score data
        resp, err := http.Get(fmt.Sprintf("https://example.com/users/%s/score", s.UserName))
        if err != nil {
            return nil, err
        }
        defer resp.Body.Close()

        // unmarshal score data
        var data struct {
            Score float64 `json:"score"`
        }
        if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
            return nil, err
        }

        // set score field on Student instance
        s.Score = data.Score

        // append StudentScore
        scores = append(scores, s)
    }

    return scores, nil
}

To do it concurrently you could do something like the following:

type StudentScore struct {
    UserName string  `json:"user_name"`
    Score    float64 `json:"score"`
}

func GetStudentScore() ([]StudentScore, error) {
    var wg sync.WaitGroup
    var ch = make(chan StudentScore)

    for _, row := range rowsOne {
        wg.Add(1)

        // for each row execute the retrieveStudentScore in a separate goroutine
        go func() {
            if err := retrieveStudentScore(row.UserName, ch); err != nil {
                log.Println("failed to retrieve StudentScore:", err)
            }
            wg.Done()
        }()
    }
    
    go func() {
        wg.Wait() // wait until every retrieveStudentScore is finished
        close(ch) // this will cause the range loop below to exit
    }()

    var scores []StudentScore
    for s := range ch {
        // append StudentScore received from channel
        scores = append(scores, s)
    }

    return scores, nil
}
func retrieveStudentScore(userName string, ch chan StudentScore) error {
    // request score data
    resp, err := http.Get(fmt.Sprintf("https://example.com/users/%s/score", userName))
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    // unmarshal score data
    var data struct {
        Score float64 `json:"score"`
    }
    if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
        return err
    }
    
    // send StudentScore to channel
    ch <- StudentScore{UserName: userName, Score: data.Score}
    
    return nil
}

Answered By – mkopriva

Answer Checked By – Marilyn (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.