I am fetching a JSON array from PostgreSQL, and I would like to read it into a map. I am able to Unmarshal the values into a []string slice, but what I actually want is a map[string]bool.
I've written a custom type for the column with a Scan interface that converts the JSON array into a slice of strings first, then reads each string into the custom map type as keys.
type custMap map[string]bool
func (m *custMap) Scan(src interface{}) error {
b, ok := src.([]byte)
if !ok {
return error(errors.New("Error Scanning Array"))
}
s := make([]string, 0)
json.Unmarshal(b, &s)
for _, v := range s {
(*m)[v] = true
}
return nil
}
type data struct {
vals custMap `json: "vals"`
}
The query I am trying to scan returns a row with a column vals which is a JSON array: ["some", "arr", "vals"], where the custom type is used like so:
var d models.data
sqlDB.QueryRow().Scan(&d.vals)
My expected output is a struct with the following shape
{ vals: map[string]bool { "some": true, "arr": true, "vals": true }
This compiles fine, but my code panics with "assignment to entry in nil map"
How can I fix my Scan function? Is it even possible to do this with a map type?
You are calling your method Scan of type *custMap on a unitialised map. Initialize d.vals either like
d.vals = custMap{}
or
d.vals = make(custMap)
Other answers already provide an explanation.
The Go Programming Language Specification
Map types
A map is an unordered group of elements of one type, called the
element type, indexed by a set of unique keys of another type, called
the key type. The value of an uninitialized map is nil.
A new, empty map value is made using the built-in function make, which
takes the map type and an optional capacity hint as arguments:
make(map[string]int)
make(map[string]int, 100)
The initial capacity does not bound its size: maps grow to accommodate
the number of items stored in them, with the exception of nil maps. A
nil map is equivalent to an empty map except that no elements may be
added.
I don't see a make to initialize your map: "A nil map is equivalent to an empty map except that no elements may be added."
Related
I need to unmarshal some data, and in one case, the marshaled data represents a map with a byte array as a key.
Slices are not allowed as map keys, however arrays are. But the problem here is that arrays can't be created with a non-constant size as far as I can tell.
Example:
package main
import (
"fmt"
"reflect"
)
func getHashable(value interface{}) interface{} {
rfl := reflect.ValueOf(value)
if rfl.Kind() == reflect.Slice && rfl.Type().Elem().Kind() == reflect.Uint8 {
slice, ok := value.([]uint8)
if !ok {
panic(fmt.Errorf("Could not coerce to []uint8"))
}
var arr [len(slice)]uint8 // This fails
copy(arr, slice)
value = arr
}
return value
}
func unmarshalMap(serialized []byte) map[interface{}]interface{} {
result := make(map[interface{}]interface{})
for len(serialized) > 0 {
var value interface{}
key, bytesConsumed := deserializeValue(serialized)
serialized = serialized[bytesConsumed:]
value, bytesConsumed = deserializeValue(serialized)
serialized = serialized[bytesConsumed:]
result[getHashable(key)] = value
}
}
If deserializeValue() returns a []byte, then it can't be stored as a key in the result map. An array would work, but I can't create an array because I don't know what size I'll need until runtime, and it only allows compile time constants.
A simplified version https://play.golang.org/p/wkYGs3S-uSD fails with the error
./prog.go:15:12: non-constant array bound len(slice)
How can I use an array of bytes that I've unmarshaled as a key in a map in Go?
While use a string is clearly the better way, if the code you don't control uses a byte array as key, here is how you can turn the byte slice into an array as an interface using reflect.
varr := reflect.New(reflect.ArrayOf(len(slice), reflect.TypeOf(uint8(0))))
reflect.Copy(varr.Elem(), reflect.ValueOf(slice))
return varr.Elem().Interface()
Please consider other options before using this.
Playground: https://play.golang.org/p/CXsxZwgjiRR
Use string instead of a fixed size array of bytes. A string can hold an arbitrary sequence of bytes.
func getHashable(value interface{}) interface{} {
rfl := reflect.ValueOf(value)
if rfl.Kind() == reflect.Slice && rfl.Type().Elem().Kind() == reflect.Uint8 {
value = string(rfl.Bytes())
}
return value
}
If you are only need to handle []byte and not named types for []byte, use type assertions instead of reflection:
func getHashable(value interface{}) interface{} {
switch value := value.(type) {
case []byte:
return string(value)
default:
return value
}
}
If the user of the map needs to distinguish string keys from keys created form []byte, define a string type to distinguish those values:
type convertedSlice string
Replace use of string() conversion in the code above with convertedSlice().
The application can check for converted keys with:
_, ok := key.(convertedSlice) // ok is true if key is converted slice.
and convert the keys back to []byte with:
cv, ok := key.(convertedSice)
if ok {
key = []byte(cv)
}
I want to create an array that contains unique strings. How can I do that?
var paths = make([]string, 0)
func main() {
// Members are added dynamically
paths = append(paths, "aaa")
paths = append(paths, "bbb")
paths = append(paths, "bbb")
paths = append(paths, "ccc")
// convert ["aaa", "bbb", "bbb", "ccc"] -> ["aaa", "bbb", "ccc"]
// or can I use some class that disallow the same string automaticaly?
}
If you want a collection of unique elements, that is the Set data type. Go does not have a builtin set data type, but you can use a map to act as a set: keys in a map must be unique.
For a "nice" set, use a map with bool value type (with true values) and exploit the zero value. For a set with the smallest memory footprint, use a map with struct{} value type as values of struct{} type occupy no memory; and use the comma-ok idiom to tell if a value is in the set / map.
Here's how the "nice" version of set looks like. Instead of a slice add your elements to a map[string]bool as the key with a true as the value:
m := make(map[string]bool)
m["aaa"] = true
m["bbb"] = true
m["bbb"] = true
m["ccc"] = true
To check if an element is already in the collection (map), you can simply use an index expression:
exists := m["somevalue"]
This exploits the zero value, that is if the map does not yet contain an element, the zero value of the value type is returned which is false in case of bool type, properly indicating that the element is not in the collection.
Elements in a map have no fixed order. If you need to keep the order (e.g. insertion order), then use a slice (to remember the order) and a map (to tell if an element to be added is new). This is easiest with a helper add() function:
var m = make(map[string]bool)
var a = []string{}
func main() {
add("aaa")
add("bbb")
add("bbb")
add("ccc")
}
func add(s string) {
if m[s] {
return // Already in the map
}
a = append(a, s)
m[s] = true
}
I have declared an array of an array of tuples:
var graphDetailsArray: [Array<(graphType: String, columnLabel: String, columnColor: NSColor, columnHeight: CGFloat, columnNumber: Int)>] = []
a little later on in the function I check this array for nil before activating some code:
if graphDetailsArray != nil { //some code here
This is throwing up an error at build time:
Binary operator '!=' cannot be applied to type Array<(graphType:
String, columnLabel: String, columnColor: NSColor, columnHeight:
CGFloat, columnNumber: Int)> and nil.
I have a tried a range of different syntaxes and can not get this to work.
Any assistance would be appreciated. How do I check for an empty array?
Is using "graphDetails.isEmpty" the same as checking for nil??
Your array is an array, not an optional array. While an optional array could be nil, a non-optional array in Swift can never, ever be nil. Therefore, it doesn't even support an if-statement that would compare it to nil.
Replace
graphDetailsArray != nil
with
true
or remove the if-statement completely.
isEmpty is something totally different. An array could have 0 elements, 1 element, 2 elements etc. isEmpty returns true if your array has 0 elements. Which is totally different from an array being nil.
Hi I'm trying to search an array with various objects though I'm getting an error.
Generic parameter 'C.Generator.Element' cannot be bound to non-#ob
Here is my code I'm using:
var arraySearching = [Any]()
arraySearching = ["this","that",2]
find(arraySearching, 2)
How do you search arrays of type [Any]?
This is a misleading error message, but the problem is that find requires that the contents of the collection you are searching be Equatable, and Any isn’t. You can’t do much with Any at all in fact, never mind equate it with values of other types. You have to cast it to a real type and then use that.
That’d be easy to do in a closure, except there isn’t a version of find that takes a closure. But it’s easy to write one:
func find<C: CollectionType>(source: C, match: C.Generator.Element -> Bool) -> C.Index? {
for idx in indices(source) {
if match(source[idx]) { return idx }
}
return nil
}
Once you have this, you can use it to search for a value of a specific type in an array of Any:
find(arraySearching) {
// cast the candidate to an Int, then compare (comparing an
// optional result of as? works fine, nil == 2 is false)
($0 as? Int) == 2
}
I am trying to write a function Map, so that it can handle all the types of array.
// Interface to specify generic type of array.
type Iterable interface {
}
func main() {
list_1 := []int{1, 2, 3, 4}
list_2 := []uint8{'a', 'b', 'c', 'd'}
Map(list_1)
Map(list_2)
}
// This function prints the every element for
// all []types of array.
func Map(list Iterable) {
for _, value := range list {
fmt.Print(value)
}
}
But it throws the compile time error.
19: cannot range over list (type Iterable)
The error is correct because range require array, pointer to an array, slice, string, map, or channel permitting receive operations and here type is Iterable. I think problem that I am facing is, conversion of the argument type Iterable to array type. Please suggest, how could I use my function to handle generic array.
As Rob Pike mentions in this thread
Is it possible to express "any map", "any array" or "any slice" in a Go type switch?
No. The static types must be exact.
The empty interface is really a type, not a wildcard.
You only could iterate over a list of a specific type, like an interface with known functions.
You can see an example with "Can we write a generic array/slice deduplication in go?"
Even using reflection, to pass a slice as an interface{} would be, as this thread shows, error-prone (see this example).
Update Nov. 2021, 7 years later: CL 363434, for Go 1.18 (Q1 2022) actually introduces functions useful with slices of any type, using generics.
// Package slices defines various functions useful with slices of any type.
// Unless otherwise specified, these functions all apply to the elements
// of a slice at index 0 <= i < len(s).
package slices
import "golang.org/x/exp/constraints"
// Equal reports whether two slices are equal: the same length and all
// elements equal. If the lengths are different, Equal returns false.
// Otherwise, the elements are compared in index order, and the
// comparison stops at the first unequal pair.
// Floating point NaNs are not considered equal.
func Equal[T comparable](s1, s2 []T) bool {
if len(s1) != len(s2) {
return false
}
for i, v1 := range s1 {
v2 := s2[i]
if v1 != v2 {
return false
}
}
return true
}
Note that issue 50792 and CL 382834 show that:
We left constraints behind in the standard library because we believed it was fundamental to using generics, but in practice that hasn't proven to be the case.
In particular, most code uses any or comparable.
If those are the only common constraints, maybe we don't need the package.
Or if constraints.Ordered is the only other commonly used constraint, maybe that should be a predeclared identifier next to any and comparable.
Hence import "golang.org/x/exp/constraints" instead of import "constraints".
Your definition of Map is some unсomplete. Usual way to declare it would have mapper method.
Your example can be implemented at least this way
package main
import "fmt"
// Interface to specify something thet can be mapped.
type Mapable interface {
}
func main() {
list_1 := []int{1, 2, 3, 4}
list_2 := []string{"a", "b", "c", "d"}
Map(print, list_1)
Map(print, list_2)
}
func print(value Mapable){
fmt.Print(value)
}
// This function maps the every element for
// all []types of array.
func Map(mapper func(Mapable), list ... Mapable) {
for _, value := range list {
mapper(value)
}
}
It works. Need to say it's a bit of untyped. Because no, Go has not 'generics' in Hindley-Milner sence