I am trying to decode an arbitrary JSON using Golang, so I unmarshal the incoming JSON in a map[string]interface{} as shown in the code below:
func JsonHandler(jsonRequest []byte) {
// Creating the maps for JSON
var m interface{}
// Parsing/Unmarshalling JSON encoding/json
if err := json.Unmarshal([]byte(jsonRequest), &m); err != nil {
panic(err)
}
//Creating an output file for writing
f, err := os.OpenFile("/home/dorrahadrich/Desktop/output.txt", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
panic(err)
}
defer f.Close()
ParseJson(m, f, err)
}
func ParseJson(m interface{}, f *os.File, err error) {
switch v := m.(interface{}).(type){
case map[string]interface{}:
ParseMap (m.(map[string]interface{}),f,err)
fmt.Println(v)
case []interface{}:
ParseArray (m.([]interface{}),f,err)
fmt.Println(v)
default:
}
}
func ParseMap(aMap map[string]interface{}, f *os.File, err error) {
for key, val := range aMap {
switch val.(type) {
case map[string]interface{}:
if _, err = f.WriteString(key + "={\n"); err != nil {
panic(err)
}
ParseMap(val.(map[string]interface{}), f, err)
//Close brackets
if _, err = f.WriteString("};\n"); err != nil {
panic(err)
}
case []interface{}:
//Write to file
if _, err = f.WriteString(key + "={\n"); err != nil {
panic(err)
}
ParseArray(val.([]interface{}), f, err)
//Close brackets
if _, err = f.WriteString("};\n"); err != nil {
panic(err)
}
default:
otherValues(key, val.(interface{}), f , err)
}
}
}
func ParseArray(anArray []interface{}, f *os.File, err error) {
for _, val := range anArray {
switch val.(type) {
case map[string]interface{}:
ParseMap(val.(map[string]interface{}), f, err)
case []interface{}:
ParseArray(val.([]interface{}), f, err)
default:
}
}
}
func otherValues(key string, other interface{}, f *os.File, err error) {
if _, err = f.WriteString(key); err != nil {
panic(err)
}
if _, err = f.WriteString("="); err != nil {
panic(err)
}
switch other.(interface{}).(type) {
case string:
if _, err = f.WriteString(other.(string)); err != nil {
panic(err)
}
case float64:
if _, err = f.WriteString(strconv.FormatFloat(other.(float64), 'f', -1, 64)); err != nil {
panic(err)
}
case bool:
if _, err = f.WriteString(strconv.FormatBool(other.(bool))); err != nil {
panic(err)
}
default:
}
}
The problem is that whenever a JSON contains a bool/int/float or any not string value the program panics saying that it fails converting an interface to the given type! Please note that the JSON is arbitrary so I don't have any idea about the keys nor the values, I can't unmrashal into an interface nor access the values giving a path.
The error says it all:
interface conversion: interface{} is bool/float64
when you are unmarshalling json the values for int and bool which are not of interface type. In your switch add case for bool/float64/string too. Since json is arbitrary unmarshal them using interface{}.
func otherValues(other interface{}, f *os.File, err error) {
switch bb := other.(interface{}).(type) {
case string:
fmt.Println("This is a string")
case float64:
fmt.Println("this is a float")
case bool:
fmt.Println("this is a boolean")
default:
fmt.Printf("Default value is of type %v", bb)
}
}
Use file.Write in place of file.WriteString
func (f *File) Write(b []byte) (n int, err error)
Write writes len(b) bytes to the File. It returns the number of bytes
written and an error, if any. Write returns a non-nil error when n !=
len(b).
Related
I tried to use the following tutorial:
https://golangdocs.com/aes-encryption-decryption-in-golang
In order to encrypt/decrypt text using AES256 with Go,
It seems to work with plain strings, but not with JSON encoded structure.
I don't understand why, because I thought JSON encoded data were strings as well.
The part of the code dealing with plain strings is commented with Using trings.
// Using strings
pt := "This is a secret"
c := EncryptAES([]byte(key), []byte(pt))
fmt.Printf("Initial string: %#v\n", pt)
fmt.Printf("Coded: %v\n", c)
decoded := DecryptAES([]byte(key), c)
fmt.Printf("Decoded: %s\n", decoded)
The part of the code after the comment Using JSON strings is the part which doesn't seem to word as expected.
// Using JSON strings
p2 := []record{{Name: "John", Age: 20}, {Name: "Jane", Age: 25}}
m2, _ := json.Marshal(p2)
fmt.Printf("m2 = %s\n", string(m2))
fmt.Printf("m2 = %#v\n", string(m2))
coded := EncryptAES([]byte(key), m2)
decoded = DecryptAES([]byte(key), coded)
fmt.Printf("Decoded: %s\n", decoded)
What am I doing wrong?
I'm using Go: go version go1.18 darwin/arm64
package main
import (
"crypto/aes"
"encoding/json"
"fmt"
)
func CheckError(err error) {
if err != nil {
panic(err)
}
}
type record struct {
Name string `json:"first_name"`
Age int `json:"age"`
}
func main() {
// cipher key
key := "thisis32bitlongpassphraseimusing"
fmt.Printf("len of key %d\n", len(key))
// Using strings
pt := "This is a secret"
c := EncryptAES([]byte(key), []byte(pt))
fmt.Printf("Initial string: %#v\n", pt)
fmt.Printf("Coded: %v\n", c)
decoded := DecryptAES([]byte(key), c)
fmt.Printf("Decoded: %s\n", decoded)
// Using JSON strings
p2 := []record{{Name: "John", Age: 20}, {Name: "Jane", Age: 25}}
m2, _ := json.Marshal(p2)
fmt.Printf("m2 = %s\n", string(m2))
fmt.Printf("m2 = %#v\n", string(m2))
coded := EncryptAES([]byte(key), m2)
decoded = DecryptAES([]byte(key), coded)
fmt.Printf("Decoded: %s\n", decoded)
}
func EncryptAES(key []byte, plaintext []byte) []byte {
c, err := aes.NewCipher(key)
CheckError(err)
out := make([]byte, len(plaintext))
c.Encrypt(out, []byte(plaintext))
return out
}
func DecryptAES(key []byte, ct []byte) []byte {
c, err := aes.NewCipher(key)
CheckError(err)
pt := make([]byte, len(ct))
c.Decrypt(pt, ct)
return pt
}
Here is a working implementation of the encryptFile and decryptFile functions:
(Based on: https://medium.com/#mertkimyonsen/encrypt-a-file-using-go-f1fe3bc7c635)
func encryptFile(key []byte, plainText []byte) []byte {
// Creating block of algorithm
block, err := aes.NewCipher(key)
if err != nil {
log.Fatalf("cipher err: %v", err.Error())
}
// Creating GCM mode
gcm, err := cipher.NewGCM(block)
if err != nil {
log.Fatalf("cipher GCM err: %v", err.Error())
}
// Generating random nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
log.Fatalf("nonce err: %v", err.Error())
}
// Decrypt file
cipherText := gcm.Seal(nonce, nonce, plainText, nil)
return cipherText
}
func decryptFile(key []byte, cipherText []byte) []byte {
// Creating block of algorithm
block, err := aes.NewCipher(key)
if err != nil {
log.Fatalf("cipher err: %v", err.Error())
}
// Creating GCM mode
gcm, err := cipher.NewGCM(block)
if err != nil {
log.Fatalf("cipher GCM err: %v", err.Error())
}
// Deattached nonce and decrypt
nonce := cipherText[:gcm.NonceSize()]
cipherText = cipherText[gcm.NonceSize():]
plainText, err := gcm.Open(nil, nonce, cipherText, nil)
if err != nil {
log.Fatalf("decrypt file err: %v", err.Error())
}
return plainText
}
I have a GQL scheme:
extend type MyType #key(fields: "id") {
id: ID! #external
properties: JSON #external
myField: String! #requires(fields: "properties")
}
scalar JSON
In graph/model/model.go:
package model
import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
)
type JSON map[string]interface{}
// UnmarshalGQL implements the graphql.Unmarshaler interface
func (b *JSON) UnmarshalGQL(v interface{}) error {
*b = make(map[string]interface{})
byteData, err := json.Marshal(v)
if err != nil {
panic("FAIL WHILE MARSHAL SCHEME")
}
tmp := make(map[string]interface{})
err = json.Unmarshal(byteData, &tmp)
if err != nil {
panic("FAIL WHILE UNMARSHAL SCHEME")
//return fmt.Errorf("%v", err)
}
*b = tmp
return nil
}
// MarshalGQL implements the graphql.Marshaler interface
func (b JSON) MarshalGQL(w io.Writer) {
byteData, err := json.Marshal(b)
if err != nil {
panic("FAIL WHILE MARSHAL SCHEME")
}
_, _ = w.Write(byteData)
}
But when I run go run github.com/99designs/gqlgen generate
error:
generating core failed: type.gotpl: template: type.gotpl:52:28: executing "type.gotpl" at <$type.Elem.GO>: nil pointer evaluating *config.TypeReference.
GOexit status 1
I just need to get map[string]interface{} which called JSON. I knew there's scalar Map, but for apollo federation that field must be called JSON.
it's should to replace MarshalGQL to MarshalJSON like:
type JSON map[string]interface{}
func MarshalJSON(b JSON) graphql.Marshaler {
return graphql.WriterFunc(func(w io.Writer) {
byteData, err := json.Marshal(b)
if err != nil {
log.Printf("FAIL WHILE MARSHAL JSON %v\n", string(byteData))
}
_, err = w.Write(byteData)
if err != nil {
log.Printf("FAIL WHILE WRITE DATA %v\n", string(byteData))
}
})
}
func UnmarshalJSON(v interface{}) (JSON, error) {
byteData, err := json.Marshal(v)
if err != nil {
return JSON{}, fmt.Errorf("FAIL WHILE MARSHAL SCHEME")
}
tmp := make(map[string]interface{})
err = json.Unmarshal(byteData, &tmp)
if err != nil {
return JSON{}, fmt.Errorf("FAIL WHILE UNMARSHAL SCHEME")
}
return tmp, nil
}
I have a problem with getting information from json files during reading it from directory. I don't understand, why when i wrote code it's not working at all.
func FilePathWalkDir(root string) ([]string, error) {
var files []string
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
files = append(files, path)
}
return nil
})
return files, err
}
var s []string
func main() {
var (
files []string
err error
)
files, err = FilePathWalkDir("D:/Go/Go_project/Go_pro/files")
if err != nil {
panic(err)
}
for _, file := range files {
//fmt.Println("Index for json:", index)
jsonFile, err := os.Open(file)
if err != nil {
log.Println("Error:", err)
}
defer jsonFile.Close()
byteValue, _ := ioutil.ReadAll(jsonFile)
_ = json.Unmarshal([]byte(byteValue), &s)
log.Printf("Unmarshaled: %v", s)
}
}
After this, i got:
2020/06/21 13:10:03 Unmarshaled: []
2020/06/21 13:10:03 Unmarshaled: []
2020/06/21 13:10:03 Unmarshaled: []
Json files:
First:
{
"name":"Kate",
"date":"2013-04-23T19:24:59.511Z",
"data":"is nice"
}
Second:
{
"name":"Gleison",
"date":"2012-04-23T19:25:00.511Z",
"data":"is a good person"
}
Third:
{
"name":"Rodrigo",
"date":"2013-04-23T20:24:59.511Z",
"data":"is kind"
}
You are trying to unmarshal in slice type []string, while data inside file is map type map[string]string.
Slice type is: ["1", "2", "3"], and Map type is: {"name": "Andrew", "age": 33"}.
Please, read about slices and maps.
func FilePathWalkDir(root string) ([]string, error) {
var files []string
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
files = append(files, path)
}
return nil
})
return files, err
}
// var s []string !
var s map[string]string
func main() {
files, err := FilePathWalkDir("D:/Go/Go_project/Go_pro/files")
if err != nil {
panic(err)
}
for _, file := range files {
jsonFile, err := os.Open(file)
if err != nil {
// ???
log.Println("Error:", err)
}
defer jsonFile.Close()
byteValue, _ := ioutil.ReadAll(jsonFile)
if err := json.Unmarshal([]byte(byteValue), &s); err != nil {
// always check errors
panic(err)
}
log.Printf("Unmarshaled: %v", s)
}
}
If you want to convert the map[string] string to the [] string, you can use the following method
var strs []string
strs = append(s[key],strs)
...
But this method will make you lose the map's key.
Why do you use []string instead of map.
I have JSON string like
"{\"a\": \"b\", \"a\":true,\"c\":[\"field_3 string 1\",\"field3 string2\"]}"
how to detect the duplicate attribute in this json string using Golang
Use the json.Decoder to walk through the JSON. When an object is found, walk through keys and values checking for duplicate keys.
func check(d *json.Decoder, path []string, dup func(path []string) error) error {
// Get next token from JSON
t, err := d.Token()
if err != nil {
return err
}
// Is it a delimiter?
delim, ok := t.(json.Delim)
// No, nothing more to check.
if !ok {
// scaler type, nothing to do
return nil
}
switch delim {
case '{':
keys := make(map[string]bool)
for d.More() {
// Get field key.
t, err := d.Token()
if err != nil {
return err
}
key := t.(string)
// Check for duplicates.
if keys[key] {
// Duplicate found. Call the application's dup function. The
// function can record the duplicate or return an error to stop
// the walk through the document.
if err := dup(append(path, key)); err != nil {
return err
}
}
keys[key] = true
// Check value.
if err := check(d, append(path, key), dup); err != nil {
return err
}
}
// consume trailing }
if _, err := d.Token(); err != nil {
return err
}
case '[':
i := 0
for d.More() {
if err := check(d, append(path, strconv.Itoa(i)), dup); err != nil {
return err
}
i++
}
// consume trailing ]
if _, err := d.Token(); err != nil {
return err
}
}
return nil
}
Here's how to call it:
func printDup(path []string) error {
fmt.Printf("Duplicate %s\n", strings.Join(path, "/"))
return nil
}
...
data := `{"a": "b", "a":true,"c":["field_3 string 1","field3 string2"], "d": {"e": 1, "e": 2}}`
if err := check(json.NewDecoder(strings.NewReader(data)), nil, printDup); err != nil {
log.Fatal(err)
}
The output is:
Duplicate a
Duplicate d/e
Run it on the Playground
Here's how to generate an error on the first duplicate key:
var ErrDuplicate = errors.New("duplicate")
func dupErr(path []string) error {
return ErrDuplicate
}
...
data := `{"a": "b", "a":true,"c":["field_3 string 1","field3 string2"], "d": {"e": 1, "e": 2}}`
err := check(json.NewDecoder(strings.NewReader(data)), nil, dupErr)
if err == ErrDuplicate {
fmt.Println("found a duplicate")
} else if err != nil {
// some other error
log.Fatal(err)
}
One that would probably work well would be to simply decode, reencode, then check the length of the new json against the old json:
https://play.golang.org/p/50P-x1fxCzp
package main
import (
"encoding/json"
"fmt"
)
func main() {
jsn := []byte("{\"a\": \"b\", \"a\":true,\"c\":[\"field_3 string 1\",\"field3 string2\"]}")
var m map[string]interface{}
err := json.Unmarshal(jsn, &m)
if err != nil {
panic(err)
}
l := len(jsn)
jsn, err = json.Marshal(m)
if err != nil {
panic(err)
}
if l != len(jsn) {
panic(fmt.Sprintf("%s: %d (%d)", "duplicate key", l, len(jsn)))
}
}
The right way to do it would be to re-implement the json.Decode function, and store a map of keys found, but the above should work (especially if you first stripped any spaces from the json using jsn = bytes.Replace(jsn, []byte(" "), []byte(""), -1) to guard against false positives.
What is the idiomatic way to unmarshal into time.Duration in Go? How can I make use of time.ParseDuration?
The lack of JSON marshaling and unmarshaling methods on time.Duration was an unfortunate oversight. This should hopefully be resolved in Go2 (see issue #10275).
You can, however, define your own type around time.Duration that supports marshaling to the string representation of the duration and unmarshaling from either the numeric or string representations. Here is an example of such an implementation:
package main
import (
"encoding/json"
"errors"
"fmt"
"time"
)
type Duration struct {
time.Duration
}
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case float64:
d.Duration = time.Duration(value)
return nil
case string:
var err error
d.Duration, err = time.ParseDuration(value)
if err != nil {
return err
}
return nil
default:
return errors.New("invalid duration")
}
}
type Message struct {
Elapsed Duration `json:"elapsed"`
}
func main() {
msgEnc, err := json.Marshal(&Message{
Elapsed: Duration{time.Second * 5},
})
if err != nil {
panic(err)
}
fmt.Printf("%s\n", msgEnc)
var msg Message
if err := json.Unmarshal([]byte(`{"elapsed": "1h"}`), &msg); err != nil {
panic(err)
}
fmt.Printf("%#v\n", msg)
}
https://play.golang.org/p/Zm6hpNR-ZJ2
Just to extend the previous answer. There is another way (very close to Tim's)
type Duration time.Duration
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Duration(d).String())
}
func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case float64:
*d = Duration(time.Duration(value))
return nil
case string:
tmp, err := time.ParseDuration(value)
if err != nil {
return err
}
*d = Duration(tmp)
return nil
default:
return errors.New("invalid duration")
}
}