implement json marshaling and unmarshaling (TODO needs tests)
This commit is contained in:
parent
e52befb7ed
commit
bd650dfc08
49
gg/gg.go
49
gg/gg.go
@ -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
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