How to unmarshal two json with same internal struc

2019-07-22 03:39发布

问题:

I have two json files with following structure

{
 "cast": [
        {
            "url": "carey-mulligan",
            "name": "Carey Mulligan",
            "role": "Actress"
        },
        {
            "url": "leonardo-dicaprio",
            "name": "Leonardo DiCaprio",
            "role": "Actor"
        },
        .
        .
        .
         ]
}

and

{
 "movie": [
        {
            "url": "carey-mulligan",
            "name": "Carey Mulligan",
            "role": "Actress"
        },
        {
            "url": "leonardo-dicaprio",
            "name": "Leonardo DiCaprio",
            "role": "Actor"
        },
        .
        .
        .
         ]
}

as you can see internal structure of the json is same for cast and movie. I want to unmarshel these json file into the same golang structure. But i am not able to give two name tags (cast and movie) for same struct element. I want something like

type Detail struct {
    Name string `json:"name"`
    Url  string `json:"url"`
    Role string `json:"role"`
}

type Info struct {
    Detail []Detail `json:"cast or movie"`
}

In which case Detail could parse both cast and movie.

Here is my current code

// RIMAGE project main.go
package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
)

const (
    website = "https://data.moviebuff.com/"
)

func main() {
    fmt.Println("Hello World!")
    content, err := ioutil.ReadFile("data/great-getsby")
    if err != nil {
        panic(err)
    }

    var info Info

    err = json.Unmarshal(content, &info)
    if err != nil {
        panic(err)
    }

    fmt.Println(info.Detail)
}

type Detail struct {
    Name string `json:"name"`
    Url  string `json:"url"`
    Role string `json:"role"`
}

type Info struct {
    Detail []Detail `json:"cast" json:"movie"
}

but it only works for first tag "cast" and gives nill in case json contain the movie.

Thanks in advance.

回答1:

You can use type Info map[string][]Detail instead of your struct. Try it on the Go playground

Or you can use both types in your structure, and make method Details() which will return right one:

type Info struct {
    CastDetails  []Detail `json:"cast"`
    MovieDetails []Detail `json:"movie"`
}

func (i Info) Details() []Detail {
    if i.CastDetails == nil {
        return i.MovieDetails
    }
    return i.CastDetails
}

Try it on the Go playground



回答2:

Try anonymous field in struct:

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
)

type Detail struct {
    Name string `json:"name"`
    Url  string `json:"url"`
    Role string `json:"role"`
}

type Cast struct {
    Detail []Detail `json:"cast"`
}

type Movie struct {
    Detail []Detail `json:"movie"`
}

type Info struct {
    Cast
    Movie
}

func (i *Info) getDetails() []Detail {
    if len(i.Cast.Detail) > 0 {
        return i.Cast.Detail
    }
    return i.Movie.Detail
}

func main() {
    cast, _ := ioutil.ReadFile("./cast.json")
    movie, _ := ioutil.ReadFile("./movie.json")

    var cInfo Info
    err := json.Unmarshal(cast, &cInfo)
    fmt.Printf("cast: %+v\n", &cInfo)
    fmt.Printf("err: %v\n", err)
    fmt.Printf("details: %v\n", cInfo.getDetails())

    var mInfo Info
    err = json.Unmarshal(movie, &mInfo)
    fmt.Printf("movie: %+v\n", &mInfo)
    fmt.Printf("err: %v\n", err)
    fmt.Printf("details: %v\n", mInfo.getDetails())
}

Things to note:

  • One more level of indirection: to access 'Details' field, you need to access either 'Cast' or 'Movie' field first in Info first.
  • Better provide an access function for 'Details' ('getDetail' in this example)


回答3:

If you dig far enough down into encoding/json you'll get to https://github.com/golang/go/blob/master/src/encoding/json/encode.go and the following:

        tag := sf.Tag.Get("json")
        if tag == "-" {
            continue
        }

So it gets one json tag and keeps on going.

You could always go with RoninDev's solution and just copy it over when done.



标签: json encoding go