10

I'm a new Go programmer (From Java) and I would like to reproduce a generic way which is esay to use in Java.

I want to create some function which allow me to do an Unmarshal on a JSON string in order to avoid code duplicity.

This is my current code which is not working :

type myStruct1 struct {
    id string
    name string
}

func (obj myStruct1) toString() string {
    var result bytes.Buffer
    result.WriteString("id : ")
    result.WriteString(obj.id)
    result.WriteString("\n")
    result.WriteString("name : ")
    result.WriteString(obj.name)

    return result.String()
}

func main() {

    content := `{id:"id1",name="myName"}`
    object := myStruct1{}
    parseJSON(content, object)

    fmt.Println(object.toString()) 
}

func parseJSON(content string, object interface{}) {
    var parsed interface{}
    json.Unmarshal([]byte(content), &parsed)
}

This code, on run, returns me this :

id : 
name : 

Do you have any idea ?

Thanks

Henry Woody
  • 14,024
  • 7
  • 39
  • 56
Jérémy Bricout
  • 159
  • 1
  • 3
  • 7
  • 1
    adding an abstraction to one line of code isn't solving duplicity, it's creating obfuscation. Don't underestimate the power of simple readability of code. `json.Unmarshal` is something everyone who reads your code will understand. If I see `parseJSON` in a codebase, I'll have to go look at what that does because I'd never guess that a function was made to wrap an already simple construct. – David Budworth Mar 17 '16 at 13:58
  • Ok, thanks for your advices. It's a Java dev behaviour to try to all factorize. I guess it's a Go's thing I must adopt :) – Jérémy Bricout Mar 17 '16 at 14:15
  • I cant see it as a duplicate. But some people has marked it as duplicate. Here is a programmatic way to tackle the semi-structured objects in golang. https://goplay.tools/snippet/fC7bI-Z6kK3 Idea is to create a recursive struct to represent semi-structured data. type basic struct { AsNumber float64 AsBool bool AsStr string } type Semi struct { AsArray []Semi AsObj map[string]Semi AsPlain *basic } – pPanda_beta May 30 '22 at 15:23
  • Your JSON string representation is wrong. content := `{id:"id1",name="myName"}` Should actually be content := `{id:"id1",name:"myName"}` If you had error handling you would see the json.Unmarshall was failing because of a bad string. – abarbaneld Apr 03 '23 at 14:28

6 Answers6

19

The issue is you want to write to a generic type? You probably want a string map. This works with BSON anyways:

var anyJson map[string]interface{}
json.Unmarshal(bytes, &anyJson)

You'll be able to access the fields like so:

anyJson["id"].(string)

Don't forget to type assert your values, and they must be the correct type or they'll panic. (You can read more about type assertions on the golang site)

user161778
  • 524
  • 2
  • 9
5

To parse "generic JSON" when you have no idea what schema it has:

    var parsed any
    err := json.Unmarshal(jsonText, &parsed)

The returned any in parsed will be a map[string]any or []any or nil or single values float64, bool, string.

You can test the type and react accordingly.

import (
    "encoding/json"
    "fmt"
)

func test(jsonText []byte) {
    // parsing
    var parsed any
    err := json.Unmarshal(jsonText, &parsed)
    if err != nil {
        panic(err) // malformed input
    }

    // type-specific logic
    switch val := parsed.(type) {
    case nil:
        fmt.Println("json specifies null")
    case map[string]any:
        fmt.Printf("id:%s name:%s\n", val["id"], val["name"])
    case []any:
        fmt.Printf("list of %d items\n", len(val))
    case float64:
        fmt.Printf("single number %f\n", val)
    case bool:
        fmt.Printf("single bool %v\n", val)
    case string:
        fmt.Printf("single string %s\n", val)
    default:
        panic(fmt.Errorf("type %T unexpected", parsed))
    }
}
jws
  • 2,171
  • 19
  • 30
1

Unmarshal will only set exported fields of the struct.

Which means you need to modify the json struct to use capital case letters:

type myStruct1 struct {
    Id string
    Name string
}

The reason behind this is that the json library does not have the ability to view fields using reflect unless they are exported.

Endre Simo
  • 11,330
  • 2
  • 40
  • 49
0

You have to export your fields:

type myStruct1 struct {
        Id string
        Name string
}

See Exported Identifiers from documentation.

molivier
  • 2,146
  • 1
  • 18
  • 20
0

There are a few changes you need to make in your code to make it work:

  1. The function json.Unmarshal can only set variables inside your struct which are exported, that is, which start with capital letters. Use something like ID and Name for your variable names inside myStruct1.
  2. Your content is invalid JSON. What you actually want is {"ID":"id1","Name":"myName"}.
  3. You're passing object to parseJSON but you're using parsed instead, not object. Make parseJSON receive a *myStruct (instead of an interface{}), and use that variable instead of parsed when unmarshalling the string. Also, always handle the error returns, like err := json.Unmarshal(content, object), and check err.

I'd suggest you to do the Golang Tour ;)

cd1
  • 15,908
  • 12
  • 46
  • 47
  • Thanks for your tips, i'm afraid i didn't have corrected my code before sent it, i'm sorry. The problem about your 3rd tip is that I want to be able to use as well myStruct1 and myStruct2 as parameter of parseJSON. Have you any idea ? – Jérémy Bricout Mar 17 '16 at 14:05
  • well, you can use `interface{}` then, but you still don't need to declare `parsed`. And there won't be a lot of advantage in having the function `parseJSON` if all it does is to call another function, `json.Unmarshal` :-) But yes, you can leave it as `interface{}` as well, I just like to make things more explicit. – cd1 Mar 17 '16 at 14:12
-1

You can also set the file as an Object with dynamic properties inside another struct. This will let you add metadata and you read it the same way.

type MyFile struct {
    Version string
    Data  map[string]interface{}
}
EduLopez
  • 721
  • 7
  • 18