Home > Mobile >  How to unmarshal JSOn with an array of different types
How to unmarshal JSOn with an array of different types

Time:04-07

I have JSON like this that I need to parse into a golang type:

{
    name: "something"
    rules: [
    {
      "itemTypeBasedConditions": [["containsAny", ["first_match", "second_match"]]],
      "validity": "INVALID"
    }]
}

The problem is that each array of the array in itemTypeBasedConditions contains a mix of strings (always first element) and another array (second element), and I am not sure how to parse all of that into an object that I could then manipulate.

I got to:

type RulesFile struct {
    Name string
    Rules []RulesItem
}

type RulesItem struct {
    itemTypeBasedConditions [][]interface{}
    validity bool
}

And then I guess I have to convert elements one by one from interface{} to either string (containsAny) or an array of strings ("first_match", "second_match")

Is there a better way of approaching this JSON parsing?

CodePudding user response:

I would do something like this, you can probably alter this to your needs.

package main

import (
    "encoding/json"
    "fmt"
    "os"
    "reflect"
)

type RulesFile struct {
    Name  string      `json:"name"`
    Rules []RulesItem `json:"rules"`
}

type RulesItem struct {
    ItemTypeBasedConditions [][]Condition `json:"itemTypeBasedConditions"`
    Validity                bool          `json:"validity"`
}

type Condition struct {
    Value *string
    Array *[]string
}

func (c Condition) String() string {
    if c.Value != nil {
        return *c.Value
    }

    return fmt.Sprintf("%v", *c.Array)
}

func (c *Condition) UnmarshalJSON(data []byte) error {

    var y interface{}

    err := json.Unmarshal(data, &y)
    if err != nil {
        return err
    }
    switch reflect.TypeOf(y).String() {
    case "string":
        val := fmt.Sprintf("%v", y)
        c.Value = &val
        return nil
    case "[]interface {}":
        temp := y.([]interface{})
        a := make([]string, len(temp))
        for i, v := range temp {
            a[i] = fmt.Sprint(v)
        }
        c.Array = &a
        return nil
    }

    return fmt.Errorf("cannot unmarshall into string or []string: %v", y)
}

var input string = `
{
    "name": "something",
    "rules": [
        {
            "itemTypeBasedConditions": [["containsAny",["first_match", "second_match"]]],
            "validity": false
        }
    ]
}`

func main() {
    var ruleFile RulesFile
    err := json.Unmarshal([]byte(input), &ruleFile)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    fmt.Printf("% v\n", ruleFile)
}

CodePudding user response:

You can implement the json.Unmarshaler interface. Have that implementation first unmarshal the json into a slice of json.RawMessage, then, once you've done that, you can unmarshal the individual elements to their corresponding types.

type Cond struct {
    Name string
    Args []string
}

func (c *Cond) UnmarshalJSON(data []byte) error {
    // unmarshal into a slice of raw json
    var raw []json.RawMessage
    if err := json.Unmarshal(data, &raw); err != nil {
        return err
    } else if len(raw) != 2 {
        return errors.New("unsupported number of elements in condition")
    }

    // unmarshal the first raw json element into a string
    if err := json.Unmarshal(raw[0], &c.Name); err != nil {
        return err
    }

    // unmarshal the second raw json element into a slice of string
    return json.Unmarshal(raw[1], &c.Args)
}

https://go.dev/play/p/-tbr73TvX0d

  •  Tags:  
  • go
  • Related