dongzhang3482 2018-06-06 11:32
浏览 426
已采纳

在Golang中将JSONSchema解析为结构类型

So, my use case consists of parsing varying JSON schemas into new struct types, which will be further used with an ORM to fetch data from a SQL database. Being compiled in nature, I believe there will not be an out-of-the-box solution in go, but is there any hack available to do this, without creating a separate go process. I tried with reflection, but could not find a satisfactory approach.

Currently, I am using a-h generate library which does generate the structs, but I am stuck at how to load these new struct types in go runtime.

EDIT

Example JSON Schema:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Address",
  "id": "Address",
  "type": "object",
  "description": "address",
  "properties": {
    "houseName": {
      "type": "string",
      "description": "House Name",
      "maxLength": 30
    },
    "houseNumber": {
      "type": "string",
      "description": "House Number",
      "maxLength": 4
    },
    "flatNumber": {
      "type": "string",
      "description": "Flat",
      "maxLength": 15
    },
    "street": {
      "type": "string",
      "description": "Address 1",
      "maxLength": 40
    },
    "district": {
      "type": "string",
      "description": "Address 2",
      "maxLength": 30
    },
    "town": {
      "type": "string",
      "description": "City",
      "maxLength": 20
    },
    "county": {
      "type": "string",
      "description": "County",
      "maxLength": 20
    },
    "postcode": {
      "type": "string",
      "description": "Postcode",
      "maxLength": 8
    }
  }
}

Now, in the above-mentioned library, there is a command line tool, which generates the text for struct type for above json as below:

// Code generated by schema-generate. DO NOT EDIT.

package main

// Address address
type Address struct {
  County string `json:"county,omitempty"`
  District string `json:"district,omitempty"`
  FlatNumber string `json:"flatNumber,omitempty"`
  HouseName string `json:"houseName,omitempty"`
  HouseNumber string `json:"houseNumber,omitempty"`
  Postcode string `json:"postcode,omitempty"`
  Street string `json:"street,omitempty"`
  Town string `json:"town,omitempty"`
}

Now, the issue is that how to use this struct type without re-compilation in the program. There is a hack, where I can start a new go process, but that doesn't seem a good way to do it. One other way is to write my own parser for unmarshalling JSON schema, something like:

b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
var f interface{}
json.Unmarshal(b, &f)
m := f.(map[string]interface{})
for k, v := range m {
    switch vv := v.(type) {
    case string:
        fmt.Println(k, "is string", vv)
    case float64:
        fmt.Println(k, "is float64", vv)
    case int:
        fmt.Println(k, "is int", vv)
    case []interface{}:
        fmt.Println(k, "is an array:")
        for i, u := range vv {
            fmt.Println(i, u)
        }
    default:
        fmt.Println(k, "is of a type I don't know how to handle")
    }
}

Can someone please suggest some pointers to look for. Thanks.

  • 写回答

1条回答 默认 最新

  • duanpacan2583 2018-06-06 12:49
    关注

    So it looks like you're trying to implement your own json marshalling. That's no biggie: the standard json package already supports that. Just have your type implement the MarshalJSON and UnmarshalJSON functions (cf first example on the docs). Assuming some fields will be shared (eg schema, id, type), you can create a unified type like this:

    // poor naming, but we need this level of wrapping here
    type Data struct {
        Metadata
    }
    
    type Metadata struct {
        Schema string `json:"$schema"`
        Type string `json:"type"`
        Description string `json:"description"`
        Id string `json:"id"`
        Properties json.RawMessage `json:"properties"`
        Address *Address `json:"-"`
        // other types go here, too
    }
    

    Now all properties will be unmarshalled into a json.RawMessage field (essentially this is a []byte field). What you can do in your custom unmarshall function now is something like this:

    func (d *Data) UnmarshalJSON(b []byte) error {
        meta := Metadata{}
        // unmarshall common fields
        if err := json.Unmarshal(b, &meta); err != nil {
            return err
        }
        // Assuming the Type field contains the value that allows you to determine what data you're actually unmarshalling
        switch meta.Type {
        case "address":
            meta.Address = &Address{} // initialise field
            if err := json.Unmarshal([]byte(meta.Properties), meta.Address); err != nil {
                return err
            }
        case "name":
            meta.Name = &Name{}
            if err := json.Unmarshal([]byte(meta.Properties), meta.Name); err != nil {
                return err
            }
        default:
            return errors.New("unknown message type")
        }
        // all done
        d.Metadata = meta // assign to embedded
        // optionally: clean up the Properties field, as it contains raw JSON, and is exported
        d.Metadata.Properties = json.RawMessage{}
        return nil
    }
    

    You can do pretty much the same thing for marshalling. First work out what type you're actually working with, then marshal that object into the properties field, and then marhsal the entire structure

    func (d Data) MarshalJSON() ([]byte, error) {
        var (
            prop []byte
            err error
        )
        switch {
        case d.Metadata.Address != nil:
            prop, err = json.Marshal(d.Address)
        case d.Metadata.Name != nil:
            prop, err = json.Marshal(d.Name) // will only work if field isn't masked, better to be explicit
        default:
            err = errors.New("No properties to marshal") // handle in whatever way is best
        }
        if err != nil {
            return nil, err
        }
        d.Metadata.Properties = json.RawMessage(prop)
        return json.Marshal(d.Metadata) // marshal the unified type here
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 metadata提取的PDF元数据,如何转换为一个Excel
  • ¥15 关于arduino编程toCharArray()函数的使用
  • ¥100 vc++混合CEF采用CLR方式编译报错
  • ¥15 coze 的插件输入飞书多维表格 app_token 后一直显示错误,如何解决?
  • ¥15 vite+vue3+plyr播放本地public文件夹下视频无法加载
  • ¥15 c#逐行读取txt文本,但是每一行里面数据之间空格数量不同
  • ¥50 如何openEuler 22.03上安装配置drbd
  • ¥20 ING91680C BLE5.3 芯片怎么实现串口收发数据
  • ¥15 无线连接树莓派,无法执行update,如何解决?(相关搜索:软件下载)
  • ¥15 Windows11, backspace, enter, space键失灵