PostgreSQL提供两种与JSON有关的数据类型,你可以使用 -JSON 和JSONB 。主要的区别是:
JSON存储一个JSON输入的精确拷贝。JSONB存储JSON输入的二进制表示。这使得它的插入速度较慢,但查询速度较快。它可能会改变键的顺序,并且会删除空白和重复的键。 也支持 (存在)和 (包含)运算符,而 不支持。JSONB?@>JSON
PostgreSQL的文档建议你一般应该使用JSONB ,除非你有特殊的理由不这样做(比如需要保留键的顺序)。
这里有一个基本命令的对照表:
-- Create a table with a JSONB column.
CREATE TABLE items (
id SERIAL PRIMARY KEY,
attrs JSONB
);
-- You can insert any well-formed json input into the column. Note that only
-- lowercase `true` and `false` spellings are accepted.
INSERT INTO items (attrs) VALUES ('{
"name": "Pasta",
"ingredients": ["Flour", "Eggs", "Salt", "Water"],
"organic": true,
"dimensions": {
"weight": 500.00
}
}');
-- Create an index on all key/value pairs in the JSONB column.
CREATE INDEX idx_items_attrs ON items USING gin (attrs);
-- Create an index on a specific key/value pair in the JSONB column.
CREATE INDEX idx_items_attrs_organic ON items USING gin ((attrs->'organic'));
-- The -> operator is used to get the value for a key. The returned value has
-- the type JSONB.
SELECT attrs->'dimensions' FROM items;
SELECT attrs->'dimensions'->'weight' FROM items;
-- Or you can use ->> to do the same thing, but this returns a TEXT value
-- instead.
SELECT attrs->>'dimensions' FROM items;
-- You can use the returned values as normal, although you may need to type
-- cast them first.
SELECT * FROM items WHERE attrs->>'name' ILIKE 'p%';
SELECT * FROM items WHERE (attrs->'dimensions'->>'weight')::numeric < 100.00;
-- Use ? to check for the existence of a specific key.
SELECT * FROM items WHERE attrs ? 'ingredients';
-- The ? operator only works at the top level. If you want to check for the
-- existence of a nested key you can do this:
SELECT * FROM items WHERE attrs->'dimensions' ? 'weight';
-- The ? operator can also be used to check for the existence of a specific
-- text value in json arrays.
SELECT * FROM items WHERE attrs->'ingredients' ? 'Salt';
-- Use @> to check if the JSONB column contains some specific json. This can
-- be useful to filter for a specific key/value pair like so:
SELECT * FROM items WHERE attrs @> '{"organic": true}'::jsonb;
SELECT * FROM items WHERE attrs @> '{"dimensions": {"weight": 10}}'::jsonb;
-- Note that @> looks for *containment*, not for an exact match. The
-- followingquery will return records which have both "Flour" and "Water"
-- as ingredients, rather than *only* "Flour" and "Water" as the ingredients.
SELECT * FROM items WHERE attrs @> '{"ingredients": ["Flour", "Water"]}'::jsonb;
与Go一起使用
如果你不熟悉在Go中使用SQL数据库的一般模式,你可能想在继续之前阅读我对database/sql包的介绍。
已知的JSON字段
当事先知道JSON/JSONB列中的字段时,您可以将JSON/JSONB列的内容映射到一个结构中,或从一个结构中映射出来。要做到这一点,你需要确保该结构实现了:
-
的接口,这样它就可以将数据库中的值编入
driver.Valuer接口,这样它就可以将对象打包成数据库可以理解的JSON字节片了。 -
的接口,这样它就可以将对象打包成数据库可以理解的JSON字节片。
sql.Scanner接口,这样它就可以将JSON字节片从数据库中解压缩成结构字段。
下面是一个示范:
package main
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"errors"
"log"
_ "github.com/lib/pq"
)
type Item struct {
ID int
Attrs Attrs
}
// The Attrs struct represents the data in the JSON/JSONB column. We can use
// struct tags to control how each field is encoded.
type Attrs struct {
Name string `json:"name,omitempty"`
Ingredients []string `json:"ingredients,omitempty"`
Organic bool `json:"organic,omitempty"`
Dimensions struct {
Weight float64 `json:"weight,omitempty"`
} `json:"dimensions,omitempty"`
}
// Make the Attrs struct implement the driver.Valuer interface. This method
// simply returns the JSON-encoded representation of the struct.
func (a Attrs) Value() (driver.Value, error) {
return json.Marshal(a)
}
// Make the Attrs struct implement the sql.Scanner interface. This method
// simply decodes a JSON-encoded value into the struct fields.
func (a *Attrs) Scan(value interface{}) error {
b, ok := value.([]byte)
if !ok {
return errors.New("type assertion to []byte failed")
}
return json.Unmarshal(b, &a)
}
func main() {
db, err := sql.Open("postgres", "postgres://user:pass@localhost/db")
if err != nil {
log.Fatal(err)
}
// Initialize a new Attrs struct and add some values.
attrs := new(Attrs)
attrs.Name = "Pesto"
attrs.Ingredients = []string{"Basil", "Garlic", "Parmesan", "Pine nuts", "Olive oil"}
attrs.Organic = false
attrs.Dimensions.Weight = 100.00
// The database driver will call the Value() method and and marshall the
// attrs struct to JSON before the INSERT.
_, err = db.Exec("INSERT INTO items (attrs) VALUES($1)", attrs)
if err != nil {
log.Fatal(err)
}
// Similarly, we can also fetch data from the database, and the driver
// will call the Scan() method to unmarshal the data to an Attr struct.
item := new(Item)
err = db.QueryRow("SELECT id, attrs FROM items ORDER BY id DESC LIMIT 1").Scan(&item.ID, &item.Attrs)
if err != nil {
log.Fatal(err)
}
// You can then use the struct fields as normal...
weightKg := item.Attrs.Dimensions.Weight / 1000
log.Printf("Item: %d, Name: %s, Weight: %.2fkg", item.ID, item.Attrs.Name, weightKg)
}
未知JSON字段
如果你事先知道你的JSON/JSONB数据将包含哪些键和值,那么上述模式就非常有效。而且它的主要优点是类型安全。
对于那些你事先不知道的时候(例如,数据包含用户生成的键和值),你可以将JSON/JSONB列的内容映射到一个map[string]interface{} ,而不是从一个 。这样做的最大缺点是,你需要键入你从数据库中检索到的任何值,以便使用它们。
下面是同一个例子,但重新写成了使用map[string]interface{}:
package main
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"errors"
"log"
_ "github.com/lib/pq"
)
type Item struct {
ID int
Attrs Attrs
}
type Attrs map[string]interface{}
func (a Attrs) Value() (driver.Value, error) {
return json.Marshal(a)
}
func (a *Attrs) Scan(value interface{}) error {
b, ok := value.([]byte)
if !ok {
return errors.New("type assertion to []byte failed")
}
return json.Unmarshal(b, &a)
}
func main() {
db, err := sql.Open("postgres", "postgres://user:pass@localhost/db")
if err != nil {
log.Fatal(err)
}
item := new(Item)
item.Attrs = Attrs{
"name": "Passata",
"ingredients": []string{"Tomatoes", "Onion", "Olive oil", "Garlic"},
"organic": true,
"dimensions": map[string]interface{}{
"weight": 250.00,
},
}
_, err = db.Exec("INSERT INTO items (attrs) VALUES($1)", item.Attrs)
if err != nil {
log.Fatal(err)
}
item = new(Item)
err = db.QueryRow("SELECT id, attrs FROM items ORDER BY id DESC LIMIT 1").Scan(&item.ID, &item.Attrs)
if err != nil {
log.Fatal(err)
}
name, ok := item.Attrs["name"].(string)
if !ok {
log.Fatal("unexpected type for name")
}
dimensions, ok := item.Attrs["dimensions"].(map[string]interface{})
if !ok {
log.Fatal("unexpected type for dimensions")
}
weight, ok := dimensions["weight"].(float64)
if !ok {
log.Fatal("unexpected type for weight")
}
weightKg := weight / 1000
log.Printf("%s: %.2fkg", name, weightKg)
}