Using struct with API call in Go - json

I'm new to Go and working hard to follow its style and I'm not sure how to proceed.
I want to push a JSON object to a Geckoboard leaderboard, which I believe requires the following format based on the API doc and the one for leaderboards specifically:
{
"api_key": "222f66ab58130a8ece8ccd7be57f12e2",
"data": {
"item": [
{ "label": "Bob", "value": 4, "previous_value": 6 },
{ "label": "Alice", "value": 3, "previous_value": 4 }
]
}
}
My instinct is to build a struct for the API call itself and another called Contestants, which will be nested under item. In order to use json.Marshall(Contestant1), the naming convention of my variables would not meet fmt's expectations:
// Contestant structure to nest into the API call
type Contestant struct {
label string
value int8
previous_rank int8
}
This feels incorrect. How should I configure my Contestant objects and be able to marshall them into JSON without breaking convention?

To output a proper JSON object from a structure, you have to export the fields of this structure. To do it, just capitalize the first letter of the field.
Then you can add some kind of annotations, to tell your program how to name your JSON fields :
type Contestant struct {
Label string `json:"label"`
Value int8 `json:"value"`
PreviousRank int8 `json:"previous_rank"`
}

Related

Parsing complex JSON, again

I have some JSON that get via an API call and I want to now parse this using JSON, I followed an online tutorial in how to parse JSON using structs, but my actual JSON is a lot more complex than the one they used. Here is an example of the JSON I have:
{
"metadata": {},
"items": [
{
"metadata": {
"name": "run7",
"namespace": "default",
"uid": "e218fcc4",
"creationTimestamp": "2022-01-01T00:00:00Z"
},
"spec": {
"arguments": {}
},
"status": {
"phase": "Succeeded",
"startedAt": "2022-01-01T00:00:00Z",
"finishedAt": "2022-01-01T00:00:00Z"
}
}
]
}
and here is the strucs that I created for it:
type wfSpec struct{
Arguments string
}
type wfStatus struct {
Phase string
StartedAt string
FinishedAt string
}
type wfMetadata struct {
Name string
Namespace string
Uid string
CreationTimestamp string
}
type Metadata []struct {
Data string
}
type Items []struct {
wfMetadata
wfStatus
wfSpec
}
type Workflow struct {
Metadata Metadata
Items Items
}
When I first tried to print a value using fmt.Printf(workflows.Items.wfMetadata.Name) I got the error workflows.Items.Metadata undefined (type Items has no field or method Metadata)so then I tried to just print the whole thing using fmt.Printf(workflows) and I got this error cannot use workflows (type Workflow) as type string in argument to fmt.Printf
The only data I need to parse from the JSON is the
"name": "run7",
"namespace": "default",
"uid": "e218fcc4",
"creationTimestamp": "2022-01-01T00:00:00Z"
First off
The problem I expect you're having is not using the tags. To parse a JSON the names of the structs must match the names in the JSON fields. Read here Golang Marshal
Secondly wfMetadata has a lowecase first letter, meaning it will not be imported.
Thirdly, workflow.metadata and workflow.items[i].spec.arguments is set as a {} and not the emptystring "". I assume they're not supposed to be string. This can be avoided using the open interface{} if you don't know or care, or actually implementing them using the official documentations from the API you're connecting to.
As a note, using []struct seems wrong to me. Instead define it in the usage
Note, by using an IDE like GoLand from jetbrains they first off support converting JSON to a struct by simply pasting the JSON into a .go file. They might be daunting at first but do help a lot, and would do much of this for you in seconds.
Now try this instead, and understand why and how this is better and working.
type Status struct {
Phase string `json:"phase"`
StartedAt string `json:"startedAt"`
FinishedAt string `json:"finishedAt"`
}
type ItemMetadata struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
UID string `json:"uid"`
CreationTimestamp string `json:"creationTimestamp"`
}
type Items struct {
Metadata ItemMetadata `json:"metadata"`
Status Status `json:"status"`
Spec interface{} `json:"spec"`
}
type Workflow struct {
Metadata interface{} `json:"metadata"`
Items []Items `json:"items"`
}
Working example in playground https://go.dev/play/p/d9rT4FZJsGv

AWS AppSync: How to return valid JSON via DynamoDB

I have an AppSync GraphQL API that makes a Query to a DynamoDB and returns a JSON String, however in my Response Mapping Template I use the built-in $util.parseJson() function as listed here - but I'm still returned a JSON string in the Query window and when requesting the data in my React app.
Schema file, I have an ordinary ID & Address field that is of type AWSJSON.
type Venue {
id: ID!
address: AWSJSON
}
When running a mutation, I usually run the address object through a quick JSON.stringify(addressObj) and that formats the object as a string with the \"\" escaped, meaning that it can be inserted into DynamoDB.
Request Mapping template
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.id),
}
}
Response Mapping template
#set($result = $ctx.result)
## address - parse back to JSON
#set($result.address = $util.parseJson($ctx.result.address))
## Return the result
$util.toJson($result)
The idea to create a new variable and then assign the value to the parseJSON value was taken from How return JSON object from DynamoDB with appsync?. So, as seen below, I am parsing the value through what seems to be the correct method to turn it from stringified JSON, to an object - but it doesn't appear to work.
The current response:
{
"data": {
"getVenue": {
"id": "31538150",
"address": "{\"lng\":-1.54511300000001,\"postcode\":\"LS1 5DL\",\"short\":\"New Station St., LS1\",\"lat\":53.795231,\"full\":\"16 New Station St, Leeds LS1 5DL, UK\"}"
}
}
}
Whereas the response that I am wanting is...
{
"data": {
"getVenue": {
"id": "31538150",
"address": { "lng": -1.54511300000001, "postcode": "LS1 5DL", "short": "New Station St., LS1", "lat": 53.795231, "full": "16 New Station St, Leeds LS1 5DL, UK" }
}
}
}
Any help is greatly appreciated!
Old question but thought I would add some notes...
AWSJSON is a string value that will be parsed into DynamoDB as JSON and stringified again on fetching.
So AppSync expects a string (stringified JSON) for inputs and will return a string that can be parsed with JSON.parse.
However this data is parsed before storing in DynamoDB. So of you query DynamoDB separately than through AppSync then you can query it like it was an object. Same with inputing directly into DynamoDB.
The only way to get JSON in the AppSync result is to define each and every field in GraphQL. Sometimes this can be achieved by restructuring data. For example instead of storing:
{
bob: { age: 34 },
igor: { age: 124 }
}
Which would need to be stringified as an AWSJSON field. It can however be restructured like this:
[{
name: 'bob',
age: 34
}, {
name: 'igor',
age: 123
}]
Which can be defined in GraphQL as something like this:
type User {
age: Int,
name: String
}
So that one can now extract specific value such as age from the GraphQL without involving and JSON parse/stringify.

Assign proper types to data read from JSON

I have a struct such as this one:
type Data struct {
Id string
Value string
Custom customtype1
Special customtype2
TimeStamp Time
}
var model Data
I am reading data from a JSON object. Because the JSON is structured very differently, I can't just directly unmarshall the JSON into the struct. So I am trying to "match" the fields from the JSON objects to those of the struct one by one. I don't actually need to properly unmarshall the JSON data into the struct, all I really need is to be able to assign, for each field, the proper type to its value.
So I unmarshall the JSON to a generic interface, then convert it to a map[string]interface{} and iterate over that. For each field, I try to find a match among the field names in the model variable which I get using reflect.
Now this all works fine, but the problem arises when I try to get the right type for the values.
I can get the Type for a certain field from the model using reflect, but then I can't use that to cast the type of the value I get from the JSON because that is not a type. I can't use a switch statement either, because this is a simplified version of the situation and in reality I'm dealing with 1000+ different possible types. How can I convert the values I have for each field into their proper type ?
The only I can think of solving this would be to recreate a json string that matches the format of the struct and then unmarshall that into its proper struct, but that seems way to convoluted. Surely there must be a simpler way?
Here's a sample JSON (I can not change this structure, unless I rework it within my Go program):
{
"requestId": 101901,
"userName": "test",
"options": [1, 4],
"request": {
"timeStamp": {
"Value1": "11/02/2018",
"Value2": "11/03/2018"
},
"id": {
"Value1": "123abcd",
"Value2": "0987acd",
"Value3": "a9c003"
},
"custom": {
"Value1": "customtype1_value",
"Value2": "customtype1_value"
}
}
}
I'd advise against your current approach. You haven't provided enough context to tell us why you're choosing to unmarshall things one by one, but Go's support for JSON is good enough that I'd guess it is capable of doing what you want.
Are you aware of Marshall's support for struct tags? Those might serve the purpose you're looking for. Your struct would then look something more like:
type Data struct {
Id string `json:"id"`
Value string `json:"value"`
Custom customtype1 `json:"custom_type"`
Special customtype2 `json:"special_type"`
TimeStamp Time `json:"timestamp"`
}
If your problem is that the custom types don't know how to be unmarshalled, you can define custom unmarshalling functions for them.
This would then enable you to unmarshall an object like the following:
{
"id": "foo",
"value": "bar",
"custom_type": "2342-5234-4b24-b23a",
"special_type": "af23-af2f-rb32-ba23",
"timestamp": "2018-05-01 12:03:41"
}

Dynamically define structs from JSON file in Go

I want to dynamically define structs in a Go project based on a JSON file.
For example, if I had a json file like so...
{
"date": "today",
"time": 12,
"era": "never",
"alive": true
}
Then I would expect a struct to be generated (that would look) like this (but not explicitly defined in the source code)...
type DynamicJSON struct {
date, era string
time int
alive bool
}
Furthermore, I want to nest JSON objects such that I could do something like this...
{
"date": "today",
"time": 12,
"era": "never",
"alive": true,
"nested": {
"date": "tomorrow",
"alive": true
}
}
...which would actually generate two different structs, like this...
type DynamicJSON1 struct {
date, era string
time int
alive bool
}
type DynamicJSON2 struct {
date string
alive bool
}
Is this something that is currently supported?
I can't garantantee final result, but easyjson does exactly what you asking.
easyjson aims to keep generated Go code simple enough so that it can
be easily optimized or fixed. Another goal is to provide users with
the ability to customize the generated code by providing options not
available with the standard encoding/json package, such as generating
"snake_case" names or enabling omitempty behavior by default.

Marshal dynamic JSON field tags in Go

I'm trying to generate JSON for a Terraform file. Because I (think I) want to use marshalling instead of rolling my own JSON, I'm using Terraforms JSON format instead of the 'native' TF format.
{
"resource": [
{
"aws_instance": {
"web1": {
"some": "data"
}
}]
}
resource and aws_instance are static identifiers while web1 in this case is the random name. Also it wouldn't be unthinkable to also have web2 and web3.
type Resource struct {
AwsResource AwsResource `json:"aws_instance,omitempty"`
}
type AwsResource struct {
AwsWebInstance AwsWebInstance `json:"web1,omitempty"`
}
The problem however; how do do I generate random/variable JSON keys with Go's field tags?
I have a feeling the answer is "You don't". What other alternatives do I have then?
In most cases where there are names not known at compile time, a map can be used:
type Resource struct {
AWSInstance map[string]AWSInstance `json:"aws_instance"`
}
type AWSInstance struct {
AMI string `json:"ami"`
Count int `json:"count"`
SourceDestCheck bool `json:"source_dest_check"`
// ... and so on
}
Here's an example showing how to construct the value for marshalling:
r := Resource{
AWSInstance: map[string]AWSInstance{
"web1": AWSInstance{
AMI: "qdx",
Count: 2,
},
},
}
playground example