Unmarshal 2 different structs in a slice

My input json data is this (cannot be changed, from an external resource):

[{
   "Url": "test.url",
   "Name": "testname"
},{ 
   "FormName": "Test - 2018",
   "FormNumber": 43,
   "FormSlug": "test-2018"
}]

I have two structs that will always match the data within the array:

type UrlData struct{
  "Url"  string `json:Url` 
  "Name" string `json:Name` 
}

type FormData struct{
  "FormName"  string `json:FormName` 
  "FormNumber" string `json:FormNumber` 
  "FormSlug" string `json:FormSlug`
}

Obviously the code below will not work, but is it possible at the top level (or otherwise) to declare something like this:

type ParallelData [
 urlData UrlData
 formData FormData
]

Solution 1:

Use a two step process for unmarshaling. First, unmarshal a list of arbitrary JSON, then unmarshal the first and second element of that list into their respective types.

You can implement that logic in a method called UnmarshalJSON, thus implementing the json.Unmarshaler interface. This will give you the compound type you are looking for:

type ParallelData struct {
    UrlData  UrlData
    FormData FormData
}

// UnmarshalJSON implements json.Unmarshaler.
func (p *ParallelData) UnmarshalJSON(b []byte) error {
    var records []json.RawMessage
    if err := json.Unmarshal(b, &records); err != nil {
        return err
    }

    if len(records) < 2 {
        return errors.New("short JSON array")
    }

    if err := json.Unmarshal(records[0], &p.UrlData); err != nil {
        return err
    }

    if err := json.Unmarshal(records[1], &p.FormData); err != nil {
        return err
    }

    return nil
}

Try it on the playground: https://play.golang.org/p/QMn_rbJj-P-

Solution 2:

I think Answer of Peter is awesome.

Option 1:

type ParallelData [
 urlData UrlData
 formData FormData
]

if you need above structure then you can define it as

type UrlData struct {
    Url  string `json:"Url,omitempty"`
    Name string `json:"Name,omitempty"`
}
type FormData struct {
    FormName   string `json:"FormName,omitempty"`
    FormNumber string `json:"FormNumber,omitempty"`
    FormSlug   string `json:"FormSlug,omitempty"`
}
type ParallelData struct {
    UrlData  UrlData  `json:"UrlData,omitempty"`
    FormData FormData `json:"FormData,omitempty"`
}

In this case, your json will look like

[  
   {  
      "UrlData":{  
         "Url":"test.url",
         "Name":"testname"
      }
   },
   {  
      "FormData":{  
         "FormName":"Test - 2018",
         "FormNumber":"43",
         "FormSlug":"test-2018"
      }
   }
]

Option 2:

You've provide following json:

[  
   {  
      "Url":"test.url",
      "Name":"testname"
   },
   {  
      "FormName":"Test - 2018",
      "FormNumber":43,
      "FormSlug":"test-2018"
   }
]

If your json really look like, then you can use following struct

type UrlData struct {
    Url  string `json:Url`
    Name string `json:Name`
}

type FormData struct {
    FormName   string `json:FormName`
    FormNumber int    `json:FormNumber`
    FormSlug   string `json:FormSlug`
}

type ParallelData struct {
    UrlData
    FormData
}

For both options, you can Unmarshall your json like this

var parallelData []ParallelData
err := json.Unmarshal([]byte(str), &parallelData)
if err != nil {
    panic(err)
}
fmt.Println(parallelData)

See option 1 in playground

See option 2 in playground

Solution 3:

You can unmarshal into a map[string]interface{} for example:

type ParallelData map[string]interface{}

func main() {
    textBytes := []byte(`[
    {
      "Url": "test.url",
      "Name": "testname" 
    },
    {
      "FormName": "Test - 2018",
      "FormNumber": 43,
      "FormSlug": "test-2018"
    }]`)
    var acc []ParallelData
    json.Unmarshal(textBytes, &acc)

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

Output:

=> [map[Url:test.url Name:testname] map[FormName:Test - 2018 FormNumber:43 FormSlug:test-2018]]

Playground