implement json marshaling and unmarshaling (TODO needs tests)

This commit is contained in:
Brian Picciano 2018-01-23 13:32:11 +00:00
parent e52befb7ed
commit bd650dfc08
2 changed files with 209 additions and 28 deletions

View File

@ -157,6 +157,24 @@ func (g *Graph) cp() *Graph {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Graph creation // 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 // ValueOut creates a OpenEdge which, when used to construct a Graph, represents
// an edge (with edgeVal attached to it) coming from the ValueVertex containing // an edge (with edgeVal attached to it) coming from the ValueVertex containing
// val. // val.
@ -165,14 +183,7 @@ func (g *Graph) cp() *Graph {
// multiple ValueOut OpenEdges constructed with the same val will be leaving the // multiple ValueOut OpenEdges constructed with the same val will be leaving the
// same Vertex instance in the constructed Graph. // same Vertex instance in the constructed Graph.
func ValueOut(val, edgeVal Value) OpenEdge { func ValueOut(val, edgeVal Value) OpenEdge {
return OpenEdge{ return OpenEdge{fromV: mkVertex(ValueVertex, val, nil), val: edgeVal}
fromV: vertex{
id: val.ID,
VertexType: ValueVertex,
val: val,
},
val: edgeVal,
}
} }
// JunctionOut creates a OpenEdge which, when used to construct a Graph, // JunctionOut creates a OpenEdge which, when used to construct a Graph,
@ -183,16 +194,8 @@ func ValueOut(val, edgeVal Value) OpenEdge {
// edges. So multiple Junction OpenEdges constructed with the same set of input // edges. So multiple Junction OpenEdges constructed with the same set of input
// edges will be leaving the same Junction instance in the constructed Graph. // edges will be leaving the same Junction instance in the constructed Graph.
func JunctionOut(in []OpenEdge, edgeVal Value) OpenEdge { func JunctionOut(in []OpenEdge, edgeVal Value) OpenEdge {
inIDs := make([]string, len(in))
for i := range in {
inIDs[i] = in[i].id()
}
return OpenEdge{ return OpenEdge{
fromV: vertex{ fromV: mkVertex(JunctionVertex, Value{}, in),
id: "[" + strings.Join(inIDs, ",") + "]",
VertexType: JunctionVertex,
in: in,
},
val: edgeVal, 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 // referenced within toe OpenEdge which do not yet exist in the Graph will also
// be created in this step. // be created in this step.
func (g *Graph) AddValueIn(oe OpenEdge, val Value) *Graph { func (g *Graph) AddValueIn(oe OpenEdge, val Value) *Graph {
to := vertex{ to := mkVertex(ValueVertex, val, nil)
id: val.ID,
VertexType: ValueVertex,
val: val,
}
toID := to.id toID := to.id
// if to is already in the graph, pull it out, as it might have existing in // 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 // 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. // which that edge is their only outgoing edge will be removed from the Graph.
func (g *Graph) DelValueIn(oe OpenEdge, val Value) *Graph { func (g *Graph) DelValueIn(oe OpenEdge, val Value) *Graph {
to := vertex{ to := mkVertex(ValueVertex, val, nil)
id: val.ID,
VertexType: ValueVertex,
val: val,
}
toID := to.id toID := to.id
// pull to out of the graph. if it's not there then bail // pull to out of the graph. if it's not there then bail

186
gg/json.go Normal file
View 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
}