0

I want to store a custom color palette inside a JSON file, but the palette has the type []color.Color (that is an interface and not a concrete type). When I marshal the palette I get something like this:

[{"R":0,"G":0,"B":0,"A":255},{"R":0,"G":0,"B":51,"A":255}...]

The problem is, when I unmarshal that JSON the type []color.Color does not work, because Go cannot create a concrete type underneath that interface.

I have simplified my code to following example:

type myT struct {
    P []color.Color
}

func main() {
    t := myT{palette.WebSafe}
    b, err := json.Marshal(t)
    e("json.Marshal", err)
    t2 := myT{}
    err = json.Unmarshal(b, &t2)
    e("json.Unmarshal", err)
    fmt.Println(string(b))
}

func e(s string, err error) {
    if err != nil {
        fmt.Println(s, err)
    }
}

https://play.golang.org/p/QYIpJ7L1ete

Is there a simple solution or do I have to transform []color.Color to []color.RGBA?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
apxp
  • 5,240
  • 4
  • 23
  • 43
  • You can implement `(*myT) UnmarshalJSON(b []byte) error`, but you still have to decide what `color.Color` implementation you are going to unmarshal to. –  Nov 21 '18 at 20:00
  • That being said, if you are dealing with RGBA components, I would start using `color.RGBA` instead of `color.Color`. –  Nov 21 '18 at 20:01

1 Answers1

1

I would follow Tim's advice and start using color.RGBA, but if you're interested in how to implement custom UnmarshalJSON functions for your custom types I've outline the code below and here: https://play.golang.org/p/8p5a09993GV

Basically you use the UnmarshalJSON func as a middle layer to decode to the "correct" RGBA type and then do some type conversion fu to get it to become the interface you want on your custom myT type.

Again, it might be easier to use color.RGBA instead of color.Color in the overall implementation, but this is HOW you would convert it if you wanted to.

Here is a good gist that goes into the basics: https://gist.github.com/mdwhatcott/8dd2eef0042f7f1c0cd8

A gopher academy blog post that really does some fun stuff: https://blog.gopheracademy.com/advent-2016/advanced-encoding-decoding/

And a good explanation on why a []struct does not 1/1 match a []interface it might implement: golang: slice of struct != slice of interface it implements?

package main

import (
    "encoding/json"
    "fmt"
    "image/color"
    "image/color/palette"
)

type myT struct {
    P []color.Color
}

func main() {
    t := myT{palette.WebSafe}
    b, err := json.Marshal(t)
    e("json.Marshal", err)
    t2 := myT{}
    err = json.Unmarshal(b, &t2)
    e("json.Unmarshal", err)
    fmt.Println(string(b))
    fmt.Println(string(t2))
}

func e(s string, err error) {
    if err != nil {
        fmt.Println(s, err)
    }
}

func (myt *myT) UnmarshalJSON(b []byte) error {
    var tempJson struct {
        P []color.RGBA
    }
    // Unmarshal to our temp struct
    err := json.Unmarshal(b, &tempJson)
    if err != nil {
        return err
    }
    // convert our new friends O(n) to the interface type
    newColors := make([]color.Color, len(tempJson.P))
    for i, v := range tempJson.P {
        newColors[i] = color.Color(v)
    }
    myt.P = newColors
    return nil
}