зеркало из https://github.com/Azure/aks-engine.git
Create a --set flag for generate (#2787)
This commit is contained in:
Родитель
914172d3cd
Коммит
ca9b3f1cad
|
@ -30,6 +30,7 @@ type generateCmd struct {
|
|||
classicMode bool
|
||||
noPrettyPrint bool
|
||||
parametersOnly bool
|
||||
set []string
|
||||
|
||||
// derived
|
||||
containerService *api.ContainerService
|
||||
|
@ -48,6 +49,15 @@ func newGenerateCmd() *cobra.Command {
|
|||
if err := gc.validate(cmd, args); err != nil {
|
||||
log.Fatalf(fmt.Sprintf("error validating generateCmd: %s", err.Error()))
|
||||
}
|
||||
|
||||
if err := gc.mergeAPIModel(); err != nil {
|
||||
log.Fatalf(fmt.Sprintf("error merging API model in generateCmd: %s", err.Error()))
|
||||
}
|
||||
|
||||
if err := gc.loadAPIModel(cmd, args); err != nil {
|
||||
log.Fatalf(fmt.Sprintf("error loading API model in generateCmd: %s", err.Error()))
|
||||
}
|
||||
|
||||
return gc.run()
|
||||
},
|
||||
}
|
||||
|
@ -57,6 +67,7 @@ func newGenerateCmd() *cobra.Command {
|
|||
f.StringVar(&gc.outputDirectory, "output-directory", "", "output directory (derived from FQDN if absent)")
|
||||
f.StringVar(&gc.caCertificatePath, "ca-certificate-path", "", "path to the CA certificate to use for Kubernetes PKI assets")
|
||||
f.StringVar(&gc.caPrivateKeyPath, "ca-private-key-path", "", "path to the CA private key to use for Kubernetes PKI assets")
|
||||
f.StringArrayVar(&gc.set, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
|
||||
f.BoolVar(&gc.classicMode, "classic-mode", false, "enable classic parameters and outputs")
|
||||
f.BoolVar(&gc.noPrettyPrint, "no-pretty-print", false, "skip pretty printing the output")
|
||||
f.BoolVar(&gc.parametersOnly, "parameters-only", false, "only output parameters files")
|
||||
|
@ -65,8 +76,6 @@ func newGenerateCmd() *cobra.Command {
|
|||
}
|
||||
|
||||
func (gc *generateCmd) validate(cmd *cobra.Command, args []string) error {
|
||||
var caCertificateBytes []byte
|
||||
var caKeyBytes []byte
|
||||
var err error
|
||||
|
||||
gc.locale, err = i18n.LoadTranslations()
|
||||
|
@ -90,6 +99,34 @@ func (gc *generateCmd) validate(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf(fmt.Sprintf("specified api model does not exist (%s)", gc.apimodelPath))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gc *generateCmd) mergeAPIModel() error {
|
||||
var err error
|
||||
|
||||
// if --set flag has been used
|
||||
if gc.set != nil && len(gc.set) > 0 {
|
||||
m := make(map[string]transform.APIModelValue)
|
||||
transform.MapValues(m, gc.set)
|
||||
|
||||
// overrides the api model and generates a new file
|
||||
gc.apimodelPath, err = transform.MergeValuesWithAPIModel(gc.apimodelPath, m)
|
||||
if err != nil {
|
||||
return fmt.Errorf(fmt.Sprintf("error merging --set values with the api model: %s", err.Error()))
|
||||
}
|
||||
|
||||
log.Infoln(fmt.Sprintf("new api model file has been generated during merge: %s", gc.apimodelPath))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gc *generateCmd) loadAPIModel(cmd *cobra.Command, args []string) error {
|
||||
var caCertificateBytes []byte
|
||||
var caKeyBytes []byte
|
||||
var err error
|
||||
|
||||
apiloader := &api.Apiloader{
|
||||
Translator: &i18n.Translator{
|
||||
Locale: gc.locale,
|
||||
|
@ -128,6 +165,7 @@ func (gc *generateCmd) validate(cmd *cobra.Command, args []string) error {
|
|||
prop.CertificateProfile.CaCertificate = string(caCertificateBytes)
|
||||
prop.CertificateProfile.CaPrivateKey = string(caKeyBytes)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -7,9 +7,7 @@ import (
|
|||
)
|
||||
|
||||
func TestGenerateCmdValidate(t *testing.T) {
|
||||
|
||||
g := &generateCmd{}
|
||||
|
||||
r := &cobra.Command{}
|
||||
|
||||
// validate cmd with 1 arg
|
||||
|
@ -37,3 +35,51 @@ func TestGenerateCmdValidate(t *testing.T) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGenerateCmdMergeAPIModel(t *testing.T) {
|
||||
g := &generateCmd{}
|
||||
g.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
|
||||
err := g.mergeAPIModel()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error calling mergeAPIModel with no --set flag defined: %s", err.Error())
|
||||
}
|
||||
|
||||
g = &generateCmd{}
|
||||
g.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
|
||||
g.set = []string{"masterProfile.count=3,linuxProfile.adminUsername=testuser"}
|
||||
err = g.mergeAPIModel()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error calling mergeAPIModel with one --set flag: %s", err.Error())
|
||||
}
|
||||
|
||||
g = &generateCmd{}
|
||||
g.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
|
||||
g.set = []string{"masterProfile.count=3", "linuxProfile.adminUsername=testuser"}
|
||||
err = g.mergeAPIModel()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error calling mergeAPIModel with multiple --set flags: %s", err.Error())
|
||||
}
|
||||
|
||||
g = &generateCmd{}
|
||||
g.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
|
||||
g.set = []string{"agentPoolProfiles[0].count=1"}
|
||||
err = g.mergeAPIModel()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error calling mergeAPIModel with one --set flag to override an array property: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateCmdMLoadAPIModel(t *testing.T) {
|
||||
g := &generateCmd{}
|
||||
r := &cobra.Command{}
|
||||
|
||||
g.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
|
||||
g.set = []string{"agentPoolProfiles[0].count=1"}
|
||||
|
||||
g.validate(r, []string{"../pkg/acsengine/testdata/simple/kubernetes.json"})
|
||||
g.mergeAPIModel()
|
||||
err := g.loadAPIModel(r, []string{"../pkg/acsengine/testdata/simple/kubernetes.json"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error loading api model: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,12 +98,26 @@ Edit the [simple Kubernetes cluster definition](/examples/kubernetes.json) and f
|
|||
|
||||
Optional: attach to an existing virtual network (VNET). Details [here](features.md#feat-custom-vnet)
|
||||
|
||||
Note: you can then use the `--set` option of the generate command to override values from the cluster definition file directly in the command line (cf. [Step 4](deploy.md#step-4-generate-the-templates))
|
||||
|
||||
### Step 4: Generate the Templates
|
||||
|
||||
The generate command takes a cluster definition and outputs a number of templates which describe your Kubernetes cluster. By default, `generate` will create a new directory named after your cluster nested in the `_output` directory. If my dnsPrefix was `larry` my cluster templates would be found in `_output/larry-`.
|
||||
|
||||
Run `acs-engine generate examples/kubernetes.json`
|
||||
|
||||
The generate command lets you override values from the cluster definition file without having to update the file. You can use the `--set` flag to do that:
|
||||
|
||||
```bash
|
||||
acs-engine generate --set linuxProfile.adminUsername=myNewUsername,masterProfile.count=3 clusterdefinition.json
|
||||
```
|
||||
|
||||
The `--set` flag only supports JSON properties under `properties`. You can also work with array, like the following:
|
||||
|
||||
```bash
|
||||
acs-engine generate --set agentPoolProfiles[0].count=5,agentPoolProfiles[1].name=myPoolName clusterdefinition.json
|
||||
```
|
||||
|
||||
### Step 5: Submit your Templates to Azure Resource Manager (ARM)
|
||||
|
||||
[Deploy the output azuredeploy.json and azuredeploy.parameters.json](../acsengine.md#deployment-usage)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
hash: 6ccf7505e5ada9ca2cbbfc77ca540b6ec59e0bd40d9392c79e29942386446685
|
||||
updated: 2018-04-06T09:25:41.745057-07:00
|
||||
hash: d3817832375df23ed63ab732dabbb73bd3b59896118b8965a1cfbf09815d4c27
|
||||
updated: 2018-04-25T16:03:39.306816-07:00
|
||||
imports:
|
||||
- name: github.com/alexcesaro/statsd
|
||||
version: 7fea3f0d2fab1ad973e641e51dba45443a311a90
|
||||
|
@ -82,6 +82,8 @@ imports:
|
|||
- client/v2
|
||||
- models
|
||||
- pkg/escape
|
||||
- name: github.com/Jeffail/gabs
|
||||
version: 2a3aa15961d5fee6047b8151b67ac2f08ba2c48c
|
||||
- name: github.com/JiangtianLi/gettext
|
||||
version: a8983c062be4b565d723c478922d7736e04fdba4
|
||||
- name: github.com/juju/ratelimit
|
||||
|
@ -165,6 +167,8 @@ imports:
|
|||
- name: golang.org/x/crypto
|
||||
version: 122d919ec1efcfb58483215da23f815853e24b81
|
||||
subpackages:
|
||||
- bcrypt
|
||||
- blowfish
|
||||
- curve25519
|
||||
- ed25519
|
||||
- ed25519/internal/edwards25519
|
||||
|
|
|
@ -56,6 +56,8 @@ import:
|
|||
version: 366b072768b4e6d93c7de236464c0abe85d0b7c6
|
||||
- package: k8s.io/client-go
|
||||
version: ~4.0.0
|
||||
- package: github.com/Jeffail/gabs
|
||||
version: 1.0
|
||||
testImport:
|
||||
# glide isn't able to mutually reconcile pinned versions of these deps
|
||||
- package: github.com/onsi/gomega
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
package transform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Jeffail/gabs"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// APIModelValue represents a value in the APIModel JSON file
|
||||
type APIModelValue struct {
|
||||
stringValue string
|
||||
intValue int64
|
||||
arrayValue bool
|
||||
arrayIndex int
|
||||
arrayProperty string
|
||||
arrayName string
|
||||
}
|
||||
|
||||
// MapValues converts an arraw of rwa ApiModel values (like ["masterProfile.count=4","linuxProfile.adminUsername=admin"]) to a map
|
||||
func MapValues(m map[string]APIModelValue, values []string) {
|
||||
if values == nil || len(values) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, value := range values {
|
||||
splittedValues := strings.Split(value, ",")
|
||||
if len(splittedValues) > 1 {
|
||||
MapValues(m, splittedValues)
|
||||
} else {
|
||||
keyValueSplitted := strings.Split(value, "=")
|
||||
key := keyValueSplitted[0]
|
||||
stringValue := keyValueSplitted[1]
|
||||
|
||||
flagValue := APIModelValue{}
|
||||
|
||||
if asInteger, err := strconv.ParseInt(stringValue, 10, 64); err == nil {
|
||||
flagValue.intValue = asInteger
|
||||
} else {
|
||||
flagValue.stringValue = stringValue
|
||||
}
|
||||
|
||||
// use regex to find array[index].property pattern in the key
|
||||
re := regexp.MustCompile(`(.*?)\[(.*?)\]\.(.*?)$`)
|
||||
match := re.FindStringSubmatch(key)
|
||||
|
||||
// it's an array
|
||||
if len(match) != 0 {
|
||||
i, err := strconv.ParseInt(match[2], 10, 32)
|
||||
if err != nil {
|
||||
log.Warnln(fmt.Sprintf("array index is not specified for property %s", key))
|
||||
} else {
|
||||
arrayIndex := int(i)
|
||||
flagValue.arrayValue = true
|
||||
flagValue.arrayName = match[1]
|
||||
flagValue.arrayIndex = arrayIndex
|
||||
flagValue.arrayProperty = match[3]
|
||||
m[key] = flagValue
|
||||
}
|
||||
} else {
|
||||
m[key] = flagValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MergeValuesWithAPIModel takes the path to an ApiModel JSON file, loads it and merges it with the values in the map to another temp file
|
||||
func MergeValuesWithAPIModel(apiModelPath string, m map[string]APIModelValue) (string, error) {
|
||||
// load the apiModel file from path
|
||||
fileContent, err := ioutil.ReadFile(apiModelPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// parse the json from file content
|
||||
jsonObj, err := gabs.ParseJSON(fileContent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// update api model definition with each value in the map
|
||||
for key, flagValue := range m {
|
||||
// working on an array
|
||||
if flagValue.arrayValue {
|
||||
log.Infoln(fmt.Sprintf("--set flag array value detected. Name: %s, Index: %b, PropertyName: %s", flagValue.arrayName, flagValue.arrayIndex, flagValue.arrayProperty))
|
||||
arrayValue := jsonObj.Path(fmt.Sprint("properties.", flagValue.arrayName))
|
||||
if flagValue.stringValue != "" {
|
||||
arrayValue.Index(flagValue.arrayIndex).SetP(flagValue.stringValue, flagValue.arrayProperty)
|
||||
} else {
|
||||
arrayValue.Index(flagValue.arrayIndex).SetP(flagValue.intValue, flagValue.arrayProperty)
|
||||
}
|
||||
} else {
|
||||
if flagValue.stringValue != "" {
|
||||
jsonObj.SetP(flagValue.stringValue, fmt.Sprint("properties.", key))
|
||||
} else {
|
||||
jsonObj.SetP(flagValue.intValue, fmt.Sprint("properties.", key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate a new file
|
||||
tmpFile, err := ioutil.TempFile("", "mergedApiModel")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tmpFileName := tmpFile.Name()
|
||||
err = ioutil.WriteFile(tmpFileName, []byte(jsonObj.String()), os.ModeAppend)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tmpFileName, nil
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package transform
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/Jeffail/gabs"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestAPIModelMergerMapValues(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
|
||||
m := make(map[string]APIModelValue)
|
||||
values := []string{"masterProfile.count=5", "agentPoolProfiles[0].name=agentpool1", "linuxProfile.adminUsername=admin"}
|
||||
|
||||
MapValues(m, values)
|
||||
Expect(m["masterProfile.count"].intValue).To(BeIdenticalTo(int64(5)))
|
||||
Expect(m["agentPoolProfiles[0].name"].arrayValue).To(BeTrue())
|
||||
Expect(m["agentPoolProfiles[0].name"].arrayIndex).To(BeIdenticalTo(0))
|
||||
Expect(m["agentPoolProfiles[0].name"].arrayProperty).To(BeIdenticalTo("name"))
|
||||
Expect(m["agentPoolProfiles[0].name"].arrayName).To(BeIdenticalTo("agentPoolProfiles"))
|
||||
Expect(m["agentPoolProfiles[0].name"].stringValue).To(BeIdenticalTo("agentpool1"))
|
||||
Expect(m["linuxProfile.adminUsername"].stringValue).To(BeIdenticalTo("admin"))
|
||||
}
|
||||
|
||||
func TestMergeValuesWithAPIModel(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
|
||||
m := make(map[string]APIModelValue)
|
||||
values := []string{"masterProfile.count=5", "agentPoolProfiles[0].name=agentpool1", "linuxProfile.adminUsername=admin"}
|
||||
|
||||
MapValues(m, values)
|
||||
tmpFile, _ := MergeValuesWithAPIModel("../testdata/simple/kubernetes.json", m)
|
||||
|
||||
jsonFileContent, err := ioutil.ReadFile(tmpFile)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
jsonAPIModel, err := gabs.ParseJSON(jsonFileContent)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
masterProfileCount := jsonAPIModel.Path("properties.masterProfile.count").Data()
|
||||
Expect(masterProfileCount).To(BeIdenticalTo(float64(5)))
|
||||
|
||||
adminUsername := jsonAPIModel.Path("properties.linuxProfile.adminUsername").Data()
|
||||
Expect(adminUsername).To(BeIdenticalTo("admin"))
|
||||
|
||||
agentPoolProfileName := jsonAPIModel.Path("properties.agentPoolProfiles").Index(0).Path("name").Data().(string)
|
||||
Expect(agentPoolProfileName).To(BeIdenticalTo("agentpool1"))
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2014 Ashley Jeffs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,284 @@
|
|||
![Gabs](gabs_logo.png "Gabs")
|
||||
|
||||
Gabs is a small utility for dealing with dynamic or unknown JSON structures in golang. It's pretty much just a helpful wrapper around the golang json.Marshal/json.Unmarshal behaviour and map[string]interface{} objects. It does nothing spectacular except for being fabulous.
|
||||
|
||||
https://godoc.org/github.com/Jeffail/gabs
|
||||
|
||||
##How to install:
|
||||
|
||||
```bash
|
||||
go get github.com/Jeffail/gabs
|
||||
```
|
||||
|
||||
##How to use
|
||||
|
||||
###Parsing and searching JSON
|
||||
|
||||
```go
|
||||
...
|
||||
|
||||
import "github.com/Jeffail/gabs"
|
||||
|
||||
jsonParsed, err := gabs.ParseJSON([]byte(`{
|
||||
"outter":{
|
||||
"inner":{
|
||||
"value1":10,
|
||||
"value2":22
|
||||
},
|
||||
"alsoInner":{
|
||||
"value1":20
|
||||
}
|
||||
}
|
||||
}`))
|
||||
|
||||
var value float64
|
||||
var ok bool
|
||||
|
||||
value, ok = jsonParsed.Path("outter.inner.value1").Data().(float64)
|
||||
// value == 10.0, ok == true
|
||||
|
||||
value, ok = jsonParsed.Search("outter", "inner", "value1").Data().(float64)
|
||||
// value == 10.0, ok == true
|
||||
|
||||
value, ok = jsonParsed.Path("does.not.exist").Data().(float64)
|
||||
// value == 0.0, ok == false
|
||||
|
||||
exists := jsonParsed.Exists("outter", "inner", "value1")
|
||||
// exists == true
|
||||
|
||||
exists := jsonParsed.Exists("does", "not", "exist")
|
||||
// exists == false
|
||||
|
||||
exists := jsonParsed.ExistsP("does.not.exist")
|
||||
// exists == false
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
###Iterating objects
|
||||
|
||||
```go
|
||||
...
|
||||
|
||||
jsonParsed, _ := gabs.ParseJSON([]byte(`{"object":{ "first": 1, "second": 2, "third": 3 }}`))
|
||||
|
||||
// S is shorthand for Search
|
||||
children, _ := jsonParsed.S("object").ChildrenMap()
|
||||
for key, child := range children {
|
||||
fmt.Printf("key: %v, value: %v\n", key, child.Data().(string))
|
||||
}
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
###Iterating arrays
|
||||
|
||||
```go
|
||||
...
|
||||
|
||||
jsonParsed, _ := gabs.ParseJSON([]byte(`{"array":[ "first", "second", "third" ]}`))
|
||||
|
||||
// S is shorthand for Search
|
||||
children, _ := jsonParsed.S("array").Children()
|
||||
for _, child := range children {
|
||||
fmt.Println(child.Data().(string))
|
||||
}
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Will print:
|
||||
|
||||
```
|
||||
first
|
||||
second
|
||||
third
|
||||
```
|
||||
|
||||
Children() will return all children of an array in order. This also works on objects, however, the children will be returned in a random order.
|
||||
|
||||
###Searching through arrays
|
||||
|
||||
If your JSON structure contains arrays you can still search the fields of the objects within the array, this returns a JSON array containing the results for each element.
|
||||
|
||||
```go
|
||||
...
|
||||
|
||||
jsonParsed, _ := gabs.ParseJSON([]byte(`{"array":[ {"value":1}, {"value":2}, {"value":3} ]}`))
|
||||
fmt.Println(jsonParsed.Path("array.value").String())
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Will print:
|
||||
|
||||
```
|
||||
[1,2,3]
|
||||
```
|
||||
|
||||
###Generating JSON
|
||||
|
||||
```go
|
||||
...
|
||||
|
||||
jsonObj := gabs.New()
|
||||
// or gabs.Consume(jsonObject) to work on an existing map[string]interface{}
|
||||
|
||||
jsonObj.Set(10, "outter", "inner", "value")
|
||||
jsonObj.SetP(20, "outter.inner.value2")
|
||||
jsonObj.Set(30, "outter", "inner2", "value3")
|
||||
|
||||
fmt.Println(jsonObj.String())
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Will print:
|
||||
|
||||
```
|
||||
{"outter":{"inner":{"value":10,"value2":20},"inner2":{"value3":30}}}
|
||||
```
|
||||
|
||||
To pretty-print:
|
||||
|
||||
```go
|
||||
...
|
||||
|
||||
fmt.Println(jsonObj.StringIndent("", " "))
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Will print:
|
||||
|
||||
```
|
||||
{
|
||||
"outter": {
|
||||
"inner": {
|
||||
"value": 10,
|
||||
"value2": 20
|
||||
},
|
||||
"inner2": {
|
||||
"value3": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
###Generating Arrays
|
||||
|
||||
```go
|
||||
...
|
||||
|
||||
jsonObj := gabs.New()
|
||||
|
||||
jsonObj.Array("foo", "array")
|
||||
// Or .ArrayP("foo.array")
|
||||
|
||||
jsonObj.ArrayAppend(10, "foo", "array")
|
||||
jsonObj.ArrayAppend(20, "foo", "array")
|
||||
jsonObj.ArrayAppend(30, "foo", "array")
|
||||
|
||||
fmt.Println(jsonObj.String())
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Will print:
|
||||
|
||||
```
|
||||
{"foo":{"array":[10,20,30]}}
|
||||
```
|
||||
|
||||
Working with arrays by index:
|
||||
|
||||
```go
|
||||
...
|
||||
|
||||
jsonObj := gabs.New()
|
||||
|
||||
// Create an array with the length of 3
|
||||
jsonObj.ArrayOfSize(3, "foo")
|
||||
|
||||
jsonObj.S("foo").SetIndex("test1", 0)
|
||||
jsonObj.S("foo").SetIndex("test2", 1)
|
||||
|
||||
// Create an embedded array with the length of 3
|
||||
jsonObj.S("foo").ArrayOfSizeI(3, 2)
|
||||
|
||||
jsonObj.S("foo").Index(2).SetIndex(1, 0)
|
||||
jsonObj.S("foo").Index(2).SetIndex(2, 1)
|
||||
jsonObj.S("foo").Index(2).SetIndex(3, 2)
|
||||
|
||||
fmt.Println(jsonObj.String())
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Will print:
|
||||
|
||||
```
|
||||
{"foo":["test1","test2",[1,2,3]]}
|
||||
```
|
||||
|
||||
###Converting back to JSON
|
||||
|
||||
This is the easiest part:
|
||||
|
||||
```go
|
||||
...
|
||||
|
||||
jsonParsedObj := gabs.ParseJSON([]byte(`{
|
||||
"outter":{
|
||||
"values":{
|
||||
"first":10,
|
||||
"second":11
|
||||
}
|
||||
},
|
||||
"outter2":"hello world"
|
||||
}`))
|
||||
|
||||
jsonOutput := jsonParsedObj.String()
|
||||
// Becomes `{"outter":{"values":{"first":10,"second":11}},"outter2":"hello world"}`
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
And to serialize a specific segment is as simple as:
|
||||
|
||||
```go
|
||||
...
|
||||
|
||||
jsonParsedObj := gabs.ParseJSON([]byte(`{
|
||||
"outter":{
|
||||
"values":{
|
||||
"first":10,
|
||||
"second":11
|
||||
}
|
||||
},
|
||||
"outter2":"hello world"
|
||||
}`))
|
||||
|
||||
jsonOutput := jsonParsedObj.Search("outter").String()
|
||||
// Becomes `{"values":{"first":10,"second":11}}`
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
### Parsing Numbers
|
||||
|
||||
Gabs uses the `json` package under the bonnet, which by default will parse all number values into `float64`. If you need to parse `Int` values then you should use a `json.Decoder` (https://golang.org/pkg/encoding/json/#Decoder):
|
||||
|
||||
```go
|
||||
sample := []byte(`{"test":{"int":10, "float":6.66}}`)
|
||||
dec := json.NewDecoder(bytes.NewReader(sample))
|
||||
dec.UseNumber()
|
||||
|
||||
val, err := gabs.ParseJSONDecoder(dec)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to parse: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
intValue, err := val.Path("test.int").Data().(json.Number).Int64()
|
||||
```
|
|
@ -0,0 +1,475 @@
|
|||
/*
|
||||
Copyright (c) 2014 Ashley Jeffs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// Package gabs implements a simplified wrapper around creating and parsing JSON.
|
||||
package gabs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
var (
|
||||
// ErrOutOfBounds - Index out of bounds.
|
||||
ErrOutOfBounds = errors.New("out of bounds")
|
||||
|
||||
// ErrNotObjOrArray - The target is not an object or array type.
|
||||
ErrNotObjOrArray = errors.New("not an object or array")
|
||||
|
||||
// ErrNotObj - The target is not an object type.
|
||||
ErrNotObj = errors.New("not an object")
|
||||
|
||||
// ErrNotArray - The target is not an array type.
|
||||
ErrNotArray = errors.New("not an array")
|
||||
|
||||
// ErrPathCollision - Creating a path failed because an element collided with an existing value.
|
||||
ErrPathCollision = errors.New("encountered value collision whilst building path")
|
||||
|
||||
// ErrInvalidInputObj - The input value was not a map[string]interface{}.
|
||||
ErrInvalidInputObj = errors.New("invalid input object")
|
||||
|
||||
// ErrInvalidInputText - The input data could not be parsed.
|
||||
ErrInvalidInputText = errors.New("input text could not be parsed")
|
||||
|
||||
// ErrInvalidPath - The filepath was not valid.
|
||||
ErrInvalidPath = errors.New("invalid file path")
|
||||
|
||||
// ErrInvalidBuffer - The input buffer contained an invalid JSON string
|
||||
ErrInvalidBuffer = errors.New("input buffer contained invalid JSON")
|
||||
)
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
// Container - an internal structure that holds a reference to the core interface map of the parsed
|
||||
// json. Use this container to move context.
|
||||
type Container struct {
|
||||
object interface{}
|
||||
}
|
||||
|
||||
// Data - Return the contained data as an interface{}.
|
||||
func (g *Container) Data() interface{} {
|
||||
return g.object
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
// Path - Search for a value using dot notation.
|
||||
func (g *Container) Path(path string) *Container {
|
||||
return g.Search(strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// Search - Attempt to find and return an object within the JSON structure by specifying the
|
||||
// hierarchy of field names to locate the target. If the search encounters an array and has not
|
||||
// reached the end target then it will iterate each object of the array for the target and return
|
||||
// all of the results in a JSON array.
|
||||
func (g *Container) Search(hierarchy ...string) *Container {
|
||||
var object interface{}
|
||||
|
||||
object = g.object
|
||||
for target := 0; target < len(hierarchy); target++ {
|
||||
if mmap, ok := object.(map[string]interface{}); ok {
|
||||
object = mmap[hierarchy[target]]
|
||||
} else if marray, ok := object.([]interface{}); ok {
|
||||
tmpArray := []interface{}{}
|
||||
for _, val := range marray {
|
||||
tmpGabs := &Container{val}
|
||||
res := tmpGabs.Search(hierarchy[target:]...).Data()
|
||||
if res != nil {
|
||||
tmpArray = append(tmpArray, res)
|
||||
}
|
||||
}
|
||||
if len(tmpArray) == 0 {
|
||||
return &Container{nil}
|
||||
}
|
||||
return &Container{tmpArray}
|
||||
} else {
|
||||
return &Container{nil}
|
||||
}
|
||||
}
|
||||
return &Container{object}
|
||||
}
|
||||
|
||||
// S - Shorthand method, does the same thing as Search.
|
||||
func (g *Container) S(hierarchy ...string) *Container {
|
||||
return g.Search(hierarchy...)
|
||||
}
|
||||
|
||||
// Exists - Checks whether a path exists.
|
||||
func (g *Container) Exists(hierarchy ...string) bool {
|
||||
return g.Search(hierarchy...).Data() != nil
|
||||
}
|
||||
|
||||
// ExistsP - Checks whether a dot notation path exists.
|
||||
func (g *Container) ExistsP(path string) bool {
|
||||
return g.Exists(strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// Index - Attempt to find and return an object within a JSON array by index.
|
||||
func (g *Container) Index(index int) *Container {
|
||||
if array, ok := g.Data().([]interface{}); ok {
|
||||
if index >= len(array) {
|
||||
return &Container{nil}
|
||||
}
|
||||
return &Container{array[index]}
|
||||
}
|
||||
return &Container{nil}
|
||||
}
|
||||
|
||||
// Children - Return a slice of all the children of the array. This also works for objects, however,
|
||||
// the children returned for an object will NOT be in order and you lose the names of the returned
|
||||
// objects this way.
|
||||
func (g *Container) Children() ([]*Container, error) {
|
||||
if array, ok := g.Data().([]interface{}); ok {
|
||||
children := make([]*Container, len(array))
|
||||
for i := 0; i < len(array); i++ {
|
||||
children[i] = &Container{array[i]}
|
||||
}
|
||||
return children, nil
|
||||
}
|
||||
if mmap, ok := g.Data().(map[string]interface{}); ok {
|
||||
children := []*Container{}
|
||||
for _, obj := range mmap {
|
||||
children = append(children, &Container{obj})
|
||||
}
|
||||
return children, nil
|
||||
}
|
||||
return nil, ErrNotObjOrArray
|
||||
}
|
||||
|
||||
// ChildrenMap - Return a map of all the children of an object.
|
||||
func (g *Container) ChildrenMap() (map[string]*Container, error) {
|
||||
if mmap, ok := g.Data().(map[string]interface{}); ok {
|
||||
children := map[string]*Container{}
|
||||
for name, obj := range mmap {
|
||||
children[name] = &Container{obj}
|
||||
}
|
||||
return children, nil
|
||||
}
|
||||
return nil, ErrNotObj
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
// Set - Set the value of a field at a JSON path, any parts of the path that do not exist will be
|
||||
// constructed, and if a collision occurs with a non object type whilst iterating the path an error
|
||||
// is returned.
|
||||
func (g *Container) Set(value interface{}, path ...string) (*Container, error) {
|
||||
if len(path) == 0 {
|
||||
g.object = value
|
||||
return g, nil
|
||||
}
|
||||
var object interface{}
|
||||
if g.object == nil {
|
||||
g.object = map[string]interface{}{}
|
||||
}
|
||||
object = g.object
|
||||
for target := 0; target < len(path); target++ {
|
||||
if mmap, ok := object.(map[string]interface{}); ok {
|
||||
if target == len(path)-1 {
|
||||
mmap[path[target]] = value
|
||||
} else if mmap[path[target]] == nil {
|
||||
mmap[path[target]] = map[string]interface{}{}
|
||||
}
|
||||
object = mmap[path[target]]
|
||||
} else {
|
||||
return &Container{nil}, ErrPathCollision
|
||||
}
|
||||
}
|
||||
return &Container{object}, nil
|
||||
}
|
||||
|
||||
// SetP - Does the same as Set, but using a dot notation JSON path.
|
||||
func (g *Container) SetP(value interface{}, path string) (*Container, error) {
|
||||
return g.Set(value, strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// SetIndex - Set a value of an array element based on the index.
|
||||
func (g *Container) SetIndex(value interface{}, index int) (*Container, error) {
|
||||
if array, ok := g.Data().([]interface{}); ok {
|
||||
if index >= len(array) {
|
||||
return &Container{nil}, ErrOutOfBounds
|
||||
}
|
||||
array[index] = value
|
||||
return &Container{array[index]}, nil
|
||||
}
|
||||
return &Container{nil}, ErrNotArray
|
||||
}
|
||||
|
||||
// Object - Create a new JSON object at a path. Returns an error if the path contains a collision
|
||||
// with a non object type.
|
||||
func (g *Container) Object(path ...string) (*Container, error) {
|
||||
return g.Set(map[string]interface{}{}, path...)
|
||||
}
|
||||
|
||||
// ObjectP - Does the same as Object, but using a dot notation JSON path.
|
||||
func (g *Container) ObjectP(path string) (*Container, error) {
|
||||
return g.Object(strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// ObjectI - Create a new JSON object at an array index. Returns an error if the object is not an
|
||||
// array or the index is out of bounds.
|
||||
func (g *Container) ObjectI(index int) (*Container, error) {
|
||||
return g.SetIndex(map[string]interface{}{}, index)
|
||||
}
|
||||
|
||||
// Array - Create a new JSON array at a path. Returns an error if the path contains a collision with
|
||||
// a non object type.
|
||||
func (g *Container) Array(path ...string) (*Container, error) {
|
||||
return g.Set([]interface{}{}, path...)
|
||||
}
|
||||
|
||||
// ArrayP - Does the same as Array, but using a dot notation JSON path.
|
||||
func (g *Container) ArrayP(path string) (*Container, error) {
|
||||
return g.Array(strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// ArrayI - Create a new JSON array at an array index. Returns an error if the object is not an
|
||||
// array or the index is out of bounds.
|
||||
func (g *Container) ArrayI(index int) (*Container, error) {
|
||||
return g.SetIndex([]interface{}{}, index)
|
||||
}
|
||||
|
||||
// ArrayOfSize - Create a new JSON array of a particular size at a path. Returns an error if the
|
||||
// path contains a collision with a non object type.
|
||||
func (g *Container) ArrayOfSize(size int, path ...string) (*Container, error) {
|
||||
a := make([]interface{}, size)
|
||||
return g.Set(a, path...)
|
||||
}
|
||||
|
||||
// ArrayOfSizeP - Does the same as ArrayOfSize, but using a dot notation JSON path.
|
||||
func (g *Container) ArrayOfSizeP(size int, path string) (*Container, error) {
|
||||
return g.ArrayOfSize(size, strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// ArrayOfSizeI - Create a new JSON array of a particular size at an array index. Returns an error
|
||||
// if the object is not an array or the index is out of bounds.
|
||||
func (g *Container) ArrayOfSizeI(size, index int) (*Container, error) {
|
||||
a := make([]interface{}, size)
|
||||
return g.SetIndex(a, index)
|
||||
}
|
||||
|
||||
// Delete - Delete an element at a JSON path, an error is returned if the element does not exist.
|
||||
func (g *Container) Delete(path ...string) error {
|
||||
var object interface{}
|
||||
|
||||
if g.object == nil {
|
||||
return ErrNotObj
|
||||
}
|
||||
object = g.object
|
||||
for target := 0; target < len(path); target++ {
|
||||
if mmap, ok := object.(map[string]interface{}); ok {
|
||||
if target == len(path)-1 {
|
||||
delete(mmap, path[target])
|
||||
} else if mmap[path[target]] == nil {
|
||||
return ErrNotObj
|
||||
}
|
||||
object = mmap[path[target]]
|
||||
} else {
|
||||
return ErrNotObj
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteP - Does the same as Delete, but using a dot notation JSON path.
|
||||
func (g *Container) DeleteP(path string) error {
|
||||
return g.Delete(strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
Array modification/search - Keeping these options simple right now, no need for anything more
|
||||
complicated since you can just cast to []interface{}, modify and then reassign with Set.
|
||||
*/
|
||||
|
||||
// ArrayAppend - Append a value onto a JSON array.
|
||||
func (g *Container) ArrayAppend(value interface{}, path ...string) error {
|
||||
array, ok := g.Search(path...).Data().([]interface{})
|
||||
if !ok {
|
||||
return ErrNotArray
|
||||
}
|
||||
array = append(array, value)
|
||||
_, err := g.Set(array, path...)
|
||||
return err
|
||||
}
|
||||
|
||||
// ArrayAppendP - Append a value onto a JSON array using a dot notation JSON path.
|
||||
func (g *Container) ArrayAppendP(value interface{}, path string) error {
|
||||
return g.ArrayAppend(value, strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// ArrayRemove - Remove an element from a JSON array.
|
||||
func (g *Container) ArrayRemove(index int, path ...string) error {
|
||||
if index < 0 {
|
||||
return ErrOutOfBounds
|
||||
}
|
||||
array, ok := g.Search(path...).Data().([]interface{})
|
||||
if !ok {
|
||||
return ErrNotArray
|
||||
}
|
||||
if index < len(array) {
|
||||
array = append(array[:index], array[index+1:]...)
|
||||
} else {
|
||||
return ErrOutOfBounds
|
||||
}
|
||||
_, err := g.Set(array, path...)
|
||||
return err
|
||||
}
|
||||
|
||||
// ArrayRemoveP - Remove an element from a JSON array using a dot notation JSON path.
|
||||
func (g *Container) ArrayRemoveP(index int, path string) error {
|
||||
return g.ArrayRemove(index, strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// ArrayElement - Access an element from a JSON array.
|
||||
func (g *Container) ArrayElement(index int, path ...string) (*Container, error) {
|
||||
if index < 0 {
|
||||
return &Container{nil}, ErrOutOfBounds
|
||||
}
|
||||
array, ok := g.Search(path...).Data().([]interface{})
|
||||
if !ok {
|
||||
return &Container{nil}, ErrNotArray
|
||||
}
|
||||
if index < len(array) {
|
||||
return &Container{array[index]}, nil
|
||||
}
|
||||
return &Container{nil}, ErrOutOfBounds
|
||||
}
|
||||
|
||||
// ArrayElementP - Access an element from a JSON array using a dot notation JSON path.
|
||||
func (g *Container) ArrayElementP(index int, path string) (*Container, error) {
|
||||
return g.ArrayElement(index, strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
// ArrayCount - Count the number of elements in a JSON array.
|
||||
func (g *Container) ArrayCount(path ...string) (int, error) {
|
||||
if array, ok := g.Search(path...).Data().([]interface{}); ok {
|
||||
return len(array), nil
|
||||
}
|
||||
return 0, ErrNotArray
|
||||
}
|
||||
|
||||
// ArrayCountP - Count the number of elements in a JSON array using a dot notation JSON path.
|
||||
func (g *Container) ArrayCountP(path string) (int, error) {
|
||||
return g.ArrayCount(strings.Split(path, ".")...)
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
// Bytes - Converts the contained object back to a JSON []byte blob.
|
||||
func (g *Container) Bytes() []byte {
|
||||
if g.object != nil {
|
||||
if bytes, err := json.Marshal(g.object); err == nil {
|
||||
return bytes
|
||||
}
|
||||
}
|
||||
return []byte("{}")
|
||||
}
|
||||
|
||||
// BytesIndent - Converts the contained object to a JSON []byte blob formatted with prefix, indent.
|
||||
func (g *Container) BytesIndent(prefix string, indent string) []byte {
|
||||
if g.object != nil {
|
||||
if bytes, err := json.MarshalIndent(g.object, prefix, indent); err == nil {
|
||||
return bytes
|
||||
}
|
||||
}
|
||||
return []byte("{}")
|
||||
}
|
||||
|
||||
// String - Converts the contained object to a JSON formatted string.
|
||||
func (g *Container) String() string {
|
||||
return string(g.Bytes())
|
||||
}
|
||||
|
||||
// StringIndent - Converts the contained object back to a JSON formatted string with prefix, indent.
|
||||
func (g *Container) StringIndent(prefix string, indent string) string {
|
||||
return string(g.BytesIndent(prefix, indent))
|
||||
}
|
||||
|
||||
// New - Create a new gabs JSON object.
|
||||
func New() *Container {
|
||||
return &Container{map[string]interface{}{}}
|
||||
}
|
||||
|
||||
// Consume - Gobble up an already converted JSON object, or a fresh map[string]interface{} object.
|
||||
func Consume(root interface{}) (*Container, error) {
|
||||
return &Container{root}, nil
|
||||
}
|
||||
|
||||
// ParseJSON - Convert a string into a representation of the parsed JSON.
|
||||
func ParseJSON(sample []byte) (*Container, error) {
|
||||
var gabs Container
|
||||
|
||||
if err := json.Unmarshal(sample, &gabs.object); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gabs, nil
|
||||
}
|
||||
|
||||
// ParseJSONDecoder - Convert a json.Decoder into a representation of the parsed JSON.
|
||||
func ParseJSONDecoder(decoder *json.Decoder) (*Container, error) {
|
||||
var gabs Container
|
||||
|
||||
if err := decoder.Decode(&gabs.object); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gabs, nil
|
||||
}
|
||||
|
||||
// ParseJSONFile - Read a file and convert into a representation of the parsed JSON.
|
||||
func ParseJSONFile(path string) (*Container, error) {
|
||||
if len(path) > 0 {
|
||||
cBytes, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
container, err := ParseJSON(cBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return container, nil
|
||||
}
|
||||
return nil, ErrInvalidPath
|
||||
}
|
||||
|
||||
// ParseJSONBuffer - Read the contents of a buffer into a representation of the parsed JSON.
|
||||
func ParseJSONBuffer(buffer io.Reader) (*Container, error) {
|
||||
var gabs Container
|
||||
jsonDecoder := json.NewDecoder(buffer)
|
||||
if err := jsonDecoder.Decode(&gabs.object); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gabs, nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
Двоичные данные
vendor/github.com/Jeffail/gabs/gabs_logo.png
сгенерированный
поставляемый
Normal file
Двоичные данные
vendor/github.com/Jeffail/gabs/gabs_logo.png
сгенерированный
поставляемый
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 56 KiB |
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче