9

I am trying to unmarshal JSON into a struct. However, the struct has a field with a tag. Using reflection, and I try to see if the tag has the string "json" in it. If it does, then the json to unmarshal should simply be unmarshaled into the field as a string.

Example:

const data = `{"I":3, "S":{"phone": {"sales": "2223334444"}}}`
type A struct {
    I int64
    S string `sql:"type:json"`
}

Problem is simple - unmarshal "S" in the json as a string into the struct A.

This is how far I have come. But I am stuck here.

http://play.golang.org/p/YzrhjuXxGN

tshepang
  • 12,111
  • 21
  • 91
  • 136
sat
  • 5,489
  • 10
  • 63
  • 81

3 Answers3

15

This is the go way of doing it - no reflection requred. Create a new type RawString and create MarshalJSON and UnmarshalJSON methods for it. (playground)

// RawString is a raw encoded JSON object.
// It implements Marshaler and Unmarshaler and can
// be used to delay JSON decoding or precompute a JSON encoding.
type RawString string

// MarshalJSON returns *m as the JSON encoding of m.
func (m *RawString) MarshalJSON() ([]byte, error) {
    return []byte(*m), nil
}

// UnmarshalJSON sets *m to a copy of data.
func (m *RawString) UnmarshalJSON(data []byte) error {
    if m == nil {
        return errors.New("RawString: UnmarshalJSON on nil pointer")
    }
    *m += RawString(data)
    return nil
}

const data = `{"i":3, "S":{"phone": {"sales": "2223334444"}}}`

type A struct {
    I int64
    S RawString `sql:"type:json"`
}

func main() {
    a := A{}
    err := json.Unmarshal([]byte(data), &a)
    if err != nil {
        log.Fatal("Unmarshal failed", err)
    }
    fmt.Println("Done", a)
}

I modified the implementation of RawMessage to create the above.

Nick Craig-Wood
  • 52,955
  • 12
  • 126
  • 132
3

The problem here is that you are using one encoding format for two protocols and probably there is something wrong in your model.

That is a valid Json payload and should be handled as such. One trick that you can use is to create another field to handle the "string" json and one to handle the "json struct". See this modified example: I first unmarshal json and then marshal back to json to create the final string to upload to the database. One field is used for unmarshaling and the other to communicate with the DB.

package main

import(
"fmt"
"encoding/json"
)


const data = `{"i":3, "S":{"phone": {"sales": "2223334444"}}}`
type A struct {
    I int64
    Sjson struct {
       Phone struct {
          Sales string `json:"sales"`
       } `json:"phone"`
    } `json:"S", sql:"-"`
    S string `sql:"type:json",json:"-"`
}

func main() {
    msg := A{}
    _ = json.Unmarshal([]byte(data), &msg)
    data, _ := json.Marshal(msg.Sjson)
    msg.S = string(data)
    fmt.Println("Done", msg)
}
LucianoMdA
  • 73
  • 1
  • 3
fabrizioM
  • 46,639
  • 15
  • 102
  • 119
2

Looks like the problem is that s interface{} in your code was not addressable. For Value.SetString the Value has to be addressable and with Kind String. you can check the documentation for it - http://golang.org/pkg/reflect/#Value.SetString

How i understand it SetString would not change the value in a, since you are only working with interface s. in Laws of Reflection you can find "reflect.ValueOf is a copy of x, not x itself"(3rd Law).

To make your code work I made some type assertions, and I used reflect.ValueOf on a pointer to asserted struct.

To check if Value is settable or addressable you can use Value.CanSet ad Value.CanAddr

working code: http://play.golang.org/p/DTriENkzA8

No idea whether its correct way to do this

Milan Halada
  • 1,943
  • 18
  • 28
  • Thanks. But how do we do it without the type assertion. We usually don't know the type beforehand (s = l.(A)) in the UnmarshalJsonIntoStruct function – sat May 22 '14 at 14:16
  • @BVSat You could make a switch/case for all the possible structs – Milan Halada May 22 '14 at 17:09