Prepare a json object from unmarshaled data

828 views Asked by At

I have json data like this:

json: {"opt1":200,"opt3":"1","opt4":"13","opt5":null,"products":[{"product_id":1,"price":100,"variant_id":100},{"product_id":1,"price":100,"variant_id":null}]}

I have structured it using

type Products struct {
    Product_id int
    Price json.Number
    Variant_id int
}


type Pdata struct {
    Products []Products `json:"products"`
}

Then I use unmarshal

jsonb := []byte(jsonVal)
    var data Pdata
    err := json.Unmarshal(jsonb, &data)
    if err != nil {
        fmt.Println(err)
        return
    }

And get output like

{[{1 100 100} {2 100 0}]}

Now I need to convert that data into a json object like this

{"purchased_products": [{"product_id": 1,"price": 1200,"variation_id": 100},{"product_id": 2,"price": 100,"variation_id": null}]}

After that, I need to assign it to "json"

var d = map[string]string{
    "json":        jsonVal,
    "created_at":  time.Now().Format("2006-01-02 15:04:05"),
    "updated_at":  time.Now().Format("2006-01-02 15:04:05"),
}

How can I do it?

5

There are 5 answers

0
nipuna On BEST ANSWER

Create a type (eg : PurchasedProducts) as below.

type PurchasedProducts struct {
    Products []Products `json:"purchased_products"`
}

And init a PurchasedProducts type variable and assign your unmarshaled products to Purchased products as below.

    pProducts := PurchasedProducts{Products: data.Products}
    jsonByte, err := json.Marshal(pProducts)
    if err != nil {
        fmt.Println(err)
        return
    }

And convert that []byte array to a string and assign it to the map like below.

    var d = map[string]string{
        "json":        string(jsonByte),
        "created_at":  time.Now().Format("2006-01-02 15:04:05"),
        "updated_at":  time.Now().Format("2006-01-02 15:04:05"),
    }

You can run and see full code here.

0
Jaroslaw On

Here you are. Assumptions are that:

  • Product is a model which could be much more complicated, thus it has dedicated structs. Thus transformation from Product to OutputProduct can be unit tested separately.
  • It is one time use application, not a part of the application which exposes an API. Otherwise it should be properly separated into layers and the output should be written as a structure.
package main

import (
    "encoding/json"
    "log"
    "os"
    "time"
)

type (
    Product struct {
        ProductID int `json:"product_id"`
        VariantID int `json:"variant_id"`
        Price     json.Number
    }
    Products []Product

    OutputProduct struct {
        ProductID int `json:"product_id"`
        VariantID int `json:"variation_id"`
        Price     json.Number
    }
)

func (p Product) ToOutputProduct() OutputProduct {
    return OutputProduct{
        ProductID: p.ProductID,
        VariantID: p.VariantID,
        Price:     p.Price,
    }
}

func (p Products) ToOutputProducts() []OutputProduct {
    outputProducts := make([]OutputProduct, len(p))
    for i := 0; i < len(p); i++ {
        outputProducts[i] = p[i].ToOutputProduct()
    }
    return outputProducts
}

func main() {
    var inputJSON = `{"opt1":200,"opt3":"1","opt4":"13","opt5":null,"products":[{"product_id":1,"price":100,"variant_id":100},{"product_id":1,"price":100,"variant_id":null}]}`

    var parsedInput struct {
        Products Products
    }
    if err := json.Unmarshal([]byte(inputJSON), &parsedInput); err != nil {
        log.Fatal(err)
    }

    var output = map[string]interface{}{
        "json":       map[string][]OutputProduct{
            "purchased_products": parsedInput.Products.ToOutputProducts(),
        },
        "created_at": time.Now().Format("2006-01-02 15:04:05"),
        "updated_at": time.Now().Format("2006-01-02 15:04:05"),
    }
    encoder := json.NewEncoder(os.Stdout)
    encoder.SetIndent(" ", "  ")
    if err := encoder.Encode(output); err != nil {
        log.Fatal(err)
    }
}
0
mkopriva On

For nullable fields you can use pointers, so for example if the variant_id json field can be an integer or a json null, and you want to retain that information, then you can change Variant_id int to Variant_id *int.

type Product struct {
    Product_id int         `json:"product_id"`
    Price      json.Number `json:"price"`
    Variant_id *int        `json:"variant_id"`
}

To change json field names between unmarshal and marshal you can declare a second Products struct with the same fields as the original but with struct tags defining the desired field names, then, if the structs are, in all other respects, equivalent you can convert between them.

type Product struct {
    Product_id int         `json:"product_id"`
    Price      json.Number `json:"price"`
    Variant_id int         `json:"variant_id"`
}

type PurchasedProduct struct {
    Product_id int         `json:"product_id"`
    Price      json.Number `json:"price"`
    Variant_id int         `json:"variation_id"` // here variant_id becomes variation_id
}

Then, if p is of type Product, you can simply convert it to PurchasedProduct like so:

pp := PurchasedProduct(p)

To offload the conversion to the marshaling process you can have the original types implement the json.Marshaler interface and do the conversion there.

func (p Product) MarshalJSON() ([]byte, error) {
    type P struct {
        Product_id int         `json:"product_id"`
        Price      json.Number `json:"price"`
        Variant_id *int        `json:"variation_id"`
    }
    return json.Marshal(P(p))
}

With the above you can do the following:

func main() {
    // unmarshal
    var pd Pdata
    err := json.Unmarshal(data, &pd)
    if err != nil {
        panic(err)
    }

    // marshal
    out, err := json.MarshalIndent(pd, "", "  ")
    if err != nil {
        panic(err)
    }
    fmt.Println(string(out))
}

https://play.golang.org/p/0gnrjgUslza

0
blackgreen On

Simply define two more structs that model the second JSON object:

type Pdata2 struct {
    PurchasedProducts []Product2
}

type Product2 struct {
    Product_id    int
    Price         json.Number
    Variation_id  *int // pointer to int
}

The Variation_id field is an *int type because your required output JSON shows "variation_id": null. If you declare the field as simple int, its zero value will be marshaled to 0.

Then initialize those structs using values from the previous ones:

func main() {
    data2 := Pdata2{
        PurchasedProducts: make([]Product2, len(data.Products)),
    }

    for i, p := range data.Products {
        data2.PurchasedProducts[i] = Product2{
            Product_id:   p.Product_id,
            Price:        p.Price,
            Variation_id: nullableInt(p.Variant_id),
        }
    }

    b, err := json.Marshal(data2)
    if err != nil {
        // ... handle error
    }
    var d = map[string]string{
        "json": string(b),
        // ...
    }
    fmt.Println(d)
}

func nullableInt(n int) *int {
    if n == 0 {
        return nil
    }
    return &n
}

Playground: https://play.golang.org/p/xhsmHNBjRKN

0
Gopher On

Based on the suggestions from comments:

package main

import (
    "encoding/json"
    "log"
    "time"
)

type Products struct {
    Product_id int `json:"product_id"`
    Price      int `json:"price"`
    Variant_id int `json:"variant_id"`
}

type ProductData struct {
    Products []Products `json:"products"`
}

type Response struct {
    Json      json.RawMessage `json:"json"`
    CreatedAt string          `json:"created_at"`
    UpdatedAt string          `json:"updated_at"`
}

func main() {
    productJson := `{
        "opt1": 200,
        "opt3": "1",
        "opt4": "13",
        "opt5": null,
        "products": [
            { "product_id": 1, "price": 100, "variant_id": 100 },
            { "product_id": 1, "price": 100, "variant_id": null }
        ]
    }`

    productData := &ProductData{}
    err := json.Unmarshal([]byte(productJson), &productData)
    if err != nil {
        panic(err)
    }

    b, err := json.Marshal(map[string]interface{}{"purchased_products": productData.Products})
    if err != nil {
        panic(err)
    }

    d := &Response{
        Json:      b,
        CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
        UpdatedAt: time.Now().Format("2006-01-02 15:04:05"),
    }

    out, err := json.Marshal(d)
    if err != nil {
        panic(err)
    }

    log.Println(string(out))
}

Output:

2009/11/10 23:00:00 {"json":{"purchased_products":[{"product_id":1,"price":100,"variant_id":100},{"product_id":1,"price":100,"variant_id":0}]},"created_at":"2009-11-10 23:00:00","updated_at":"2009-11-10 23:00:00"}