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

Time:02-23

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.

  1. []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.

  1. []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.

  1. 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.

  1. 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.

  • Related