6

After reading JSON and Go, I do understand the basics of decoding json in Go are. However, there this problem where that input json can be sometimes a map & sometimes an array of maps.

consider the following problem:

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    b := []byte(`[{"email":"[email protected]"}]`)
    c := []byte(`{"email":"[email protected]"}`)

    var m interface{}

    json.Unmarshal(b, &m)
    switch v := m.(type) {
    case []interface{}:
        fmt.Println("this is b", v)
    default:
        fmt.Println("No type found")
    }

    json.Unmarshal(c, &m)
    switch v := m.(type) {
    case map[string]interface{}:
        fmt.Println("this is c", v)
    default:
        fmt.Println("No type found")
    }

}

Now, how to I get to the value email: [email protected] in both cases(b & c)

Question:

  1. If json is an array, I want to loop over each & print email.
  2. if json is an map, I want to print email directly

Play: http://play.golang.org/p/UPoFxorqWl

2
  • It seems like you have properly branched, why not just do a type conversion in each branch to pull out the email? Commented Feb 29, 2016 at 4:13
  • ^^How, that's where I am stuck. Commented Feb 29, 2016 at 4:14

5 Answers 5

5

In my experience, using interface{} to handle json decoding may cause some strange problem, which I tend to avoid it. Although there are ways to achieve it using the reflect package.

Here is a solution for you problem base on your origin solution, hope it helps.

package main

import (
    "encoding/json"
    "fmt"
)

type Item struct {
    Email string `json:email`
}

func main() {
    b := []byte(`[{"email":"[email protected]"}]`)
    //b := []byte(`{"email":"[email protected]"}`)

    var m = &Item{}
    var ms = []*Item{}
    err := json.Unmarshal(b, &m)
    if err != nil {
        err = json.Unmarshal(b, &ms)
        if err != nil {
            panic(err)
        }
        for _, m := range ms {
            fmt.Println(m.Email)
        }
    } else {
        fmt.Println(m.Email)
    }

}
Sign up to request clarification or add additional context in comments.

Comments

3

Is this what you wanted? http://play.golang.org/p/8yrirlUAnp

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    b := []byte(`[{"email":"[email protected]"}]`)
    c := []byte(`{"email":"[email protected]"}`)

    var m interface{}

    json.Unmarshal(b, &m)
    switch v := m.(type) {
    case []interface{}:
        for _, x := range v {
            fmt.Println("this is b", x.(map[string]interface{})["email"])
        }
    default:
        fmt.Println("No type found")
    }

    json.Unmarshal(c, &m)
    switch v := m.(type) {
    case map[string]interface{}:
        fmt.Println("this is c", v["email"])
    default:
        fmt.Println("No type found")
    }

}

Edit: missed the loop part, added it.

Comments

2

This is not the most idiomatic, but another way to do it is to try and marshal into the type that you want. The advantage here is there is no extra reflection beyond what is needed.

http://play.golang.org/p/dV5qCu3tKk

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    fmt.Println(extract([]byte(`[{"email":"[email protected]"}]`)))
    fmt.Println(extract([]byte(`{"email":"[email protected]"}`)))
}

func extract(b []byte) string {
    var m map[string]string

    err := json.Unmarshal(b, &m)
    if err == nil {
        return m["email"]
    }

    var nested []map[string]string

    err = json.Unmarshal(b, &nested)
    if err == nil {
        for _, m := range nested {
            return m["email"]
        }
    }
    return ""
}

Comments

2

You can try this package

b := []byte(`[{"email":"[email protected]"}]`)
c := []byte(`{"email":"[email protected]"}`)

var m interface{}
var mm interface{}
json.Unmarshal(b, &m)
json.Unmarshal(c, &mm)

x := map[string]interface{}{
   "wrapped": m,
}
xx := map[string]interface{}{
   "wrapped": mm,
} 

var email string
if email_interface, err := GetProperty(x, "wrapped[0].email"); err == nil {
  email, _ = email_interface.(string)
}
if email_interface, err := GetProperty(xx, "wrapped.email"); err == nil {
  email, _ = email_interface.(string)
}

Comments

1

The Idiomatic way: implement Unmarshaler interface:

type Email struct {
    Val string `json:"email"`
}

func (this *Email) UnmarshalJSON(jsonBytes []byte) error {
    var err error
    type EmailStruct Email
    bytesBuffer := bytes.Buffer{}
    if jsonBytes[0] == '[' {
        emails := []EmailStruct{}
        err = json.Unmarshal(jsonBytes, &emails)
        if err != nil {
            return err
        }

        encoder := gob.NewEncoder(&bytesBuffer)
        err = encoder.Encode(&emails[0])
        if err != nil {
            return err
        }

        decoder := gob.NewDecoder(&bytesBuffer)
        err = decoder.Decode(this)
        if err != nil {
            return err
        }

        return err
    }

    email := EmailStruct{}
    err = json.Unmarshal(jsonBytes, &email)
    if err != nil {
        return err
    }

    encoder := gob.NewEncoder(&bytesBuffer)
    err = encoder.Encode(&email)
    if err != nil {
        return err
    }

    decoder := gob.NewDecoder(&bytesBuffer)
    err = decoder.Decode(this)
    if err != nil {
        return err
    }

    return err
}

Use "json.Unmarshal(jsonBytes, location)" to decode json bytes.

email := Email{}
json.Unmarshal(jsonBytes, &email)

OR

emails := []Email{}
json.Unmarshal(jsonBytes, &emails)

Comments

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.