1

I need to create a list of values from JSON input. For example, if the input is: (this is a file. The content below is mentioned for this question)

x := `[
    {
        "customer_id": "g62"
    },
    {           
        "customer_id": "b23"
    },
    {           
        "customer_id": "a34"
    },
    {       
        "customer_id": "c42"
    }
    
]`

The output needs to be in json format: {"Customer_id":["g62","b23","a34","c42"]}

I have come up with a code which gives out following: [{"Tenant_id":"7645","Customer_id":["g62","b23","a34","c42"]}]

I have an extra field of Tenant_id and I don't think this code is efficient since I need to create a map and then a list to achieve this. I looked for many online examples but I couldn't find any specific one which can help me do better. My goal here is to make it efficient and git rid of an extra field "tenant_id". Any comment or help is appreciated.

Note: I made use of tenant_id since I needed to have a key for my map. I am looking for an alternative solution or idea!

The code below is working:

package main

    import (
        "encoding/json"
        "fmt"
    )
    
    type InpData struct {
        Tenant_id   string
        Customer_id []string
    }
    
    type InpList []InpData
    
    func main() {
    x := `[
        {
            "customer_id": "g62"
        },
        {           
            "customer_id": "b23"
        },
        {           
            "customer_id": "a34"
        },
        {       
            "customer_id": "c42"
        }
        
    ]`

    var v InpList

    if err := json.Unmarshal([]byte(x), &v); err != nil {
        fmt.Println(err)
    }

    fmt.Printf("%+v", v)

    fmt.Println("\nJson out of list:")
    fmt.Println()

    j, err := json.Marshal(v)
    if err != nil {
        fmt.Printf("Error: %s", err.Error())
    } else {
        fmt.Println(string(j))
    }
 }

func (v *InpList) UnmarshalJSON(b []byte) error {
        // Create a local struct that mirrors the data being unmarshalled
        type mciEntry struct {
            Tenant_id   string `json:"tenant_id"`
            Customer_id string `json:"customer_id"`
        }
    var tenant_id string
    tenant_id = "7645"

    type InpSet map[string][]string

    var entries []mciEntry

    // unmarshal the data into the slice
    if err := json.Unmarshal(b, &entries); err != nil {
        return err
    }

    tmp := make(InpSet)

    // loop over the slice and create the map of entries
    for _, ent := range entries {
        tmp[tenant_id] = append(tmp[tenant_id], ent.Customer_id)
    }

    fmt.Println()

    tmpList := make(InpList, 0, len(tmp))

    for key, value := range tmp {
        tmpList = append(tmpList, InpData{Tenant_id: key, Customer_id: value})
    }

    *v = tmpList

    return nil
 

  }

5
  • I think you're making this more complicated than it needs to be. Try thinking of these as two different types (one is the input and one is the output) instead of one single type. Commented Apr 12, 2022 at 22:28
  • @HenryWoody Thank you for your comment. Let's just discuss here about types: Input: json Output type: slice of strings. I had tried alternate way of generating the output type from input data, but it did not work. Commented Apr 12, 2022 at 23:30
  • 1
    Right, but besides being represented as JSON vs a slice, the two types of data have different shapes. Right now you're using a single type to try to unmarshal the input data and re-marshal in a different shape. Generally marshal and unmarshal should be inverse operations for a single type. Here's an example of what I'm thinking: go.dev/play/p/vDhQs4QWL8- Commented Apr 12, 2022 at 23:34
  • 1
    If you use two separate types you can keep the logic much simpler because you can use the default (un)marshal logic for structs and just focus on translating from the input type to the output type (without being concerned with JSON encoding). Commented Apr 12, 2022 at 23:36
  • @HenryWoody, Thank you again! I have learned an important way of thinking from your comments. This paradigm of thinking input and output as one of the considerations for code implementation is simple but crucial. Commented Apr 13, 2022 at 2:22

1 Answer 1

1

Doesn't seem like it should be any more complicated than something like this:

https://goplay.tools/snippet/IhRc4tV9UH0

package main

import (
    "encoding/json"
    "fmt"
)

type Input struct {
    CustomerId string `json:"customer_id"`
}
type InputList []Input

type Output struct {
    CustomerIds []string `json:"Customer_id"`
}

func main() {
    jsonSource := `[
        { "customer_id": "g62" },
        { "customer_id": "b23" },
        { "customer_id": "a34" },
        { "customer_id": "c42" }
    ]`

    input := deserialize(jsonSource)
    fmt.Println()
    fmt.Println("Input:")
    fmt.Printf("%+v\n", input)

    output := transform(input)
    fmt.Println()
    fmt.Println("Output:")
    fmt.Printf("%+v\n", output)

    jsonFinal := serialize(output)
    fmt.Println()
    fmt.Println("Final JSON:")
    fmt.Println(jsonFinal)

}

func deserialize(s string) InputList {
    var input InputList
    if err := json.Unmarshal([]byte(s), &input); err != nil {
        panic(err)
    }
    return input
}

func transform(input InputList) Output {
    output := Output{
        CustomerIds: make([]string, 0, len(input)),
    }
    for _, item := range input {
        output.CustomerIds = append(output.CustomerIds, item.CustomerId)
    }
    return output
}

func serialize(output Output) string {
    buf, err := json.Marshal(output)
    if err != nil {
        panic(err)
    }
    s := string(buf)
    return s
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you @NicholasCarey. The question is solved. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.