Go Unmarshal JSON, but unmarshal nested structure as a string - json

Given the following JSON object:
{
"a": 1,
"b": [1,2,3,4]
}
And the following type:
type Thing struct {
A Int `json:"a"`
B string `json:"b"
}
I would like the Array "b" to stay as a JSON string when marshalled into go.
I currently get the following error:
panic: json: cannot unmarshal array into Go struct field Thing.b of type string

Set the field as a json.RawMessage. It'll be stored as is, without interpretation (ie. as "[1,2,3,4]"), as a slice of bytes, which can be converted to a string easily enough.
If you need a string directly, you'll have to implement the json.Unmarshaler interface on your type and do the conversion yourself.

Related

Golang struct with different types and JSON marshalling

I am new to Go and I feel as though I grappling with something fundamental around typing whilst trying to build a JSON payload.
My http web app (a kubernetes mutating webhook) is returning a JSON []byte(string) in a field admissionResponse.Patch
The JSON returned is an array of JSONPatch and looks something like this:
[{
"op": "add",
"path": "/spec/containers",
"value": <SPEC-JSON>
}]
The problem I have is trying to build the different JSONPatch objects in a generic way.
I am Unmarshalling my <SPEC-JSON> from yaml files using various k8s v1 types (E.g. v1.Volume, v1.Container) which is ok, but then because each <SPEC-JSON> is of different type for different JSONPatch objects, I am having to create different struct{} types like so;
type ContainerPatch struct {
Op string `json:"op"`
Path string `json:"path"`
Value []v1.Container `json:"value"`
}
type VolumePatch struct {
Op string `json:"op"`
Path string `json:"path"`
Value []v1.Volume `json:"value"`
}
Now, when I want to put these into an array ready to Marshal back to a JSON string, they are of different types so I am ending up doing:
myPatch := []interface{}{
VolumePatch(PatchVolumes(pod)),
ContainerPatch(PatchContainers(pod)),
ContainerPatch(PatchInitContainers(pod)),
}
data, _ := json.Marshal(myPatch)
which works, but seems wrong? Essentially these are all of type JSONPatch where the json:"value" can be different k8s objects.
How should we do these things in Go?
---update--- (thanks to mkopriva)
Thank you. Reading around a bit, it seems that any is essentially an (empty) interface{}(?). So I tried with both;
type Patch struct {
Op string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value"`
}
and
type Patch struct {
Op string `json:"op"`
Path string `json:"path"`
Value any `json:"value"`
}
... and both work ok. I like this way as it allows me to have a single patching func() that returns a common type.

how to create user defined struct for array of different user defined object in golang

I am trying to parse json object in golang.
Below is the json structure :
{
"properties": {
"alertType": "VM_EICAR",
"resourceIdentifiers": [{
"azureResourceId": "/Microsoft.Compute/virtualMachines/vm1",
"type": "AzureResource"
},
{
"workspaceId": "f419f624-acad-4d89-b86d-f62fa387f019",
"workspaceSubscriptionId": "20ff7fc3-e762-44dd-bd96-b71116dcdc23",
"workspaceResourceGroup": "myRg1",
"agentId": "75724a01-f021-4aa8-9ec2-329792373e6e",
"type": "LogAnalytics"
}
],
"vendorName": "Microsoft",
"status": "New"
}
}
I have below user defined types.
type AzureResourceIdentifier struct {
AzureResourceId string `json:"azureResourceId"`
Type string `json:"type"`
}
type LogAnalyticsIdentifier struct{
AgentId string `json:"agentId"`
Type string `json:"type"`
WorkspaceId string `json:"workspaceId"`
WorkspaceResourceGroup string `json:"workspaceResourceGroup"`
WorkspaceSubscriptionId string `json:"workspaceSubscriptionId"`
}
I have a top level user defined type as properties as below.
it has resourceIdentifiers as array of two other user defined types(defined above),
AzureResourceIdentifier
LogAnalyticsIdentifier
how can I define type of resourceIdentifiers in properties ?
type Properties struct{
alertType string
resourceIdentifiers ??? `
vendorName string `
status string
}
Note: I have a existing connector and we are provisioned to input the struct of the parsing json, we cannot override or add any function.
There are a couple of options here.
[]map[string]interface{}
Using a map will allow any JSON object in the array to be parsed, but burdens you with having to safely extract information after the fact.
[]interface{}
Each is going to be a map under the hood, unless non-JSON object elements can also be in the JSON array. No reason to use it over map[string]interface{} in your case.
A slice of a new struct type which covers all possible fields: []ResourceIdentifier
type ResourceIdentifier struct {
AzureResourceId string `json:"azureResourceId"`
AgentId string `json:"agentId"`
WorkspaceId string `json:"workspaceId"`
WorkspaceResourceGroup string `json:"workspaceResourceGroup"`
WorkspaceSubscriptionId string `json:"workspaceSubscriptionId"`
Type string `json:"type"`
}
This is probably the laziest solution. It allows you to get where you want to quickly, at the cost of wasting some memory and creating a non self-explanatory data design which might cause confusion if later trying to serialize with it again.
A new struct type + custom unmarshalling implementation.
type ResourceIdentifiers struct {
AzureResourceIdentifiers []AzureResourceIdentifier
LogAnalyticsIdentifiers []LogAnalyticsIdentifier
}
Implement json.Unmarshaler to decide which type of struct to construct and in which slice to put it.

Why is Golang json.Unmarshall not working with "e" and "E" properties?

Suppose we want to unmarshal the JSON string {"e": "foo", "E": 1}.
Unmarshalling using the type messageUppercaseE works like expected. When using the type message though, the error json: cannot unmarshal number into Go struct field message.e of type string is returned.
Why are we not able to unmarshal the JSON, if only the "e" struct tag is present?
How would I be able to unmarshal the JSON? (I know that I am able to do this via Jeffail/gabs, but would like to stick to the type based approach.)
type message struct {
EventType string `json:"e"`
}
type messageUppercaseE struct {
EventType string `json:"e"`
UppercaseE uint64 `json:"E"`
}
Try it yourself at https://play.golang.org/p/T6KMJRLy7TN
Quoting the docs for unmarshal:
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.
In this case, it is the case-insensitive match that causes the trouble.

Handling two forms of JSON?

I'm writing an application in Go that will recieve two forms of JSON:
Example 1:
{"book_data":{"title":"book-title","page_number":457}}
Example 2:
{"book_data":{"collection":214},"books":{"data":[{"title":"book-title","page_number":457},{"title":"book-title","page_number":354}]}}
I thought that I could create a struct like the following and unmarshal JSON into it:
type Book struct {
Title string `json:"title"`
PageNumber int `json:"page_number"`
}
but that only works for the first example.
How can I handle JSON from both examples?
You can first unmarshal partly in json.RawMessage to next decide depending of unmarshalled payload. And you also can just unmarshal in more generic structure. Something like
type Book struct {
Title string `json:"title"`
PageNumber int `json:"page_number"`
}
type BookShelf struct {
BookData struct {
Book
Collection int `json:"collection"`
} `json:"book_data"`
Books struct {
Data []Book `json:"data"`
} `json:"books"`
}
which for me looks readable, meaningful and handy enough for further processing.
Why not unmarshal to a map[string]interface{} and then use the result to see which form you need to handle? You can then deserialize with a specific struct type for each form.
Another way would be to use the following package to check for differing attributes, so you can decide which struct to use for the real unmarshaling.
https://github.com/go-xmlpath/xmlpath/tree/v2
You can unmarshal to map because your key is string and value may be anything like - map[string]interface{}. If you are not sure any data type or value then use interface{} beacause it can store any value. Then use result to see which form it is, And deserialize to specific struct type.
Another way to convert JSON to go struct is use this tool.
https://mholt.github.io/json-to-go/

Unmarshal JSON into Golang type Big.Float

I have a json message that needs to be unmarshaled to a struct which has some big.Float fields from the math package. The json field is of type numeric. It gives me err = json: cannot unmarshal string into Go value of type *big.Float.
I wonder why it complains "cannot unmarshal string" since my json field is numeric type. And what do I need to do to unmarshal the json filed to a *big.Float field.
Example:
type Msg struct {
Usage0 *big.Float
Usage1 *big.Float
Usage2 *big.Float
}
// jsonMsg = {'Usage0': 31241.4543, "Usage1": 54354325423.65, ...}
err := json.Unmarshal(jsonMsg, &msg)
It appears to me (based on the docs) that it expects the json for big.Float to be passed in as strings. This play proves that to work:
https://play.golang.org/p/7XKn2hhXRD
If you cannot control the json then you can implement your own unmarshaller as an alternative.