在go中转换数据结构:紧急:运行时错误:索引超出范围

I have a data structure in go:

type APIMain struct {
    CodeConv string    `json:"codeConv"`
    Start    time.Time `json:"start"`
    End      time.Time `json:"end"`
    Details  []struct {
        IDPrm string `json:"idPrm"`
        Keys  []struct {
            Timestamp time.Time `json:"timestamp"`
            Value     float64   `json:"value"`
        } `json:"keys"`
    } `json:"details"`
}

that I need to transform to:

type DataGroupedByTS struct {
    CodeConv string    `json:"codeConv"`
    Start    time.Time `json:"start"`
    End      time.Time `json:"end"`
    Details  []struct {
        Timestamp time.Time `json:"timestamp"`
        Keys      []struct {
            IDPrm string  `json:"idPrm"`
            Value float64 `json:"value"`
        } `json:"keys"`
    } `json:"details"`
}

I get:

 panic: runtime error: index out of range

Here is my method but it is failing on the first line of loop:

func groupByTimestamp(apiMain datacheck.APIMain) DataGroupedByTS {
    var dataGrouped DataGroupedByTS
    dataGrouped.CodeConv = apiMain.CodeConv
    dataGrouped.Start = apiMain.Start
    dataGrouped.Start = apiMain.Start
    dataGrouped.End = apiMain.End

    var iDetail = 0
    var iKey = 0
    for _, detail := range apiMain.Details {
        for _, key := range detail.Keys {
            dataGrouped.Details[iDetail].Timestamp = key.Timestamp  // <-- failing here
            dataGrouped.Details[iDetail].Keys[iKey].IDPrm = detail.IDPrm
            dataGrouped.Details[iDetail].Keys[iKey].Value = key.Value
            iKey++
        }
        iDetail++
    }

    return dataGrouped
}

Basically, data originally comes grouped by IDPrm, and I need to group it by timestamp.

How should I do that ? Is there any helpers that could help doing it ?

The problem

The reason is simple:

var dataGrouped DataGroupedByTS

initializes the fields of dataGrouped to the so-called zero value for the type DataGroupedByTS.

The zero value of any compound type T consists of the zero-values for types corresponding to each of the fields of T.

So, for

type DataGroupedByTS struct {
    CodeConv string    `json:"codeConv"`
    Start    time.Time `json:"start"`
    End      time.Time `json:"end"`
    Details  []struct {
        Timestamp time.Time `json:"timestamp"`
        Keys      []struct {
            IDPrm string  `json:"idPrm"`
            Value float64 `json:"value"`
        } `json:"keys"`
    } `json:"details"`
}

the zero value would be

type DataGroupedByTS struct {
    CodeConv: "",
    Start:    time.Time(0),
    End:      time.Time(0),
    Details:  nil,           // watch this!
}

That's because the type of Details is []struct{ ... }, that is, a slice of some structs, and the zero-value of any slice is nil.

You then go on and attempt to write at some index into a non-existing slice (well, the slice without any backing array allocated to hold its data). This reasonably fails with "panic: runtime error: index out of range": an unallocated slice has zero elements, so there is no element at index 0, and there is nothing to assign to.

The fix

Two ways:

  1. Preallocate the destination slice:

        var dataGrouped DataGroupedByTS
        // ...
        dataGrouped.Details = make([]struct{...}, len(apiMain.Details))
        for _, detail := range apiMain.Details {
        // ...
    
  2. Append to a slice, rather than updating its elements in place:

        var dataGrouped DataGroupedByTS
        // ...
        for _, detail := range apiMain.Details {
            dataGrouped.Details = append(dataGrouped.Details, detail)
            // ...
    

    It's OK to append to a nil slice.

My two cents: Could you please whether the object is null or not, because the panic error clearly says "index out of range", and also check for the lower and upper boundaries in the for loop.