1

This is a follow up to JSON sometimes array sometimes object

In the original question, I asked how to deal with: "I am consuming a json API that might return a string for a variable or might return an array for a variable"

I have a solution but I was wondering, is there a way to modify json.RawMessage?

Rather then if/then looking at the RawMessage for a [ or { char to determine if the object is an array or an string, what if I always took a RawMessage variable string and turned it into an array?

This way, I don't have to code all of the accessors for BOTH strings AND arrays. I could simply deal with arrays.

So my question is: Is there a way to modify the json.RawMessage?

eg:

Turn this:

{ 
  "net": {
    "comment": {
        "line":
            {
                "$": "All abuse issues will only be responded to by the Abuse",
                "@number": "0"
            }
    }
}

Into This:

{ 
  "net": {
    "comment": {
        "line": [
            {
                "$": "All abuse issues will only be responded to by the Abuse",
                "@number": "0"
            }
        ]
    }
}

So, that way, when I unmarshal into my struct, there is only 1 type of comment.line, Just line[] vs line[] AND line.

Thanks in advance.

I am a golang neophyte and I'm just getting my head wrapped around the difficulties of unmarshaling into an strongly typed language.

Community
  • 1
  • 1
Techie210
  • 55
  • 1
  • 4

2 Answers2

2

Yes, you can edit json.RawMessage type as it is simply an alias for []byte.

That said, you don't need to keep raw type, just make your own implementation of the array type and in your custom Unmarshal function, make scalars an array.

Here's an example (on Play).

All we do here is see if the bytes for MagicArray starts with '[', if so, just unmarshal as normal. Otherwise, Unmarshal and append to slice.

you will have to implement custom array for each type you want to work like this, but that's still probably better than trying to safely manipulate the json binary to try coerce the scalars into arrays.

Another side benefit to this approach is you can it with the streaming json decoder, as in json.NewDecoder(reader).Decode(&obj)

package main

import "encoding/json"
import "log"

type MagicArray []interface{}

func (ma *MagicArray) UnmarshalJSON(b []byte) error {
    if b[0] == '[' {
        return json.Unmarshal(b, (*[]interface{})(ma))
    } else {
        var obj interface{}
        if err := json.Unmarshal(b, &obj); err != nil {
            return err
        }
        *ma = append(*ma, obj)
    }
    return nil
}
func main() {
    myStruct := struct {
        A MagicArray
        B MagicArray
    }{}

    err := json.Unmarshal(jsonToDecode, &myStruct)
    if err != nil {
        log.Println("Fail:", err)
    } else {
        log.Println(myStruct)
    }
}

var jsonToDecode = []byte(`
    { 
    "A": "I am not an array", 
    "B":["I am an array"]
    }
`)
David Budworth
  • 11,248
  • 1
  • 36
  • 45
  • Thanks for the response @David Budworth. It's appreciated. I like the way that you have "generified" this with interface. This will help me simplify my handling of the responses greatly! – Techie210 Nov 12 '15 at 22:45
2

I think that David has a good (better) answer, but to answer your question directly: yes, you can modify a json.RawMessage if you're careful. it's declared as type json.RawMessage []byte, meaning it's just another name for []byte under the hood. You can cast it to []byte or string, modify it, and cast it back.

Doing string options on serialized data isn't the kind of thing you should do without thinking about the consequences, but in the case of wrapping [ and ] around a JSON object, it's not too hard to prove that it should be safe. If msg is a json.RawMessage representing an object, then

json.RawMessage("[" + string(msg) + "]")

is what I would consider a readable approach to making a RawMessage representing an array containing that object :)

hobbs
  • 223,387
  • 19
  • 210
  • 288
  • Thanks for the response @hobbs It;s appreciated. The more I thought about this, the more I'm thinking this is a little like screen scraping. Meaning small changes on the API side could break my stuff but I found your response worthy as I have learned more about the internals of RawMessage. Thanks again! – Techie210 Nov 12 '15 at 22:44