I have a struct that I'd like to Marshal into JSON differently depending on the context.
For example, sometimes I want to marshal like this:
type MyStruct struct {
Nickname string `json:"nickname"`
EmailAddress string `json:"email_address"`
PhoneNumber string `json:"-"`
MailingAddress string `json:"-"`
}
And sometimes I want to marshal like this:
type MyStruct struct {
Nickname string `json:"nickname"`
EmailAddress string `json:"email_address"`
PhoneNumber string `json:"phone_number"`
MailingAddress string `json:"mailing_address"`
}
Is there a simple way to do this without:
Making 2 separate structs.
Writing a custom marshaller.
Temporarily removing the string values for PhoneNumber and MailingAddress (with an omitempty on the tag), marshaling and then adding them back.
If only there was a way to:
Specify 2 sets of tags and tell the marshaler which ones to use.
Dynamically change the tags at runtime.
I know you explicitly mention "without writing a custom marshaler", but in case someone sees this and thinks it should be avoided because of complexity, a custom marshaler to do what you want to do is really simple:
type MyStruct struct {
Nickname string `json:"nickname"`
EmailAddress string `json:"email_address"`
PhoneNumber string `json:"phone_number"`
MailingAddress string `json:"mailing_address"`
all bool
}
func (ms MyStruct) MarshalJSON() ([]byte, error) {
m := map[string]interface{}{} // ideally use make with the right capacity
m["nickname"] = ms.Nickname
m["email_address"] = ms.EmailAddress
if ms.all {
m["phone_number"] = ms.PhoneNumber
m["mailing_address"] = ms.MailingAddress
}
return json.Marshal(m)
}
If the all field should be set by an external package, then a method could be defined on the struct, or the field could be made public (wouldn't affect the JSON since it is encoded via the custom marshaler).
Runnable example on the playground: http://play.golang.org/p/1N_iBzvuW4
You can use reflection, not really the most efficient solution but it is convenient.
func MarshalSubset(obj interface{}, fields ...string) ([]byte, error) {
if len(fields) == 0 {
return json.Marshal(obj)
}
out := make(map[string]interface{}, len(fields))
val := reflect.ValueOf(obj)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
panic("not a struct")
}
typ := val.Type()
for _, f := range fields {
val := val.FieldByName(f).Interface()
rfld, _ := typ.FieldByName(f)
tag := strings.Split(rfld.Tag.Get("json"), ",")
if len(tag) > 0 {
f = tag[0]
}
out[f] = val
}
return json.Marshal(out)
}
playground
Related
type Alpha struct {
Name string `json:"name"`
SkipWhenMarshal string `json:"skipWhenMarshal"`
}
func MarshalJSON(out interface{}){
json.Marshal(out)
}
Is it possible to ignore the SkipWhenMarshal field when I do json.Marshal but not when I do
json.Unmarshal.
It should work for any type who calls MarshalJSON
Field tag modifiers like "omitempty" and "-" apply to both marshaling and unmarshaling, so there's no automatic way.
You can implement a MarshalJSON for your type that ignores whatever fields you need. There's no need to implement a custom unmarshaler, because the default works for you.
E.g. something like this:
type Alpha struct {
Id int32
Name string
SkipWhenMarshal string
}
func (a Alpha) MarshalJSON() ([]byte, error) {
m := map[string]string{
"id": fmt.Sprintf("%d", a.Id),
"name": a.Name,
// SkipWhenMarshal *not* marshaled here
}
return json.Marshal(m)
}
You can also make it simpler by using an alias type:
func (a Alpha) MarshalJSON() ([]byte, error) {
type AA Alpha
aa := AA(a)
aa.SkipWhenMarshal = ""
return json.Marshal(aa)
}
Here SkipWhenMarshal will be output, but its value is zeroed out. The advantage in this approach is that if Alpha has many fields, you don't have to repeat them.
What you want simply cannot be done with encoding/json.
But you can have two types
type AlphaIn struct {
Name string `json:"name"`
Bar string `json:"skipWhenMarshal"`
}
type AlphaOut struct {
Name string `json:"name"`
Bar string `json:"-"`
}
Use AlphaIn to deserialise JSON with encoding/json.Unmarshal and use AlphaOut to serialise a struct with encoding/json.Marshal to JSON.
Now this alone would be absolute painful to work with but: struct tags do not play a role in convertibility between types which lets you convert from AlphaIn to AlphaOut with a simple type conversion:
var a AlphaIn = ...
var b AlphaOut = AlphaOut(a)
(A saner naming scheme would be Alpha and AlphaToJSON or soemthing like this.)
I would write a custom marshal function like so: playground
package main
import (
"encoding/json"
"fmt"
"reflect"
"strings"
)
type Alpha struct {
Name string `json:"name"`
SkipWhenMarshal string `json:"SkipWhenMarshal,skip"`
}
func main() {
var a Alpha
a.Name = "John"
a.SkipWhenMarshal = "Snow"
out, _ := marshal(a)
fmt.Println(string(out))
var b Alpha
json.Unmarshal([]byte(`{"Name":"Samwell","SkipWhenMarshal":"Tarly"}`), &b)
fmt.Println(b)
}
// custom marshaling function for json that accepts an additional tag named skip to ignore the field
func marshal(v Alpha) ([]byte, error) {
m := map[string]interface{}{}
ut := reflect.TypeOf(v)
uv := reflect.ValueOf(v)
for i := 0; i < ut.NumField(); i++ {
field := ut.Field(i)
js, ok := field.Tag.Lookup("json")
if !ok || !strings.Contains(js, "skip") {
intf := uv.Field(i).Interface()
switch val := intf.(type) {
case int, int8, uint8:
m[field.Name] = val
case string:
m[field.Name] = val
}
}
}
return json.Marshal(m)
}
It basically rebuilds the struct as a map without the skipped fields and passes it to the original Marshal function.
Now just add the SKIP tag to any field and it should work.
You just need to improve this part:
switch val := intf.(type) {
Since this only assumes you only have strings or ints as fields. Handling more types would be more ideal.
You can try this i.e. breaking the structure into components -> composition
while unmarshalling use Beta type. It will unmarshall only fields that are defined inside Beta struct
type Beta struct {
Name string `json:"name"`
}
type Alpha struct {
*Beta
SkipWhenMarshal string `json:"skipWhenMarshal"`
}
I am struggling with deserializing a integer into a string struct field.
The struct field is a string and is expected to be assignable from users of my library. That's why I want it to be a string, since for the purpose of writing it to the database I actually don't care about the value inside.
The users can supply text, but some just assign integers.
Consider this struct:
type Test struct {
Foo string
}
Sometimes I end up with a JSON value that is valid but won't deserialize into the struct due to the Foo field being a integer instead of a string:
{ "foo": "1" } // works
{ "foo": 1 } // doesn't
json.Unmarshal will blow up with the following error:
json: cannot unmarshal number into Go struct field test.Foo of type string
See the reproduction: https://play.golang.org/p/4Qau3umaVm
Now in every other JSON library (in other languages) I have worked in so far, if the target field is a string and you get a integer the deserializer will usually just wrap the int in a string and be done with it. Can this be achieved in Go?
Since I can't really control how the data comes in I need to make json.Unmarshal unsensitive to this - the other solution would be to define Foo as interface{} which needlessly complicates my code with type assertions etc..
Any ideas on how to do this? I basically need the inverse of json:",string"
To handle big structs you can use embedding.
Updated to not discard possibly previously set field values.
func (t *T) UnmarshalJSON(d []byte) error {
type T2 T // create new type with same structure as T but without its method set!
x := struct{
T2 // embed
Foo json.Number `json:"foo"`
}{T2: T2(*t)} // don't forget this, if you do and 't' already has some fields set you would lose them
if err := json.Unmarshal(d, &x); err != nil {
return err
}
*t = T(x.T2)
t.Foo = x.Foo.String()
return nil
}
https://play.golang.org/p/BytXCeHMvt
You can customize how the data structure is Unmarshaler by implementing the json.Unmarshaler interface.
The simplest way to handle unknown types is to unnmarshal the JSON into an intermediate structure, and handle the type assertions and validation during deserialization:
type test struct {
Foo string `json:"foo"`
}
func (t *test) UnmarshalJSON(d []byte) error {
tmp := struct {
Foo interface{} `json:"foo"`
}{}
if err := json.Unmarshal(d, &tmp); err != nil {
return err
}
switch v := tmp.Foo.(type) {
case float64:
t.Foo = strconv.Itoa(int(v))
case string:
t.Foo = v
default:
return fmt.Errorf("invalid value for Foo: %v", v)
}
return nil
}
https://play.golang.org/p/t0eI4wCxdB
I have a Type "Book" that i read from a different interface which returns json. After reading the json and processing data, i have to convert the book to a public book type to hide fields and change output format.
My problem is, that the input type from the same field (ISBN) is sometimes string and sometimes int. I thought that the easiest solution is to use json.Number to unmarshal the data. That works - but i need string on the outgoing json on different fields...
That is the point where i need help. I would have a custom type which i can set in the public structure at the fields, where i want to set the output-json-field to string. I named the custom type "mytype" in the example. (The real data are nested and i have more fields that i set to string in the output - the id field in the public structure is only a test)
I mean, it should look something like that - or not?
func (m *mytype) MarshalJSON() ([]byte, error) {
...
}
Here is my example code: https://play.golang.org/p/rS9HddzDMp
package main
import (
"encoding/json"
"fmt"
"bytes"
)
/* ----------------------------------------------------------------------------------------------------
Definition of the internal Book object (read from input)
-----------------------------------------------------------------------------------------------------*/
type Book struct {
Id json.Number `json:"id"`
Revision int `json:"revision"`
ISBN json.Number `json:"isbn"`
Title string `json:"title"`
}
/* ----------------------------------------------------------------------------------------------------
Definition of the public Book object
-----------------------------------------------------------------------------------------------------*/
type AliasBook Book
type omit *struct{}
type mytype string
type PublicBook struct {
Id string `json:"id"`
Revision omit `json:"revision,omitempty"`
ISBN mytype `json:"isbn"`
*AliasBook
}
/* ----------------------------------------------------------------------------------------------------
Rendering functions
-----------------------------------------------------------------------------------------------------*/
func (bb *Book) MarshalJSON() ([]byte, error) {
fmt.Println("---------------MarschalJSON---------------")
aux := PublicBook{
Id: bb.Id.String(),
AliasBook: (*AliasBook)(bb),
}
return json.Marshal(&aux)
}
func main() {
var jsonStreams[2][]byte
// Input ISBN as string
jsonStreams[0] = []byte(`{"id":"123","revision":1234,"isbn":"978-3-86680-192-9","title":"Go for dummies"}`)
// Input ISBN as int
jsonStreams[1] = []byte(`{"id":123,"revision":1234,"isbn":9783866801929,"title":"Go for dummies"}`)
// For each stream
for i := range jsonStreams {
fmt.Print("stream: ")
fmt.Println(string(jsonStreams[i]))
// Read Input
b := Book{}
err := json.Unmarshal(jsonStreams[i], &b)
if err == nil {
fmt.Printf("%+v\n", b)
} else {
fmt.Println(err)
fmt.Printf("%+v\n", b)
}
// Output as JSON
response := new(bytes.Buffer)
enc := json.NewEncoder(response)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
err = enc.Encode(&b)
if err == nil {
fmt.Printf("%+v\n", response)
} else {
fmt.Println(err)
fmt.Printf("%+v\n", response)
}
}
}
Edit
I have a solution which works for me. https://play.golang.org/p/Vr4eELsHs1
The keypoint was, that i have to take "fmt.Sprint(*isbn) to return the string in the marshaler. I created a new type, convert the input to int64 with the json.Number function and convert it with the json custom marshaler to string.
The easiest solution is to have a custom type that represents ISBN numbers. You can then implement custom JSON decoding functionality so that you can parse both string & numeric input. For example
type isbn string
func (s *isbn) UnmarshalJSON(buf []byte) error {
// Read numeric characters only from raw JSON input. This will handle strings, numbers or null etc and strip any
// optional separators.
out := make([]byte, 0, len(buf))
for _, b := range buf {
if b >= '0' && b <= '9' {
out = append(out, b)
}
}
// Validate ISBN (assuming not using old 10 digit ISBN)
l := len(out)
if l != 13 {
return errors.New("Invalid ISBN length")
}
// Calculate check digit and ensure valid.
// Create formatted output. This assumes 13 characters for simplicity
*s = isbn(fmt.Sprintf("%s-%s-%s-%s-%s", out[:3], out[3:4], out[4:9], out[9:12], out[12:]))
return nil
}
The above just stores the ISBN in a format suitable for output. However, you could store in any format and have a separate json.Marshaler implementation to format the output if this was required.
Then you can simply make this a field in your Book as normal:
type Book struct {
Id json.Number `json:"id"`
Revision int `json:"revision"`
ISBN isbn `json:"isbn"`
Title string `json:"title"`
}
The ISBN decoding example above is for illustration purposes. You should create a full implementation that is unit tested to ensure it handles all expected input correctly and raises the appropriate errors on empty/malformed input. The performance could also be improved if this was an issue.
EDIT
You can't call json.Marshal inside your json.Marshaler implementation with the same variable. This will cause an infinite recursive loop, e.g.
json.Marshal(e) -> e.MarshalJSON -> json.Marshal(e) -> e.MarshalJSON ...
The json.Number type is a string representation of a number. If you are simply wanting to output all numbers as strings, you don't need any custom types at all. Simply use the relevant string values in your code. For example:
type PublicBook struct {
Id string `json:"id"`
// all other fields...
}
// Creates the public book representation from a book
func Public(b *Book) *PublicBook {
return &PublicBook{
Id: string(b.Id),
}
}
This will always output a string as you are using the string type and not the json.Number type which has custom JSON marshal/unmarshal implementations.
I have a solution which works for me. https://play.golang.org/p/Vr4eELsHs1
The keypoint was, that i have to take fmt.Sprint(*isbn) to return the string in the marshaler. I created a new type, convert the input to int64 with the json.Number function and convert it with the json custom marshaler to string.
Thank you for your help!
i'm trying to decode 2 JSON items into the same struct, because the second JSON complet the first one, but it doesn't work (do nothing) have you got some ideas ?
func getUserClip(this *LibraryController, id string) (*Clip){
//Test Api
//Send Request to azure search
Data := Clip{}
if req := GetClipById("b373400a-bd7e-452a-af68-36992b0323a5"); req == nil {
return nil
} else {
str, err := req.String()
if err != nil {
beego.Debug("Error Json req.String: ", err)
}
//Uncode Json to string
if err := json.Unmarshal([]byte(str), &Data); err != nil {
beego.Debug("Error json", err)
}
for i := range Data.Value {
if req = GetCliRedis(Data.Value[i].Id); err != nil {
return nil
} else {
str, err := req.String()
beego.Debug("JSON REDIS DEBUG: ", str)
if err != nil {
beego.Debug("Error Json req.String: ", err)
}
if err := json.Unmarshal([]byte(str), &Data); err != nil {
beego.Debug("Error json", err)
}
}
i++
}
}
return &Data
}
and the struct
type Clip struct {
Value []InfoClip `json:value`
}
type InfoClip struct {
Id string `json:id`
CreatedAt time.Time `json:createdAt`
StartTimeCode int `json:startTimeCode`
EndTimeCode int `json:endTimeCode`
Metas metas `json:metas`
Tags []string `json:tags`
Categories []string `json:categories`
UserId string `json:userId`
SourceId string `json:sourceId`
ProviderName string `json:providerName`
ProviderReference string `json:providerReference`
PublicationStatus string `json:publicationStatus`
Name string `json:name`
FacebookPage string `json:facebookPage`
TwitterHandle string `json:twitterHandle`
PermaLinkUrl string `json:permalinkUrl`
Logo string `json:logo`
Link string `json:link`
Views int `json:views`
}
type metas struct {
Title string `json:title`
Tags []string `json:tags`
Categories []string `json:categories`
PermaLink string `json:permalink`
}
The JSON I receive is:
{
"clipId":"9b2ea9bb-e54b-4291-ba16-9211fa3c755f",
"streamUrl":"https://<edited out>/asset-32e43a5d-1500-80c3-cc6b-f1e4fe2b5c44\/6c53fbf5-dbe9-4617-9692-78e8d76a7b6e_H264_500kbps_AAC_und_ch2_128kbps.mp4?sv=2012-02-12&sr=c&si=17ed71e8-5176-4432-8092-ee64928a55f6&sig=KHyToRlqvwQxWZXVvRYOkBOBOF0SuBLVmKiGp4joBpw%3D&st=2015-05-18T13%3A32%3A41Z&se=2057-05-07T13%3A32%3A41Z",
"startTimecode":"6",
"endTimecode":"16",
"createdAt":"2015-05-19 13:31:32",
"metas":"{\"title\":\"Zapping : Obama, Marine Le Pen et Michael Jackson\",\"tags\":[\"actualite\"],\"categories\":[\"actualite\"],\"permalink\":\"http:\/\/videos.lexpress.fr\/actualite\/zapping-obama-marine-le-pen-et-michael-jackson_910357.html\"}",
"sourceId":"6c53fbf5-dbe9-4617-9692-78e8d76a7b6e",
"providerName":"dailymotion",
"providerReference":"x1xmnxq",
"publicationStatus":"1",
"userId":"b373400a-bd7e-452a-af68-36992b0323a5",
"name":"LEXPRESS.fr",
"facebookPage":"https:\/\/www.facebook.com\/LExpress",
"twitterHandle":"https:\/\/twitter.com\/lexpress",
"permalinkBaseURL":"https:\/\/tym.net\/fr\/{CLIP_ID}",
"logo":"lexpress-120px.png",
"link":"http:\/\/videos.lexpress.fr\/"
}
The Redis complet the azure search missing information :
here the struct :
type Clip struct {
Value []SearchClip `json:value`
}
type SearchClip struct {
Id string `json:id`
CreatedAt string`json:createdAt`
Tags []string `json:tags`
Categories []string `json:categories`
UserId string `json:userId`
SourceId string `json:sourceId`
Views int `json:views`
}
this is the basic information and redis complet this
I don't want to merge 2 struct into a third one i think it's not the better process, i will do it if it's the last solution.
For lack of activity I'm just gonna post the embedding option as a solution. It's probably the simplest way to do what you want.
type ClipInfoAndMeta struct {
Metas
InfoClip
}
Note I upper cased the name on metas not sure it's necessary but I believe it will be. The language feature being used here is called 'embedding' and it works a lot like composition except that the fields/methods for embedded types are more or less 'hoisted' to the containing types scope. IE with an instance of ClipInfoAndMeta you can directly access any exported field that is defined on InfoClip.
One oddity of your set up is that you'll have collisions on field names between the two types. Not sure how that would play out. With all this being said, it would be helpful to see the json string you're trying to Unmarshal from. As I've been writing this I realized that metas is just a subset of InfoClip. Which has confused me about what you are actually trying to do? I mean, if the data coming back is all in one object, it would mean InfoClip is sufficient for storing all of it. If that is the case you have no reason for the other object... And if you want to trim down the fields which get passed to the display layer of your app you should just define a method on the InfoClip type like func (i *InfoClip) GetMetas() Metas { return &Metas{ ... } } then you can just deal with the one type everywhere and hand off the Metas to the display layer when it's needed.
After a lot of trial and error, I present to you this fully functional solution:
package main
import (
"encoding/json"
"fmt"
"log"
"time"
)
type Clip struct {
Value []InfoClip `json:value`
}
type customTime struct {
time.Time
}
const ctLayout = "2006-01-02 15:04:05"
func (ct *customTime) UnmarshalJSON(b []byte) (err error) {
if b[0] == '"' && b[len(b)-1] == '"' {
b = b[1 : len(b)-1]
}
ct.Time, err = time.Parse(ctLayout, string(b))
return
}
type InfoClip struct {
Id string `json:"clipId"`
CreatedAt customTime `json:"createdAt"`
StartTimeCode string `json:"startTimeCode"` //if you want ints here, you'll have to decode manually, or fix the json beforehand
EndTimeCode string `json:"endTimeCode"` //same for this one
Metas metas `json:"-"`
MetasString string `json:"metas"`
Tags []string `json:"tags"`
Categories []string `json:"categories"`
UserId string `json:"userId"`
SourceId string `json:"sourceId"`
ProviderName string `json:"providerName"`
ProviderReference string `json:"providerReference"`
PublicationStatus string `json:"publicationStatus"`
Name string `json:"name"`
FacebookPage string `json:"facebookPage"`
TwitterHandle string `json:"twitterHandle"`
PermaLinkUrl string `json:"permalinkBaseURL"`
Logo string `json:"logo"`
Link string `json:"link"`
Views int `json:"views"`
}
type metas struct {
Title string `json:"title"`
Tags []string `json:"tags"`
Categories []string `json:"categories"`
PermaLink string `json:"permalink"`
}
var jsonString = `{
"clipId":"9b2ea9bb-e54b-4291-ba16-9211fa3c755f",
"streamUrl":"https://<edited out>/asset-32e43a5d-1500-80c3-cc6b-f1e4fe2b5c44\/6c53fbf5-dbe9-4617-9692-78e8d76a7b6e_H264_500kbps_AAC_und_ch2_128kbps.mp4?sv=2012-02-12&sr=c&si=17ed71e8-5176-4432-8092-ee64928a55f6&sig=KHyToRlqvwQxWZXVvRYOkBOBOF0SuBLVmKiGp4joBpw%3D&st=2015-05-18T13%3A32%3A41Z&se=2057-05-07T13%3A32%3A41Z",
"startTimecode":"6",
"endTimecode":"16",
"createdAt":"2015-05-19 13:31:32",
"metas":"{\"title\":\"Zapping : Obama, Marine Le Pen et Michael Jackson\",\"tags\":[\"actualite\"],\"categories\":[\"actualite\"],\"permalink\":\"http:\/\/videos.lexpress.fr\/actualite\/zapping-obama-marine-le-pen-et-michael-jackson_910357.html\"}",
"sourceId":"6c53fbf5-dbe9-4617-9692-78e8d76a7b6e",
"providerName":"dailymotion",
"providerReference":"x1xmnxq",
"publicationStatus":"1",
"userId":"b373400a-bd7e-452a-af68-36992b0323a5",
"name":"LEXPRESS.fr",
"facebookPage":"https:\/\/www.facebook.com\/LExpress",
"twitterHandle":"https:\/\/twitter.com\/lexpress",
"permalinkBaseURL":"https:\/\/tym.net\/fr\/{CLIP_ID}",
"logo":"lexpress-120px.png",
"link":"http:\/\/videos.lexpress.fr\/"
}`
func main() {
res := parseJson(jsonString)
fmt.Printf("%+v\n",res)
}
func parseJson(theJson string) InfoClip {
toParseInto := struct {
InfoClip
MetasString string `json:"metas"`
}{
InfoClip: InfoClip{},
MetasString: ""}
err := json.Unmarshal([]byte(jsonString), &toParseInto)
if err != nil {
log.Panic(err)
}
err = json.Unmarshal([]byte(toParseInto.MetasString), &toParseInto.InfoClip.Metas)
if err != nil {
log.Panic(err)
}
return toParseInto.InfoClip
}
What are we doing in the parseJson function?
We create a new struct and assign that to the toParseInto variable. We design the struct in a way that it contains all of the fields from InfoClip via embedding, and We add a field to temporarily hold the JSON string metas.
We then unmarshal into that struct, which, after fixing the issues listed below, works fine.
After that, we unmarshal that inner JSON into the correct field in the embedded InfoClip.
We can now easily return that embedded InfoClip to get what we really wanted.
Now, all the issues I have identified in your original solution:
The time format in your JSON is not the standard time format to be used in JSON. That is defined in some RFC, but anyways: because of that, we have to use our own type customTime to parse that. It handles just like a normal time.Time, because that is embedded within.
All your json tags were wrong. All of them had missing quotes, and some were just not even correct.
startTimeCode and endTimeCode are strings in the JSON, not ints
Left to you to improve:
Error handling: Don't just panic in the parseJson function, but rather return the error somehow
If you want startTimecode and endTimecode to be available as ints, parse them manually. You can employ a "hack" similar to the one I used to parse the inner JSON.
One final note, not related to this answer but rather to your question: If you had provided both your code and the JSON with your original question, you would have had an answer in probably less than an hour. Please, please don't make this harder than it needs to be.
EDIT: I forgot to provide my sources, I used this question to parse your time format.
I have struct
type tySurvey struct {
Id int64 `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
I do json.Marshal write JSON bytes in HTML page. jQuery modifies name field in object and encodes object using jQueries JSON.stringify and jQuery posts string to Go handler.
id field encoded as string.
Sent: {"id":1} Received: {"id":"1"}
Problem is that json.Unmarshal fails to unmarshal that JSON because id is not integer anymore.
json: cannot unmarshal string into Go value of type int64
What is best way to handle such data? I do not wish to manually convert every field. I wish to write compact, bug free code.
Quotes is not too bad. JavaScript does not work well with int64.
I would like to learn the easy way to unmarshal json with string values in int64 values.
This is handled by adding ,string to your tag as follows:
type tySurvey struct {
Id int64 `json:"id,string,omitempty"`
Name string `json:"name,omitempty"`
}
This can be found about halfway through the documentation for Marshal.
Please note that you cannot decode the empty string by specifying omitempty as it is only used when encoding.
use json.Number
type tySurvey struct {
Id json.Number `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
You could also create a type alias for int or int64 and create a custom json unmarshaler
Sample code:
Reference
// StringInt create a type alias for type int
type StringInt int
// UnmarshalJSON create a custom unmarshal for the StringInt
/// this helps us check the type of our value before unmarshalling it
func (st *StringInt) UnmarshalJSON(b []byte) error {
//convert the bytes into an interface
//this will help us check the type of our value
//if it is a string that can be converted into a int we convert it
///otherwise we return an error
var item interface{}
if err := json.Unmarshal(b, &item); err != nil {
return err
}
switch v := item.(type) {
case int:
*st = StringInt(v)
case float64:
*st = StringInt(int(v))
case string:
///here convert the string into
///an integer
i, err := strconv.Atoi(v)
if err != nil {
///the string might not be of integer type
///so return an error
return err
}
*st = StringInt(i)
}
return nil
}
func main() {
type Item struct {
Name string `json:"name"`
ItemId StringInt `json:"item_id"`
}
jsonData := []byte(`{"name":"item 1","item_id":"30"}`)
var item Item
err := json.Unmarshal(jsonData, &item)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", item)
}