Complete package for generating ocds structs (with extensions) in golang
This commit is contained in:
1 parent
da726550d4
commit
35974fa070
47 files changed
+9861
-299
No files matched your search
@@ -0,0 +1,149 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Obj is a JSON object that remembers key order, so generated structs list
|
||||
// fields in the same order as the schema rather than alphabetically.
|
||||
type Obj struct {
|
||||
Keys []string
|
||||
M map[string]any
|
||||
}
|
||||
|
||||
func newObj() *Obj { return &Obj{M: map[string]any{}} }
|
||||
|
||||
func (o *Obj) Get(k string) any { return o.M[k] }
|
||||
|
||||
func (o *Obj) Set(k string, v any) {
|
||||
if _, ok := o.M[k]; !ok {
|
||||
o.Keys = append(o.Keys, k)
|
||||
}
|
||||
o.M[k] = v
|
||||
}
|
||||
|
||||
func (o *Obj) Delete(k string) {
|
||||
if _, ok := o.M[k]; !ok {
|
||||
return
|
||||
}
|
||||
delete(o.M, k)
|
||||
for i, key := range o.Keys {
|
||||
if key == k {
|
||||
o.Keys = append(o.Keys[:i], o.Keys[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Obj) Obj(k string) *Obj {
|
||||
v, _ := o.M[k].(*Obj)
|
||||
return v
|
||||
}
|
||||
|
||||
func (o *Obj) Str(k string) string {
|
||||
v, _ := o.M[k].(string)
|
||||
return v
|
||||
}
|
||||
|
||||
// decodeOrdered parses JSON, returning *Obj for objects, []any for arrays,
|
||||
// and string / json.Number / bool / nil for scalars.
|
||||
func decodeOrdered(r io.Reader) (any, error) {
|
||||
dec := json.NewDecoder(r)
|
||||
dec.UseNumber()
|
||||
return parseValue(dec)
|
||||
}
|
||||
|
||||
func parseValue(dec *json.Decoder) (any, error) {
|
||||
tok, err := dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch t := tok.(type) {
|
||||
case json.Delim:
|
||||
switch t {
|
||||
case '{':
|
||||
o := newObj()
|
||||
for dec.More() {
|
||||
kt, err := dec.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k, ok := kt.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected object key, got %v", kt)
|
||||
}
|
||||
v, err := parseValue(dec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.Set(k, v)
|
||||
}
|
||||
_, err := dec.Token() // '}'
|
||||
return o, err
|
||||
case '[':
|
||||
arr := []any{}
|
||||
for dec.More() {
|
||||
v, err := parseValue(dec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr = append(arr, v)
|
||||
}
|
||||
_, err := dec.Token() // ']'
|
||||
return arr, err
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected delimiter %v", t)
|
||||
default:
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
|
||||
// mergePatch applies an RFC 7386 JSON Merge Patch, which is how OCDS
|
||||
// extensions are applied to the core schema. A null in the patch deletes
|
||||
// the key; objects merge recursively; anything else replaces.
|
||||
func mergePatch(target, patch any) any {
|
||||
p, ok := patch.(*Obj)
|
||||
if !ok {
|
||||
return patch
|
||||
}
|
||||
t, ok := target.(*Obj)
|
||||
if !ok {
|
||||
t = newObj()
|
||||
}
|
||||
for _, k := range p.Keys {
|
||||
v := p.M[k]
|
||||
if v == nil {
|
||||
t.Delete(k)
|
||||
continue
|
||||
}
|
||||
t.Set(k, mergePatch(t.M[k], v))
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// MarshalJSON writes the object with its keys in their original order.
|
||||
func (o *Obj) MarshalJSON() ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
b.WriteByte('{')
|
||||
for i, k := range o.Keys {
|
||||
if i > 0 {
|
||||
b.WriteByte(',')
|
||||
}
|
||||
kb, err := json.Marshal(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vb, err := json.Marshal(o.M[k])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.Write(kb)
|
||||
b.WriteByte(':')
|
||||
b.Write(vb)
|
||||
}
|
||||
b.WriteByte('}')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
Reference in new issue
Block a user