How to handle missing fields in a JSON response dynamically in Go - json

I'm working on a Go wrapper for an API and I noticed that two of the JSON fields stay empty when they don't have any data.
Basically the API returns a set of information on a given url, and if it was visited at least once, everything is okay and I get a full json that I then Unmarshal into a struct:
{
"stats":{
"status":1,
"date":"09.07.2019",
"title":"Test",
"devices":{
"dev":[
{
"tag":"Desktop"
}
],
"sys":[
{
"tag":"GNU/Linux "
},
{
"tag":"Windows 10"
}
],
"bro":[
{
"tag":"Firefox 67.0"
},
{
"tag":"Chrome 62.0"
}
]
},
"refs":[
{
"link":"www.google.com"
}
]
}
}
This is the struct I'm using:
type Stats struct {
Stats struct {
Status int `json:"status"`
Date string `json:"date"`
Title string `json:"title"`
Devices struct {
Dev []struct {
Tag string `json:"tag"`
} `json:"dev"`
Sys []struct {
Tag string `json:"tag"`
} `json:"sys"`
Bro []struct {
Tag string `json:"tag"`
} `json:"bro"`
} `json:"devices"`
Refs []struct {
Link string `json:"link"`
} `json:"refs"`
} `json:"stats"`
}
When a new url is given, then things become a little bit weird:
{
"stats": {
"status": 1,
"date": "09.07.2019",
"title": "Test2",
"devices": [
],
"refs": [
]
}
}
As you can see, the fields "dev", "sys" and "bro" just disappear because they're not used and when I try to Unmarshal the JSON into the same struct I get json: cannot unmarshal array into Go struct field Stats.device of type [...]
I tried to use two different structs to handle both the responses but I'm sure that there's a way to handle them gracefully with just one.
Any help would be appreciated, thanks!

I finally managed to make it work with an ugly workaround.
I changed my struct to
type Stats struct {
Status int `json:"status"`
Date string `json:"date"`
Title string `json:"title"`
Devices interface{} `json:"devices"`
Refs interface{} `json:"refs"`
}
Then I can finally Unmarshal the JSON in both cases, but I get a map[string]interface{} when an object is passed and an empty interface{} when an empty array is passed. In order to fix this inconsistency, I simply check for the data type and force the use of a JSON intermediate conversion in order to unpack the map[string]interface{} value inside a custom Devices struct:
// Devices contains devices information
type Devices struct {
Dev []struct {
Tag string `json:"tag"`
Clicks string `json:"clicks"`
} `json:"dev"`
Sys []struct {
Tag string `json:"tag"`
Clicks string `json:"clicks"`
} `json:"sys"`
Bro []struct {
Tag string `json:"tag"`
Clicks string `json:"clicks"`
} `json:"bro"`
}
The algorithms I use are the following:
//ForceDevicesToRightType uses a json conversion as intermediary for filling the Stats.Devices
// struct with map[string]interface{} values
func ForceDevicesToRightType(dev interface{}) (Devices, error) {
temp, err := json.Marshal(dev)
if err != nil {
return Devices{}, err
}
// Use a temporary variable of the right type
var devices Devices
err = json.Unmarshal(temp, &devices)
if err != nil {
return Devices{}, err
}
return devices, nil
}
// ForceRefsToRightType uses a json conversion as intermediary for filling the Stats.Refs
// struct with map[string]interface{} values
func ForceRefsToRightType(refs interface{}) (Refs, error) {
temp, err := json.Marshal(refs)
if err != nil {
return Refs{}, err
}
// Use a temporary variable of the right type
var references Refs
err = json.Unmarshal(temp, &references)
if err != nil {
return Refs{}, err
}
return references, nil
}
Since the compiler knows that both Devices and Refs fields are interface{} I cannot simply access any methods after the conversion, so I simply make a cast of the right type and everything works fine.
For example, if I wanted to access the Dev sub-struct, this is the proper way:
y, _ := GetStats()
fmt.Println(y.Devices.(Devices).Dev)
It's ugly, but it works.
Thank you very much for your help, I hope that this method will save you an headache!

Related

How to create multiple validation methods for one endpoint?

I want to make a validation api in order to validate a set of json requests regarding specific set of rules. To do that I want to use just one endpoint and call functions that correspond to the specific json struct. I know that there is no method overloading in go so I am kind of stumped.
...
type requestBodyA struct {
SomeField string `json:"someField"`
SomeOtherField string `json:"someOtherField"`
}
type requestBodyB struct {
SomeDifferentField string `json:"someDifferentField"`
SomeOtherDifferentField string `json:"someOtherDifferentField"`
}
type ValidationService interface {
ValidateRequest(ctx context.Context, s string) (err error)
}
type basicValidationService struct{}
...
So in order to validate lots of different json requests, is it better to create structs for each and every json request? Or should I create these dynamically? How can I know what kind of request is sent if I only have one endpoint?
If you have a single endpoint/rpc that has to accept different JSON types, you'll need to tell it how to distinguish between them, somehow. One option is to have something like:
type request struct {
bodyA *requestBodyA
bodyB *requestBodyB
}
Then, populate these fields in a container JSON object appropriately. The json module will only populate bodyA if a bodyA key is present, otherwise leaving it a nil, and so on.
Here's a more complete example:
type RequestBodyFoo struct {
Name string
Balance float64
}
type RequestBodyBar struct {
Id int
Ref int
}
type Request struct {
Foo *RequestBodyFoo
Bar *RequestBodyBar
}
func (r *Request) Show() {
if r.Foo != nil {
fmt.Println("Request has Foo:", *r.Foo)
}
if r.Bar != nil {
fmt.Println("Request has Bar:", *r.Bar)
}
}
func main() {
bb := []byte(`
{
"Foo": {"Name": "joe", "balance": 4591.25}
}
`)
var req Request
if err := json.Unmarshal(bb, &req); err != nil {
panic(err)
}
req.Show()
var req2 Request
bb = []byte(`
{
"Bar": {"Id": 128992, "Ref": 801472}
}
`)
if err := json.Unmarshal(bb, &req2); err != nil {
panic(err)
}
req2.Show()
}
Another option is to do it more dynamically with maps, but it's likely that the method above will be sufficient.

How do I capitalize all keys in a JSON array?

I'm reading a file.json into memory. It's an array of objects, sample:
[
{"id":123123,"language":"ja-JP","location":"Osaka"}
,{"id":33332,"language":"ja-JP","location":"Tokyo"}
,{"id":31231313,"language":"ja-JP","location":"Kobe"}
]
I want to manipulate certain keys in this JSON file, so that they start with uppercase. Meaning
"language" becomes "Language" each time it's found. What I've done so far is to make a struct representing each object, as such:
type sampleStruct struct {
ID int `json:"id"`
Language string `json:"Language"`
Location string `json:"Location"`
}
Here, I define the capitalization. Meaning, id shouldn't be capitalized, but location and language should.
Rest of the code is as such:
func main() {
if len(os.Args) < 2 {
fmt.Println("Missing filename parameter.")
return
}
translationfile, err := ioutil.ReadFile(os.Args[1])
fileIsValid := isValidJSON(string(translationfile))
if !fileIsValid {
fmt.Println("Invalid JSON format for: ", os.Args[1])
return
}
if err != nil {
fmt.Println("Can't read file: ", os.Args[1])
panic(err)
}
}
func isValidJSON(str string) bool {
var js json.RawMessage
return json.Unmarshal([]byte(str), &js) == nil
}
// I'm unsure how to iterate through the JSON objects and only uppercase the objects matched in my struct here.
func upperCaseSpecificKeys()
// ...
Desired output, assuming the struct represents the whole data object, transform each key as desired:
[
{"id":123123,"Language":"ja-JP","Location":"Osaka"}
,{"id":33332,"Language":"ja-JP","Location":"Tokyo"}
,{"id":31231313,"Language":"ja-JP","Location":"Kobe"}
]
The documentation on json.Unmarshal says (with added emphasis):
To unmarshal JSON into a struct, Unmarshal matches incoming object
keys to the keys used by Marshal (either the struct field name or its
tag), preferring an exact match but also accepting a case-insensitive
match
See example here: https://play.golang.org/p/1vv8PaQUOfg
One way is to implement custom marshal method, although not very flexible:
type upStruct struct {
ID int `json:"id"`
Language string
Location string
}
type myStruct struct {
ID int `json:"id"`
Language string `json:"language"`
Location string `json:"location"`
}
func (m myStruct) MarshalJSON() ([]byte, error) {
return json.Marshal(upStruct(m))
}
....
func main() {
var mySArr []myStruct
// 1. Unmarshal the input
err := json.Unmarshal([]byte(myJson), &mySArr)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Input: \n%+v\n", mySArr)
// 2. Then, marshal it using our custom marshal method
val, err := json.Marshal(mySArr)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Output: \n%v\n", string(val))
}
Link to working code: https://play.golang.org/p/T4twqPc34k0
Thanks to mkopriva

Clean way to conditionally unmarshal JSON to struct

I'm sending requests to a JSON API, and it either returns an error...
{
"error": {
"code": 404,
"message": "Document not found.",
"status": "NOT_FOUND"
}
}
or the data.
{
"name": "projectname",
"fields": {
"userId": {
"stringValue": "erw9384rjidfge"
}
},
"createTime": "2018-06-28T00:52:25.638791Z",
"updateTime": "2018-06-28T00:52:25.638791Z"
}
Here are the corresponding structs
type HttpError struct {
Code int `json:"code"`
Message string `json:"message"`
Status string `json:"status"`
}
type Document struct {
Name string `json:"name"`
Fields struct {
UserID struct {
StringValue string `json:"stringValue"`
} `json:"userId"`
} `json:"fields"`
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
}
Once I get the response, how do I cleanly/concisely unmarshal to the correct struct? I've seen a lot of ugly solutions (maybe Go's fault instead of the writers).
func getDocument() {
resp, _ := httpClient.Get("example.com")
defer resp.Body.Close()
bodyBytes, _ := ioutil.ReadAll(resp.Body)
var data map[string]interface{}
// How to unmarshal to either HttpError or Document??
err = json.Unmarshal([]byte(bodyBytes), &data)
}
By the way, I can't use the Go Firestore client library because reasons.
You can use an struct type inside your unmarshal method; with pointers to establish what's been unmarshalled.
Note: This code assumes there is no overlap of top level json keys... error / name / fields / etc.
type outer struct {
*HttpError `json:"error"`
*Document
}
var out outer
if err := json.Unmarshal(bodyBytes, &out); err != nil {
// error handling
}
if out.HttpErr != nil {
// handle error json case
}
// Here you can use out.Document, probably worth check if it is nil first.
Runnable example

Golang unmarshal json that starts as an array

I'm trying to unmarshal this json https://www.reddit.com/r/videos/comments/3vgdsb/recruitment_2016.json
It starts as an array of two different objects, and I only need data on the second object.
I want to retrieve the comments body, it doesn't give me any error when i'm trying to decode it, but it doesn't capture the data I want.
This is the output I get from running this:
//Response struct when initialized: []
//Response struct decoded: [{{{[]}}} {{{[]}}}]
////
type Response []struct {
Parent struct {
Data struct {
Children []struct {
Com Comment
}
}
}
}
type Comment struct {
Name string `json:"body"`
}
func init() {
http.HandleFunc("/api/getcomments", getComments)
}
func getComments(w http.ResponseWriter, r *http.Request) {
url := "https://www.reddit.com/r/videos/comments/3vgdsb/recruitment_2016.json"
c := appengine.NewContext(r)
client := urlfetch.Client(c)
resp, err := client.Get(url)
if err != nil { fmt.Fprint(w, "Error client.Get(): ", err) }
re := new(Response)
fmt.Fprint(w, "Response struct: ", re, "\n")
errTwo := json.NewDecoder(resp.Body).Decode(&re)
if errTwo != nil { fmt.Fprint(w, "Error decoding: ", errTwo, "\n") }
fmt.Fprint(w, "Response struct: ", re)
}
For everyone that is struggling to create the right structure for JSON unmarshalling, here's a cool website that convert any JSON to the right Go struct: JSON-to-Go
The json data you are unmarshaling does not conform with your data, and if the names of the fields are not like in your struct, you should use struct tags as well. It should be more like this:
type Response []struct {
Kind string `json:"kind"`
Data struct {
Children []struct {
Data struct {
Replies []struct {
// whatever...
} `json:"replies"`
} `json:"data"`
} `json:"children"`
} `json:"data"`
}
}
Of course I'd replace the inline types with real, named types, but I'm just making a point here in regards to the data hierarchy.
Goddamn, that's some ugly bloated JSON BTW.

Golang decode 2 JSON items into 1 struct

i'm trying to decode 2 JSON items into the same struct, because the second JSON complet the first one, but it doesn't work (do nothing) have you got some ideas ?
func getUserClip(this *LibraryController, id string) (*Clip){
//Test Api
//Send Request to azure search
Data := Clip{}
if req := GetClipById("b373400a-bd7e-452a-af68-36992b0323a5"); req == nil {
return nil
} else {
str, err := req.String()
if err != nil {
beego.Debug("Error Json req.String: ", err)
}
//Uncode Json to string
if err := json.Unmarshal([]byte(str), &Data); err != nil {
beego.Debug("Error json", err)
}
for i := range Data.Value {
if req = GetCliRedis(Data.Value[i].Id); err != nil {
return nil
} else {
str, err := req.String()
beego.Debug("JSON REDIS DEBUG: ", str)
if err != nil {
beego.Debug("Error Json req.String: ", err)
}
if err := json.Unmarshal([]byte(str), &Data); err != nil {
beego.Debug("Error json", err)
}
}
i++
}
}
return &Data
}
and the struct
type Clip struct {
Value []InfoClip `json:value`
}
type InfoClip struct {
Id string `json:id`
CreatedAt time.Time `json:createdAt`
StartTimeCode int `json:startTimeCode`
EndTimeCode int `json:endTimeCode`
Metas metas `json:metas`
Tags []string `json:tags`
Categories []string `json:categories`
UserId string `json:userId`
SourceId string `json:sourceId`
ProviderName string `json:providerName`
ProviderReference string `json:providerReference`
PublicationStatus string `json:publicationStatus`
Name string `json:name`
FacebookPage string `json:facebookPage`
TwitterHandle string `json:twitterHandle`
PermaLinkUrl string `json:permalinkUrl`
Logo string `json:logo`
Link string `json:link`
Views int `json:views`
}
type metas struct {
Title string `json:title`
Tags []string `json:tags`
Categories []string `json:categories`
PermaLink string `json:permalink`
}
The JSON I receive is:
{
"clipId":"9b2ea9bb-e54b-4291-ba16-9211fa3c755f",
"streamUrl":"https://<edited out>/asset-32e43a5d-1500-80c3-cc6b-f1e4fe2b5c44\/6c53fbf5-dbe9-4617-9692-78e8d76a7b6e_H264_500kbps_AAC_und_ch2_128kbps.mp4?sv=2012-02-12&sr=c&si=17ed71e8-5176-4432-8092-ee64928a55f6&sig=KHyToRlqvwQxWZXVvRYOkBOBOF0SuBLVmKiGp4joBpw%3D&st=2015-05-18T13%3A32%3A41Z&se=2057-05-07T13%3A32%3A41Z",
"startTimecode":"6",
"endTimecode":"16",
"createdAt":"2015-05-19 13:31:32",
"metas":"{\"title\":\"Zapping : Obama, Marine Le Pen et Michael Jackson\",\"tags\":[\"actualite\"],\"categories\":[\"actualite\"],\"permalink\":\"http:\/\/videos.lexpress.fr\/actualite\/zapping-obama-marine-le-pen-et-michael-jackson_910357.html\"}",
"sourceId":"6c53fbf5-dbe9-4617-9692-78e8d76a7b6e",
"providerName":"dailymotion",
"providerReference":"x1xmnxq",
"publicationStatus":"1",
"userId":"b373400a-bd7e-452a-af68-36992b0323a5",
"name":"LEXPRESS.fr",
"facebookPage":"https:\/\/www.facebook.com\/LExpress",
"twitterHandle":"https:\/\/twitter.com\/lexpress",
"permalinkBaseURL":"https:\/\/tym.net\/fr\/{CLIP_ID}",
"logo":"lexpress-120px.png",
"link":"http:\/\/videos.lexpress.fr\/"
}
The Redis complet the azure search missing information :
here the struct :
type Clip struct {
Value []SearchClip `json:value`
}
type SearchClip struct {
Id string `json:id`
CreatedAt string`json:createdAt`
Tags []string `json:tags`
Categories []string `json:categories`
UserId string `json:userId`
SourceId string `json:sourceId`
Views int `json:views`
}
this is the basic information and redis complet this
I don't want to merge 2 struct into a third one i think it's not the better process, i will do it if it's the last solution.
For lack of activity I'm just gonna post the embedding option as a solution. It's probably the simplest way to do what you want.
type ClipInfoAndMeta struct {
Metas
InfoClip
}
Note I upper cased the name on metas not sure it's necessary but I believe it will be. The language feature being used here is called 'embedding' and it works a lot like composition except that the fields/methods for embedded types are more or less 'hoisted' to the containing types scope. IE with an instance of ClipInfoAndMeta you can directly access any exported field that is defined on InfoClip.
One oddity of your set up is that you'll have collisions on field names between the two types. Not sure how that would play out. With all this being said, it would be helpful to see the json string you're trying to Unmarshal from. As I've been writing this I realized that metas is just a subset of InfoClip. Which has confused me about what you are actually trying to do? I mean, if the data coming back is all in one object, it would mean InfoClip is sufficient for storing all of it. If that is the case you have no reason for the other object... And if you want to trim down the fields which get passed to the display layer of your app you should just define a method on the InfoClip type like func (i *InfoClip) GetMetas() Metas { return &Metas{ ... } } then you can just deal with the one type everywhere and hand off the Metas to the display layer when it's needed.
After a lot of trial and error, I present to you this fully functional solution:
package main
import (
"encoding/json"
"fmt"
"log"
"time"
)
type Clip struct {
Value []InfoClip `json:value`
}
type customTime struct {
time.Time
}
const ctLayout = "2006-01-02 15:04:05"
func (ct *customTime) UnmarshalJSON(b []byte) (err error) {
if b[0] == '"' && b[len(b)-1] == '"' {
b = b[1 : len(b)-1]
}
ct.Time, err = time.Parse(ctLayout, string(b))
return
}
type InfoClip struct {
Id string `json:"clipId"`
CreatedAt customTime `json:"createdAt"`
StartTimeCode string `json:"startTimeCode"` //if you want ints here, you'll have to decode manually, or fix the json beforehand
EndTimeCode string `json:"endTimeCode"` //same for this one
Metas metas `json:"-"`
MetasString string `json:"metas"`
Tags []string `json:"tags"`
Categories []string `json:"categories"`
UserId string `json:"userId"`
SourceId string `json:"sourceId"`
ProviderName string `json:"providerName"`
ProviderReference string `json:"providerReference"`
PublicationStatus string `json:"publicationStatus"`
Name string `json:"name"`
FacebookPage string `json:"facebookPage"`
TwitterHandle string `json:"twitterHandle"`
PermaLinkUrl string `json:"permalinkBaseURL"`
Logo string `json:"logo"`
Link string `json:"link"`
Views int `json:"views"`
}
type metas struct {
Title string `json:"title"`
Tags []string `json:"tags"`
Categories []string `json:"categories"`
PermaLink string `json:"permalink"`
}
var jsonString = `{
"clipId":"9b2ea9bb-e54b-4291-ba16-9211fa3c755f",
"streamUrl":"https://<edited out>/asset-32e43a5d-1500-80c3-cc6b-f1e4fe2b5c44\/6c53fbf5-dbe9-4617-9692-78e8d76a7b6e_H264_500kbps_AAC_und_ch2_128kbps.mp4?sv=2012-02-12&sr=c&si=17ed71e8-5176-4432-8092-ee64928a55f6&sig=KHyToRlqvwQxWZXVvRYOkBOBOF0SuBLVmKiGp4joBpw%3D&st=2015-05-18T13%3A32%3A41Z&se=2057-05-07T13%3A32%3A41Z",
"startTimecode":"6",
"endTimecode":"16",
"createdAt":"2015-05-19 13:31:32",
"metas":"{\"title\":\"Zapping : Obama, Marine Le Pen et Michael Jackson\",\"tags\":[\"actualite\"],\"categories\":[\"actualite\"],\"permalink\":\"http:\/\/videos.lexpress.fr\/actualite\/zapping-obama-marine-le-pen-et-michael-jackson_910357.html\"}",
"sourceId":"6c53fbf5-dbe9-4617-9692-78e8d76a7b6e",
"providerName":"dailymotion",
"providerReference":"x1xmnxq",
"publicationStatus":"1",
"userId":"b373400a-bd7e-452a-af68-36992b0323a5",
"name":"LEXPRESS.fr",
"facebookPage":"https:\/\/www.facebook.com\/LExpress",
"twitterHandle":"https:\/\/twitter.com\/lexpress",
"permalinkBaseURL":"https:\/\/tym.net\/fr\/{CLIP_ID}",
"logo":"lexpress-120px.png",
"link":"http:\/\/videos.lexpress.fr\/"
}`
func main() {
res := parseJson(jsonString)
fmt.Printf("%+v\n",res)
}
func parseJson(theJson string) InfoClip {
toParseInto := struct {
InfoClip
MetasString string `json:"metas"`
}{
InfoClip: InfoClip{},
MetasString: ""}
err := json.Unmarshal([]byte(jsonString), &toParseInto)
if err != nil {
log.Panic(err)
}
err = json.Unmarshal([]byte(toParseInto.MetasString), &toParseInto.InfoClip.Metas)
if err != nil {
log.Panic(err)
}
return toParseInto.InfoClip
}
What are we doing in the parseJson function?
We create a new struct and assign that to the toParseInto variable. We design the struct in a way that it contains all of the fields from InfoClip via embedding, and We add a field to temporarily hold the JSON string metas.
We then unmarshal into that struct, which, after fixing the issues listed below, works fine.
After that, we unmarshal that inner JSON into the correct field in the embedded InfoClip.
We can now easily return that embedded InfoClip to get what we really wanted.
Now, all the issues I have identified in your original solution:
The time format in your JSON is not the standard time format to be used in JSON. That is defined in some RFC, but anyways: because of that, we have to use our own type customTime to parse that. It handles just like a normal time.Time, because that is embedded within.
All your json tags were wrong. All of them had missing quotes, and some were just not even correct.
startTimeCode and endTimeCode are strings in the JSON, not ints
Left to you to improve:
Error handling: Don't just panic in the parseJson function, but rather return the error somehow
If you want startTimecode and endTimecode to be available as ints, parse them manually. You can employ a "hack" similar to the one I used to parse the inner JSON.
One final note, not related to this answer but rather to your question: If you had provided both your code and the JSON with your original question, you would have had an answer in probably less than an hour. Please, please don't make this harder than it needs to be.
EDIT: I forgot to provide my sources, I used this question to parse your time format.