Home > Blockchain >  Unmarshal JSON when only half of the key is known
Unmarshal JSON when only half of the key is known

Time:01-04

Is it possible to unmarshal JSON response when you only know half of the key name?

Example:

data := []byte(`[{"animal_name": "Goofy", "location": "Europe"},  {"person_name": "Gigo", "location": "Asia"}]`)

In this case, there are two possibilities, key name starts with some sort of an identifier (animal, person) and always ends with _key. There can be more (random) identifiers.

In python, you could try to retrieve the key value with endswith or something like that. But is possible to do so in go?

https://go.dev/play/p/HGQ7qFgehve.go

CodePudding user response:

You can unmarshal the JSON to a slice of maps and then extract whatever you need from the slice:

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

type Entry struct {
    Animalname string `json:"animal_name"`
    Location   string
}

func main() {
    data := []byte(`[{"animal_name": "Goofy", "location": "Europe"},  {"person_name": "Gigo", "location": "Asia"}]`)
    // json.Unmarshal will initialize it as a slice of maps: []map[string]string.
    // Alternatively, you can set the type explicitly:
    // var entries []map[string]string
    var entries interface{}
    err := json.Unmarshal(data, &entries)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(entries)
}

CodePudding user response:

You can implement the json.Unmarshaler interface on a slice of interfaces. Then in this custom function use some logic to determine which struct type should be used. I used json.RawMessage to avoid full unmarshalling of the data until we know which type to use.

(playground link)

package main

import (
    "encoding/json"
    "strings"

    "github.com/davecgh/go-spew/spew"
)

type EntitySlice []Entity

func (es *EntitySlice) UnmarshalJSON(bytes []byte) error {
    var objSlice []json.RawMessage
    err := json.Unmarshal(bytes, &objSlice)
    if err != nil {
        return err
    }

    for _, obj := range objSlice {
        kv := make(map[string]json.RawMessage)
        err = json.Unmarshal(obj, &kv)
        if err != nil {
            return err
        }

        var entityType string
        for k := range kv {
            i := strings.Index(k, "_name")
            if i != -1 {
                entityType = k[:i]
                break
            }
        }

        var e Entity
        switch entityType {
        case "person":
            e = &Person{}
        case "animal":
            e = &Animal{}
        }

        err = json.Unmarshal(obj, &e)
        if err != nil {
            return err
        }

        *es = append(*es, e)
    }

    return nil
}

type Entity interface {
    EntityMarker()
}

type Person struct {
    Name     string
    Location string
}

// Just so we implement Entity
func (p *Person) EntityMarker() {}

type Animal struct {
    Name     string
    Location string
}

// Just so we implement Entity
func (a *Animal) EntityMarker() {}

func main() {
    data := []byte(`[{"animal_name": "Goofy", "location": "Europe"},  {"person_name": "Gigo", "location": "Asia"}]`)

    var entitySlice EntitySlice
    err := json.Unmarshal(data, &entitySlice)
    if err != nil {
        spew.Dump(err)
    }
    spew.Dump(entitySlice)
}
  •  Tags:  
  • Related