草庐IT

go - 使用指针序列化结构

coder 2024-07-12 原文

具有如下结构层次结构:

type DomainStore struct {
    Domains []*Domain
    Users []*User
}

type Domain struct {
    Name    string
    Records []*Record
    Owner   *User
}

type User struct {
    Name      string
    Email     string
    Domains []*Domain
}

type Record struct {
    Name      string
    Host      string
}

单个 DomainStore 具有域和用户列表,指针位于域和用户之间。

我正在寻找一种对文件进行序列化/反序列化的方法。我一直在尝试使用 gob,但指针没有(按设计)正确序列化(扁平化)。

考虑给每个对象一个唯一的 id 并制作一个 func 来序列化/反序列化每种类型,但这似乎需要很多工作/样板。对策略有什么建议吗?

我想将整个 DomainStore 保存在内存中,并根据用户请求序列化到文件。

主要问题:如何序列化/反序列化并保持指针指向同一对象而不是同一对象的不同副本

gob 和 json 似乎都“只是”复制对象的值并进行反序列化,我最终得到了多个独立的对象副本。

使用 gob ang json 会发生这种情况:

之前,A & C 都指向 B:

A -> B <- C

使用 json/gob 反序列化后:

A -> B1 , C -> B2

A & C 指向不同的对象,具有相同的值。但是,如果我更改 B1,它不会在 B2 中更改。

---更新---

编码时我可以获得对象的内存位置并将其用作 ID:

func (u *User) MarshalJSON() ([]byte, error) {
    return json.Marshal(&JsonUser{
        ID:       fmt.Sprintf("%p", u),
        Name:     u.Name,
        Email:    u.Email,
    })
}

在编码域时,我可以替换

func (d *Domain) MarshalJSON() ([]byte, error) {
    return json.Marshal(&struct {
        ID       string `json:"id"`
        Name     string `json:"name"`
        User     string `json:"user"`
    }{
        ID:       fmt.Sprintf("%p", d),
        Name:     d.Name,
        User:     fmt.Sprintf("%p", d.User),
    })
}

现在我只是需要能够解码这个,这给我带来了一个问题,UnmarshalJSON 需要访问 id 及其各自对象的映射。

func (u *User) UnmarshalJSON(data []byte) error {
  // need acces to a map shared by all UnmarshalJSON functions
}

最佳答案

可以通过以下方法完成:

  1. 所有对象都放置在 map 中的一个 State 对象中。
  2. 当 State 对象中的对象被编码时,所有使用指针引用的对象都将替换为该对象的内存位置。
  3. 当使用以前读取的对象的全局列表恢复未编码的指针时。

代码会运行,只是为了说明方法,我是 Go 的新手,所以请耐心等待。

package main

import (
    "encoding/json"
    "errors"
    "fmt"
    "log"
    "strings"
)

type User struct {
    Name  string
    Email string
}
type JsonUser struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func (u *User) Print(level int) {
    ident := strings.Repeat("-", level)
    log.Println(ident, "Username:", u.Name, u.Email)
}
func (u *User) Id() string {
    return fmt.Sprintf("%p", u)
}
func (u *User) MarshalJSON() ([]byte, error) {
    return json.Marshal(&JsonUser{
        ID:    u.Id(),
        Name:  u.Name,
        Email: u.Email,
    })
}
func (u *User) UnmarshalJSON(data []byte) error {
    aux := &JsonUser{}
    if err := json.Unmarshal(data, &aux); err != nil {
        return err
    }
    u.Name = aux.Name
    u.Email = aux.Email
    load_helper[aux.ID] = u
    log.Println("Added user with id ", aux.ID, u.Name)
    return nil
}

type Record struct {
    Type     string // MX / A / CNAME / TXT / REDIR / SVR
    Name     string // @ / www
    Host     string // IP / address
    Priority int    // Used for MX
    Port     int    // Used for SVR
}
type JsonRecord struct {
    ID       string
    Type     string
    Name     string
    Host     string
    Priority int
    Port     int
}

func (r *Record) Print(level int) {
    ident := strings.Repeat("-", level)
    log.Println(ident, "", r.Type, r.Name, r.Host)
}
func (r *Record) Id() string {
    return fmt.Sprintf("%p", r)
}
func (r *Record) MarshalJSON() ([]byte, error) {
    return json.Marshal(&JsonRecord{
        ID:       r.Id(),
        Name:     r.Name,
        Type:     r.Type,
        Host:     r.Host,
        Priority: r.Priority,
        Port:     r.Port,
    })
}
func (r *Record) UnmarshalJSON(data []byte) error {
    aux := &JsonRecord{}
    if err := json.Unmarshal(data, &aux); err != nil {
        return err
    }
    r.Name = aux.Name
    r.Type = aux.Type
    r.Host = aux.Host
    r.Priority = aux.Priority
    r.Port = aux.Port
    load_helper[aux.ID] = r
    log.Println("Added record with id ", aux.ID, r.Name)
    return nil
}

type Domain struct {
    Name    string
    User    *User     // User ID
    Records []*Record // Record ID's
}
type JsonDomain struct {
    ID      string   `json:"id"`
    Name    string   `json:"name"`
    User    string   `json:"user"`
    Records []string `json:"records"`
}

func (d *Domain) Print(level int) {
    ident := strings.Repeat("-", level)
    log.Println(ident, "Domain:", d.Name)
    d.User.Print(level + 1)
    log.Println(ident, " Records:")
    for _, r := range d.Records {
        r.Print(level + 2)
    }
}
func (d *Domain) Id() string {
    return fmt.Sprintf("%p", d)
}
func (d *Domain) MarshalJSON() ([]byte, error) {
    var record_ids []string
    for _, r := range d.Records {
        record_ids = append(record_ids, r.Id())
    }
    return json.Marshal(JsonDomain{
        ID:      d.Id(),
        Name:    d.Name,
        User:    d.User.Id(),
        Records: record_ids,
    })
}
func (d *Domain) UnmarshalJSON(data []byte) error {
    log.Println("UnmarshalJSON domain")
    aux := &JsonDomain{}
    if err := json.Unmarshal(data, &aux); err != nil {
        return err
    }
    d.Name = aux.Name
    d.User = load_helper[aux.User].(*User) // restore pointer to domains user
    for _, record_id := range aux.Records {
        d.Records = append(d.Records, load_helper[record_id].(*Record))
    }
    return nil
}

type State struct {
    Users   map[string]*User
    Records map[string]*Record
    Domains map[string]*Domain
}

func NewState() *State {
    s := &State{}
    s.Users = make(map[string]*User)
    s.Domains = make(map[string]*Domain)
    s.Records = make(map[string]*Record)
    return s
}
func (s *State) Print() {
    log.Println("State:")
    log.Println("Users:")
    for _, u := range s.Users {
        u.Print(1)
    }
    log.Println("Domains:")
    for _, d := range s.Domains {
        d.Print(1)
    }
}
func (s *State) NewUser(name string, email string) *User {
    u := &User{Name: name, Email: email}
    id := fmt.Sprintf("%p", u)
    s.Users[id] = u
    return u
}
func (s *State) NewDomain(user *User, name string) *Domain {
    d := &Domain{Name: name, User: user}
    s.Domains[d.Id()] = d
    return d
}
func (s *State) NewMxRecord(d *Domain, rtype string, name string, host string, priority int) *Record {
    r := &Record{Type: rtype, Name: name, Host: host, Priority: priority}
    d.Records = append(d.Records, r)
    s.Records[r.Id()] = r
    return r
}
func (s *State) FindDomain(name string) (*Domain, error) {
    for _, v := range s.Domains {
        if v.Name == name {
            return v, nil
        }
    }
    return nil, errors.New("Not found")
}
func Save(s *State) (string, error) {
    b, err := json.MarshalIndent(s, "", "    ")
    if err == nil {
        return string(b), nil
    } else {
        log.Println(err)
        return "", err
    }
}

var load_helper map[string]interface{}

func Load(s *State, blob string) {
    load_helper = make(map[string]interface{})
    if err := json.Unmarshal([]byte(blob), s); err != nil {
        log.Println(err)
    } else {
        log.Println("OK")
    }
}

func test_state() {

    s := NewState()
    u := s.NewUser("Ownername", "some@email.com")
    d := s.NewDomain(u, "somedomain.com")
    s.NewMxRecord(d, "MX", "@", "192.168.1.1", 10)
    s.NewMxRecord(d, "A", "www", "192.168.1.1", 0)

    s.Print()

    x, _ := Save(s) // Saved to json string

    log.Println("State saved, the json string is:")
    log.Println(x)

    s2 := NewState() // Create a new empty State
    Load(s2, x)
    s2.Print()

    d, err := s2.FindDomain("somedomain.com")
    if err == nil {
        d.User.Name = "Changed"
    } else {
        log.Println("Error:", err)
    }
    s2.Print()
}

func main() {
    test_state()
}

这是相当多的代码,对象和序列化之间的耦合度很高。全局变量 load_helper 也不好。改进的想法将不胜感激。

另一种方法是使用反射来制作更通用的解决方案。这是使用此方法的示例:

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "strings"
    "reflect"
)

func pprint(x interface{}) {
    b, err := json.MarshalIndent(x, "", "  ")
    if err != nil {
        fmt.Println("error:", err)
    }
    fmt.Println(string(b))  
}


var typeRegistry = make(map[string]reflect.Type)

// Register a type to make it possible for the Save/Load functions
// to serialize it.
func Register(v interface{}) {
    t := reflect.TypeOf(v)
    n := t.Name()
    fmt.Println("Register type",n)
    typeRegistry[n] = reflect.TypeOf(v)
}

// Make an instance of a type from the string name of the type.
func makeInstance(name string) reflect.Value {
    v := reflect.New(typeRegistry[name]).Elem()
    return v
}

// Translate a string type name tpo a real type.
func getTypeFromString(name string) reflect.Type {
    return typeRegistry[name]
}


// Serializeable interface must be supported by all objects passed to the Load / Save functions.
type Serializeable interface {
    Id() string
}

// GenericSave saves the object d
func GenericSave(d interface{}) (string, error) {
    r := make(map[string]interface{})
    v := reflect.ValueOf(d)
    t := reflect.TypeOf(d)
    if t.Kind()==reflect.Ptr {
        t=t.Elem()
        v=v.Elem()
    }
    r["_TYPE"]=t.Name()
    r["_ID"]=fmt.Sprintf("%p", d)
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        name := f.Name
        vf := v.FieldByName(name)
//      fmt.Println("Field", i+1, "name is", name, "type is", f.Type.Name(), "and kind is", f.Type.Kind())      
//      fmt.Println("V:", vf)
        if f.Tag != "" {
            store:=strings.Split(f.Tag.Get("store"),",")
            switch store[1] {
            case "v":
                switch t.Field(i).Type.Name() {
                case "string":
                    r[store[0]]=vf.String()
                case "int":
                    r[store[0]]=vf.Int()
                }
            case "p":
                vals:=vf.MethodByName("Id").Call([]reflect.Value{})
                r[store[0]]=vals[0].String()
            case "lp":
                tr:=[]string{}
                for j := 0; j < vf.Len(); j++ {
                    vals:=vf.Index(j).MethodByName("Id").Call([]reflect.Value{})
                    tr=append(tr,vals[0].String())
                }
                r[store[0]]=tr
            }
        }
    }   
    m,_:=json.Marshal(r)
    return string(m),nil
}

// Save saves the list of objects.
func Save(objects []Serializeable) []byte {
    lst:=[]string{}
    for _,o := range(objects) {
        os,_:= GenericSave(o) // o.Save()
        lst=append(lst,os)
    }
    m,_:=json.Marshal(lst)
    return m
}


func toStructPtr(obj interface{}) interface{} {
    vp := reflect.New(reflect.TypeOf(obj))
    vp.Elem().Set(reflect.ValueOf(obj))
    return vp.Interface()
}

// Load creates a list of serializeable objects from json blob
func Load(blob []byte) []Serializeable {
    objects := []Serializeable{}
    loadHelper := make(map[string]interface{})
    var olist []interface{}
    if err := json.Unmarshal(blob, &olist); err != nil {
        log.Println(err)
    } else {
        for _,o := range(olist) {

            var omap map[string]interface{}
            json.Unmarshal([]byte(o.(string)), &omap)

            t:= getTypeFromString(omap["_TYPE"].(string))
            obj := reflect.New(t).Elem() 

            for i := 0; i < t.NumField(); i++ {
//              n:=t.Field(i).Name
//              fmt.Println(i,n,t.Field(i).Type.Name())

                if t.Field(i).Tag != "" {
                    store:=strings.Split(t.Field(i).Tag.Get("store"),",")
//                  fmt.Println(store)
                    switch store[1] {
                    case "v":
                        switch t.Field(i).Type.Name() {
                        case "string":
                            obj.FieldByIndex([]int{i}).SetString(omap[store[0]].(string))
                        case "int":
                            obj.FieldByIndex([]int{i}).SetInt(int64(omap[store[0]].(float64)))
                        }
                    case "p":
                        nObj:=loadHelper[omap[store[0]].(string)]
                        obj.FieldByIndex([]int{i}).Set(reflect.ValueOf(nObj.(*User)))
                    case "lp":
                        ptrItemType:=t.Field(i).Type.Elem()
                        slice := reflect.Zero(reflect.SliceOf(  ptrItemType /* reflect.TypeOf( &Record{} ) */  ))//.Interface()
                        for _, pID := range(omap[store[0]].([]interface{})) {
                            nObj:=loadHelper[pID.(string)]
                            slice=reflect.Append(slice,  reflect.ValueOf(nObj)  )
                        }
                        obj.FieldByIndex([]int{i}).Set(slice)                       
                    }
                }
            }
            oi:=toStructPtr(obj.Interface())
            oip:=oi.(Serializeable)
            objects=append(objects,oip)
            loadHelper[omap["_ID"].(string)]=oip
        }
    }
    return objects

}



/* Application data structures */

type User struct {
    Name  string `store:"name,v"`
    Email string `store:"email,v"`
}
func (u *User) Id() string {
    return fmt.Sprintf("%p", u)
}
func (u *User) Save() (string, error) {
    return GenericSave(u)
}
func (u *User) Print() {
    fmt.Println("User:",u.Name)
}


type Record struct {
    Type     string `store:"type,v"`// MX / A / CNAME / TXT / REDIR / SVR
    Name     string `store:"name,v"`// @ / www
    Host     string `store:"host,v"`// IP / address
    Priority int    `store:"priority,v"`// Used for MX
    Port     int    `store:"port,v"`// Used for SVR
}
func (r *Record) Id() string {
    return fmt.Sprintf("%p", r)
}
func (r *Record) Save() (string, error) {
    return GenericSave(r)
}
func (r *Record) Print() {
    fmt.Println("Record:",r.Type,r.Name,r.Host)
}


type Domain struct {
    Name    string    `store:"name,v"`
    User    *User     `store:"user,p"`    // User ID
    Records []*Record `store:"record,lp"` // Record ID's
}
func (d *Domain) Id() string {
    return fmt.Sprintf("%p", d)
}
func (d *Domain) Save() (string, error) {
    return GenericSave(d)
}
func (d *Domain) Print() {
    fmt.Println("Domain:",d.Name)
    d.User.Print()
    fmt.Println("Records:")
    for _, r := range d.Records {
        r.Print()
    }
}


type DBM struct {
    Domains []*Domain
    Users []*User
    Records []*Record
}
func (dbm *DBM) AddDomain(d *Domain) {
    dbm.Domains=append(dbm.Domains,d)
}
func (dbm *DBM) AddUser(u *User) {
    dbm.Users=append(dbm.Users,u)
}
func (dbm *DBM) AddRecord(r *Record) {
    dbm.Records=append(dbm.Records,r)
}
func (dbm *DBM) GetObjects() []Serializeable {
    objects:=[]Serializeable{}
    for _,r := range(dbm.Records) {
        objects=append(objects, r)
    }
    for _,u := range(dbm.Users) {
        objects=append(objects, u)
    }
    for _,d := range(dbm.Domains) {
        objects=append(objects, d)
    }
    return objects
}
func (dbm *DBM) SetObjects(objects []Serializeable) {
    for _,o := range(objects) {
        switch o.(type) {
        case *Record:
            fmt.Println("record")
            dbm.AddRecord(o.(*Record))
        case *User:
            fmt.Println("record")
            dbm.AddUser(o.(*User))
        case *Domain:
            fmt.Println("record")
            dbm.AddDomain(o.(*Domain))
        }
    }
}


func testState() {

    Register(User{})
    Register(Domain{})
    Register(Record{})

    dbm:=DBM{}

    u := &User{Name: "Martin", Email: "some@email.com"}
    dbm.AddUser(u)

    r1 := &Record{Name: "@", Type: "MX", Host: "mail.ishost.dk"}
    r2 := &Record{Name: "@", Type: "MX", Host: "mail.infoserv.dk"}
    dbm.AddRecord(r1)
    dbm.AddRecord(r2)

    d := &Domain{User:u, Name: "Martin", Records: []*Record{r1, r2}}
    dbm.AddDomain(d)

    x:=Save(dbm.GetObjects())

    fmt.Println("== Saved objects")
//  fmt.Println(string(x))

    fmt.Println("== Loading")

    dbm2:=DBM{}
    dbm2.SetObjects(Load(x))


    u2:=dbm2.Users[0]
    u2.Print()
    u2.Name="KURT"
    u2.Print()

    d2:=dbm2.Domains[0]
    d2.Print()
    d2.User.Name="ZIG"
    u2.Print()

}

func main() {
    testState()
}

关于go - 使用指针序列化结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56350257/

有关go - 使用指针序列化结构的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

随机推荐