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.
CodePudding user response:
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 interface type custom unmarshalling implementation.
type ResourceIdentifier interface {}
Using a custom interface communicates that there probably exists custom unmarshalling logic. The interface doesn't actually need to require any methods. In fact, that benefits us because we can unmarshal straight into it once, then "refine" the contained fields.
func (properties *Properties) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, properties)
if err != nil {
return err
}
for i := 0; i < len(properties.ResourceIdentifiers); i {
resourceIdentifier, ok := properties.ResourceIdentifiers[i].(map[string]interface)
if !ok {
return fmt.Errorf("non-JSON object in []ResourceIdentifiers")
}
// TODO:
// 1. Marshal resourceIdentifier again
// 2. Switch-statement on resourceIdentifier["Type"]
// - Get concrete value by unmarshaling into it.
// - Set value of properties.ResourceIdentifiers[i] to concrete value.
}
return nil
}
The code which uses the result will still have to do type assertions; there's no sensible way around that. If nothing else it's a good exercise. If possible I would however try to change the JSON format to not mix types in a JSON array first.