1

I'm trying to search for this document:

"meta": {
    "pageId": "...",
    "userId": "...",
    "ver": "0",
},
"dialog": {
 ...

 }

and get the entire "dialog" as a JSON, so I created this struct:

type Dialog struct {
    Dialog bson.Raw `json:"dialog" bson:"dialog"`
}

So I query the document like this:

dialog := Dialog{}
query := c.Find(locate).One(&dialog)

and when I print dialog, I get a bunch of numbers, which I believe are the raw bytes from the query.

The question is: how to unmarshal it into a JSON object?

The only thing I've found about this are Marshal into a bson.Raw (which doesn't explain how to unmarshal into a json)

Update

Following How to marshal json string to bson document in golang for writing to MongoDB?, I did:

fmt.Println(bson.UnmarshalJSON(dialog.Dialog.Data, &a))

which gets me:

json: unknown constant "l"

As you can see I had to extract the Data from the Raw type, I don't think this is the best way to do it since there's the Kind field which is not being used. Also, what's this 'l'?

Update 2

I thought I had to Unmarshal into a JSON type in order to work with it but I've found that's better to Unmarshal to a map directly, so here it is:

var a bson.M
fmt.Println(bson.Unmarshal(dialog.Dialog.Data, &a))
fmt.Println(a)

It worked for me :)

However, I'm still ignoring the Kind field of the Raw type. Is there a better way to do it?

icza
  • 389,944
  • 63
  • 907
  • 827

1 Answers1

2

JSON is not a type, so you can't unmarshal into a value of type JSON. JSON is a textual representation of some structured data.

bson.Raw is also not equal to JSON representation, so some kind of transformation is inevitable.

What you may do is unmarshal into a value of type interface{}, and then use json.Marshal() to "render" the JSON representation.

If you want this to be "automatic", you may create a new type called JSONStr, and you may implement the bson.Setter and bson.Getter interfaces by it.

This is how it could look like:

type JSONStr string

func (j *JSONStr) SetBSON(raw bson.Raw) (err error) {
    var i interface{}
    if err = raw.Unmarshal(&i); err != nil {
        return
    }
    data, err := json.Marshal(i)
    if err != nil {
        return
    }
    *j = JSONStr(data)
    return
}

func (j *JSONStr) GetBSON() (interface{}, error) {
    var i interface{}
    if err := json.Unmarshal([]byte(*j), &i); err != nil {
        return nil, err
    }
    return i, nil
}

And using this JSONStr type:

type Dialog struct {
    Dialog JSONStr `bson:"dialog"`
}

Update:

If you don't really want a JSON text representation, just use type interface{}:

type Dialog struct {
    Dialog interface{} `bson:"dialog"`
}

And obtain the JSON text like this:

var dialog Dialog
// load a dialog
data, err := json.Marshal(dialog.Dialog)
if err != nil {
    // handle error
}
fmt.Println(string(data))

(Basically this is what the JSONStr.SetBSON() method did exactly.)

Note that interface{} will "cover" all data structures. If you know it's an object, you may use a map. If you know it's an array, you may use a slice, etc. You may also use any concrete type.

icza
  • 389,944
  • 63
  • 907
  • 827
  • I actually perceived that I didn't want to have a json, I just wanted to be able to deal with the data in some way. Now I've found the Unmarshal function and Unmarshaled it into a map. However, while doing it, I'm ignoring the Raw.kind field, I'm extracting the Raw.Data directly. Could you look at my update2? Is this the best way to deal with generic data returned from a MongoDB query? –  Feb 22 '17 at 08:40
  • @GuerlandoOCs No, it's not. The `Kind` field is to tell how to interpret the `Data` field. Ignoring it will cause you troubles. Also, let the `mgo` package handle that for you. Also unmarshaling into a map will also give you errors if the value is an array. To cover all cases, unmarshal into a value of type `interface{}`, like I did in my answer. – icza Feb 22 '17 at 08:42
  • You're right, I did it and it worked. However, in order to access its members, I have to admit it's a map and cast to it. Is it bad to do like this? Because how did I know that it'd unmarshal into a map? –  Feb 22 '17 at 08:48
  • @GuerlandoOCs If you know it's a map, yes, you can use a map. Going even further, if you know all its fields prior, just create a `struct` modeling it exactly, and unmarshal into a value of that type. You can also use `json.Marshal()` on your custom types. – icza Feb 22 '17 at 08:49