implement json marshaling and unmarshaling (TODO needs tests)
This commit is contained in:
parent
e52befb7ed
commit
bd650dfc08
51
gg/gg.go
51
gg/gg.go
@ -157,6 +157,24 @@ func (g *Graph) cp() *Graph {
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Graph creation
|
||||
|
||||
func mkVertex(typ VertexType, val Value, ins []OpenEdge) vertex {
|
||||
v := vertex{VertexType: typ, in: ins}
|
||||
switch typ {
|
||||
case ValueVertex:
|
||||
v.id = val.ID
|
||||
v.val = val
|
||||
case JunctionVertex:
|
||||
inIDs := make([]string, len(ins))
|
||||
for i := range ins {
|
||||
inIDs[i] = ins[i].id()
|
||||
}
|
||||
v.id = "[" + strings.Join(inIDs, ",") + "]"
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown vertex type %q", typ))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ValueOut creates a OpenEdge which, when used to construct a Graph, represents
|
||||
// an edge (with edgeVal attached to it) coming from the ValueVertex containing
|
||||
// val.
|
||||
@ -165,14 +183,7 @@ func (g *Graph) cp() *Graph {
|
||||
// multiple ValueOut OpenEdges constructed with the same val will be leaving the
|
||||
// same Vertex instance in the constructed Graph.
|
||||
func ValueOut(val, edgeVal Value) OpenEdge {
|
||||
return OpenEdge{
|
||||
fromV: vertex{
|
||||
id: val.ID,
|
||||
VertexType: ValueVertex,
|
||||
val: val,
|
||||
},
|
||||
val: edgeVal,
|
||||
}
|
||||
return OpenEdge{fromV: mkVertex(ValueVertex, val, nil), val: edgeVal}
|
||||
}
|
||||
|
||||
// JunctionOut creates a OpenEdge which, when used to construct a Graph,
|
||||
@ -183,17 +194,9 @@ func ValueOut(val, edgeVal Value) OpenEdge {
|
||||
// edges. So multiple Junction OpenEdges constructed with the same set of input
|
||||
// edges will be leaving the same Junction instance in the constructed Graph.
|
||||
func JunctionOut(in []OpenEdge, edgeVal Value) OpenEdge {
|
||||
inIDs := make([]string, len(in))
|
||||
for i := range in {
|
||||
inIDs[i] = in[i].id()
|
||||
}
|
||||
return OpenEdge{
|
||||
fromV: vertex{
|
||||
id: "[" + strings.Join(inIDs, ",") + "]",
|
||||
VertexType: JunctionVertex,
|
||||
in: in,
|
||||
},
|
||||
val: edgeVal,
|
||||
fromV: mkVertex(JunctionVertex, Value{}, in),
|
||||
val: edgeVal,
|
||||
}
|
||||
}
|
||||
|
||||
@ -202,11 +205,7 @@ func JunctionOut(in []OpenEdge, edgeVal Value) OpenEdge {
|
||||
// referenced within toe OpenEdge which do not yet exist in the Graph will also
|
||||
// be created in this step.
|
||||
func (g *Graph) AddValueIn(oe OpenEdge, val Value) *Graph {
|
||||
to := vertex{
|
||||
id: val.ID,
|
||||
VertexType: ValueVertex,
|
||||
val: val,
|
||||
}
|
||||
to := mkVertex(ValueVertex, val, nil)
|
||||
toID := to.id
|
||||
|
||||
// if to is already in the graph, pull it out, as it might have existing in
|
||||
@ -254,11 +253,7 @@ func (g *Graph) AddValueIn(oe OpenEdge, val Value) *Graph {
|
||||
// OpenEdge, no changes are made. Any vertices referenced by toe OpenEdge for
|
||||
// which that edge is their only outgoing edge will be removed from the Graph.
|
||||
func (g *Graph) DelValueIn(oe OpenEdge, val Value) *Graph {
|
||||
to := vertex{
|
||||
id: val.ID,
|
||||
VertexType: ValueVertex,
|
||||
val: val,
|
||||
}
|
||||
to := mkVertex(ValueVertex, val, nil)
|
||||
toID := to.id
|
||||
|
||||
// pull to out of the graph. if it's not there then bail
|
||||
|
186
gg/json.go
Normal file
186
gg/json.go
Normal file
@ -0,0 +1,186 @@
|
||||
package gg
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type openEdgeJSON struct {
|
||||
From vertexJSON `json:"from"`
|
||||
ValueID string `json:"valueID"`
|
||||
}
|
||||
|
||||
type vertexJSON struct {
|
||||
Type VertexType `json:"type"`
|
||||
ValueID string `json:"valueID,omitempty"`
|
||||
In []openEdgeJSON `json:"in"`
|
||||
}
|
||||
|
||||
type graphJSON struct {
|
||||
Values map[string]json.RawMessage `json:"values"`
|
||||
ValueVertices []vertexJSON `json:"valueVertices"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface for a Graph. All Values
|
||||
// in the Graph will have json.Marshal called on them as-is in order to marshal
|
||||
// them.
|
||||
func (g *Graph) MarshalJSON() ([]byte, error) {
|
||||
gJ := graphJSON{
|
||||
Values: map[string]json.RawMessage{},
|
||||
ValueVertices: make([]vertexJSON, 0, len(g.vM)),
|
||||
}
|
||||
|
||||
withVal := func(val Value) (string, error) {
|
||||
if _, ok := gJ.Values[val.ID]; !ok {
|
||||
valJ, err := json.Marshal(val.V)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
gJ.Values[val.ID] = json.RawMessage(valJ)
|
||||
}
|
||||
return val.ID, nil
|
||||
}
|
||||
|
||||
// two locally defined, mutually recursive functions. This kind of thing
|
||||
// could probably be abstracted out, I feel like it happens frequently with
|
||||
// graph code.
|
||||
var mkIns func([]OpenEdge) ([]openEdgeJSON, error)
|
||||
var mkVert func(vertex) (vertexJSON, error)
|
||||
|
||||
mkIns = func(in []OpenEdge) ([]openEdgeJSON, error) {
|
||||
inJ := make([]openEdgeJSON, len(in))
|
||||
for i := range in {
|
||||
valID, err := withVal(in[i].val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vJ, err := mkVert(in[i].fromV)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inJ[i] = openEdgeJSON{From: vJ, ValueID: valID}
|
||||
}
|
||||
return inJ, nil
|
||||
}
|
||||
|
||||
mkVert = func(v vertex) (vertexJSON, error) {
|
||||
ins, err := mkIns(v.in)
|
||||
if err != nil {
|
||||
return vertexJSON{}, err
|
||||
}
|
||||
vJ := vertexJSON{
|
||||
Type: v.VertexType,
|
||||
In: ins,
|
||||
}
|
||||
if v.VertexType == ValueVertex {
|
||||
valID, err := withVal(v.val)
|
||||
if err != nil {
|
||||
return vJ, err
|
||||
}
|
||||
vJ.ValueID = valID
|
||||
}
|
||||
return vJ, nil
|
||||
}
|
||||
|
||||
for _, v := range g.vM {
|
||||
vJ, err := mkVert(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gJ.ValueVertices = append(gJ.ValueVertices, vJ)
|
||||
}
|
||||
|
||||
return json.Marshal(gJ)
|
||||
}
|
||||
|
||||
type jsonUnmarshaler struct {
|
||||
g *Graph
|
||||
fn func(json.RawMessage) (interface{}, error)
|
||||
}
|
||||
|
||||
// JSONUnmarshaler returns a json.Unmarshaler instance which, when used, will
|
||||
// unmarshal a json string into the Graph instance being called on here.
|
||||
//
|
||||
// The passed in function is used to unmarshal Values (used in both ValueVertex
|
||||
// vertices and edges) from json strings into go values. The returned inteface{}
|
||||
// should have already had the unmarshal from the given json string performed on
|
||||
// it.
|
||||
//
|
||||
// The json.Unmarshaler returned can be used many times, but will reset the
|
||||
// Graph completely before each use.
|
||||
func (g *Graph) JSONUnmarshaler(fn func(json.RawMessage) (interface{}, error)) json.Unmarshaler {
|
||||
return jsonUnmarshaler{g: g, fn: fn}
|
||||
}
|
||||
|
||||
func (jm jsonUnmarshaler) UnmarshalJSON(b []byte) error {
|
||||
*(jm.g) = Graph{}
|
||||
jm.g.vM = map[string]vertex{}
|
||||
|
||||
var gJ graphJSON
|
||||
if err := json.Unmarshal(b, &gJ); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vals := map[string]Value{}
|
||||
getVal := func(valID string) (Value, error) {
|
||||
if val, ok := vals[valID]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
j, ok := gJ.Values[valID]
|
||||
if !ok {
|
||||
return Value{}, fmt.Errorf("unmarshaling malformed graph, value with ID %q not defined", valID)
|
||||
}
|
||||
|
||||
V, err := jm.fn(j)
|
||||
if err != nil {
|
||||
return Value{}, err
|
||||
}
|
||||
|
||||
val := Value{ID: valID, V: V}
|
||||
vals[valID] = val
|
||||
return val, nil
|
||||
}
|
||||
|
||||
var mkIns func([]openEdgeJSON) ([]OpenEdge, error)
|
||||
var mkVert func(vertexJSON) (vertex, error)
|
||||
|
||||
mkIns = func(inJ []openEdgeJSON) ([]OpenEdge, error) {
|
||||
in := make([]OpenEdge, len(inJ))
|
||||
for i := range inJ {
|
||||
val, err := getVal(inJ[i].ValueID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err := mkVert(inJ[i].From)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
in[i] = OpenEdge{fromV: v, val: val}
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
mkVert = func(vJ vertexJSON) (vertex, error) {
|
||||
ins, err := mkIns(vJ.In)
|
||||
if err != nil {
|
||||
return vertex{}, err
|
||||
}
|
||||
var val Value
|
||||
if vJ.Type == ValueVertex {
|
||||
if val, err = getVal(vJ.ValueID); err != nil {
|
||||
return vertex{}, err
|
||||
}
|
||||
}
|
||||
return mkVertex(vJ.Type, val, ins), nil
|
||||
}
|
||||
|
||||
for _, v := range gJ.ValueVertices {
|
||||
v, err := mkVert(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jm.g.vM[v.id] = v
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user