如何在Go中构建抽象的json解组器

如何在Go中构建抽象的json解组器

问题描述:

I have multiple APIs that follow a similar structure on the high level response. It always gives back an answer in that form:

{"data": {"feed":[{...}]}, "success": true}

However, the structure in Feed varies, depending on the concrete API.

I would now like to build an abstract function to process the various APIs. I have the following objects:

type SourceDTO struct { // top level object
    Success bool       `json:"success"`
    Data    Feed       `json:"data"`
}

type Feed struct {
    FeedData []<???> `json:"Feed"`
}

(The real object is more complex, but this shows the idea)

How would be a good way in go to parse this for the different APIs, ut having some common code with some logic based on the high level data (e.g. success)?

EDIT: I am extending this, to explain more the extend of my question about the "pattern" I am looking for.

I want to create this package that parses the group of APIs. The DTO objects then have to be transferred into some other objects. These 'final' objects are defined in a different package (the entity package) and have then to be persisted.
I am now wondering, how to bring all this together: The 'finaly' entity objects, the transformation functions from DTO to entity, the parsing of the different APIs and their common and different result components.
Where do the transformation functions belong to (package wise)?

EDIT2: Specified FeedData to a slice after digging into the problem (see comments)

Thanks to @mkopriva for the input for this solution.

In order to have some abstraction in your json unmarshalling it is possible to use interface{} for many use cases.

package main

import (
    "encoding/json"
    "fmt"
)

type UniversalDTO struct {
    Success bool        `json:"success"`
    Data    interface{} `json:"data"`
}

type ConcreteData struct {
    Source string `json:"source"`
    Site   string `json:"site"`
}

func main() {
    jsondata := []byte(`{"sucess":"true","data":[{"source":"foo","site":"bar"}]}`)

    data := make([]ConcreteData, 0, 10)
    dtoToSend := UniversalDTO{Data: &data}

    describe(dtoToSend)
    describe(dtoToSend.Data)

    json.Unmarshal(jsondata, &dtoToSend)

    describe(dtoToSend)
    describe(dtoToSend.Data)
}

func describe(i interface{}) {
    fmt.Printf("(%v, %T)
", i, i)
}

Test here: https://play.golang.org/p/SSSp_zptMVN

json.Unmarshal expects an object into which the json is being put into. Thus, first we always need an object. Depending on the concrete instance of the target object, the interface{} can be overriden with a concrete struct object (which of course has to be created separately). An important learning here is, that a go interface can also be overridden with a slice. In this way, it is also possible to unmarshal an array into a go object. However, a slice of a struct has to be defined as a slice of pointers to that type.

You can embed your SourceDTO struct into another struct, like this:

type SourceDTO struct { // top level object
    Success bool       `json:"success"`
}

type FeedResponse struct {
    FeedData YourCustomFeedStruct `json:"feed"`

    // Embedded Struct
    SourceDTO
}

Now you can access the Success bool from the FeedResponse struct. Also any methods defined on the SourceDTO struct can be accessed from the FeedResponse.