I have this Go application that uploads files to either a S3 bucket or a SFTP server (and potentially Google Cloud Storage buckets in the future) and the location of these are stored in JSON config files. I wanted to
Last active
August 14, 2017 07:40
-
-
Save torbjornvatn/8a9f5b50a7f90c1d3e8cbd317e92763b to your computer and use it in GitHub Desktop.
Parsing JSON with optional and mutual exclusive fields in Golang
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"errors" | |
"fmt" | |
"github.com/fatih/structs" | |
) | |
// An JSON array containing two legal and one illegal | |
// configuration objects | |
const inputJson = ` | |
[{ | |
"name": "s3Only", | |
"s3Destination": { | |
"s3Bucket": "s3://some-s3-bucket" | |
} | |
}, | |
{ | |
"name": "sftpOnly", | |
"sftpDestination": { | |
"sftpServer": "sftp.unacast.com" | |
} | |
}, | |
{ | |
"name": "both", | |
"s3Destination": { | |
"s3Bucket": "s3://some-s3-bucket" | |
}, | |
"sftpDestination": { | |
"sftpServer": "sftp.unacast.com" | |
} | |
}] | |
` | |
// Destination is a common interface representing some place | |
// on the internet where we can upload files | |
type Destination interface{} | |
// S3Destination is used to represent S3 buckets | |
type S3Destination struct { | |
S3Bucket string `json:"s3Bucket"` | |
} | |
// SFTPDestination is used to represent SFTP servers | |
type SFTPDestination struct { | |
SFTPServer string `json:"sftpServer"` | |
} | |
// DestinationConf is the parent config object also | |
// containing a name | |
type DestinationConf struct { | |
Name string `json:"name"` | |
Dest Destination `json:"-"` | |
} | |
// String returns a string representation of a DestinationConf | |
func (dc DestinationConf) String() string { | |
name := dc.Name | |
msg := "I'm a config called %v with this destination: %v" | |
switch dest := dc.Dest.(type) { | |
case *S3Destination: | |
return fmt.Sprintf(msg, name, dest.S3Bucket) | |
case *SFTPDestination: | |
return fmt.Sprintf(msg, name, dest.SFTPServer) | |
default: | |
return fmt.Sprintf("Unknown destination: %+v", dc) | |
} | |
} | |
// AddDestintaion takes a var args with destinations and if there's only one it will | |
// set it on the DestinationConf, else it will return an error | |
func (dc *DestinationConf) AddDestination(possibleDestinations ...Destination) error { | |
for _, d := range possibleDestinations { | |
// Here we use the eminent github.com/fatih/structs library to check whether the | |
// destination struct is "empty" or contains value. | |
// "Empty" structs will be discarded. | |
if !structs.IsZero(d) { | |
if dc.Dest != nil { | |
return errors.New("Can't have more than one destionation per config") | |
} | |
dc.Dest = d | |
} | |
} | |
return nil | |
} | |
func main() { | |
// We have to start with parsing the individual configs as raw json messages | |
var rawConfigs []json.RawMessage | |
if err := json.Unmarshal([]byte(inputJson), &rawConfigs); err == nil { | |
for _, rawConfig := range rawConfigs { | |
// We set up pointers representing the different parts of the parsed config | |
conf, s3, sftp := &DestinationConf{}, &S3Destination{}, &SFTPDestination{} | |
err := json.Unmarshal(rawConfig, &struct { | |
*DestinationConf | |
*S3Destination `json:"s3Destination"` | |
*SFTPDestination `json:"sftpDestination"` | |
}{ // sending the pointers in to Unmarshal to get the values out | |
// s3 and sftp will only have values in them if they're present | |
// in the config json | |
conf, s3, sftp, | |
}) | |
if err == nil { | |
// Adding the non empty destination value, failing if both have | |
// value | |
if err = conf.AddDestination(s3, sftp); err == nil { | |
fmt.Printf("%v\n and I'm created from this json:\n%v\n\n", conf, string(rawConfig)) | |
} else { | |
fmt.Println(err) | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment