From bd650dfc08add227200a59b930674ce31fce7300 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Tue, 23 Jan 2018 13:32:11 +0000 Subject: [PATCH] implement json marshaling and unmarshaling (TODO needs tests) --- gg/gg.go | 51 +++++++-------- gg/json.go | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+), 28 deletions(-) create mode 100644 gg/json.go diff --git a/gg/gg.go b/gg/gg.go index e0f9800..950d3c8 100644 --- a/gg/gg.go +++ b/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 diff --git a/gg/json.go b/gg/json.go new file mode 100644 index 0000000..5c7ae61 --- /dev/null +++ b/gg/json.go @@ -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 +}