In the last weeks I have worked a bit on a CAD like program of which I created the initial Ruby version a decades ago:
https://github.com/StefanSalewski/SDT
And yesterday I tried to save the objects to disc for the first time. I am aware that Nim's JSON serialization does not work for heterogeneous sequences where the dynamic runtime element type is different from the parent type. But I had the feeling that it would work as in the first example from http://ssalewski.de/nimprogramming.html#_serializationstoring_data_permanently_on_external_storage
But the problem seems to be the tuples -- using the json module works only when I uncomment the field "p: seq[V2]"
import json, marshal
type
V2 = tuple[x, y: float]
type
Element = ref object of RootRef
# style: Styles
#p: seq[V2]
at: seq[Text] # attached text
hover: bool
selected: bool
gx, gy: int # text grab
isNew: bool
# type
Text = ref object of Element
text: string
parent: Element # new, for easy reattaching, and maybe a graphical parent indication (arrow)
detached: bool # maybe with new parent field this boolean is redundant.
sizeInPixel: bool
type
Line = ref object of Element
type
Trace = ref object of Element
type
Rect = ref object of Element
type
Circ = ref object of Element
type
Pad = ref object of Element
cornerRadius: float
type
Path = ref object of Element
type
Pin = ref object of Element
type
Group = ref object of Element
lines: seq[Line]
circs: seq[Circ]
texts: seq[Text]
rects: seq[Rect]
pads: seq[Pad]
paths: seq[Path]
pins: seq[Pin]
traces: seq[Trace]
proc main =
discard
var g = Group()
var l = Line()
#l.p.add((1.0,2.0))
echo marshal.`$$`(g)
echo ""
echo json.pretty(json.`%*`(g))
main()
[140376818196560, {"lines": [], "circs": [], "texts": [], "rects": [], "pads": [], "paths": [], "pins": [], "traces": [], "at": [], "hover": false, "selected": false, "gx": 0, "gy": 0, "isNew": false}]
{
"lines": [],
"circs": [],
"texts": [],
"rects": [],
"pads": [],
"paths": [],
"pins": [],
"traces": [],
"at": [],
"hover": false,
"selected": false,
"gx": 0,
"gy": 0,
"isNew": false
}
The marshal module can process the tuple field, but the output it ugly and is seems to print the addresses of ref objects, which is strange, I would guess that marshal can not load ref objects at all?
I tried also the YAML module, but got it not compiling with my sdt.nim app.
Google found two closed issues about JSON and tuples, but I still do not understand why JSON can not work with tuples.
So what is an easy working solution to store the Group ref object from above to disk and reload it again later?
I could convert the V2 tuple to an array, would it work than? Would be some work of course -- I was using a tuple because for tuples automatic unpacking works, which arrays do not support. And because access by name like .x can be sometimes nicer than access by subscript [0] only.
Another related question is, if there is a chance at all that after reloading the data the parent field of the Text ref object can have the correct ref value again? Element instances have a seq of attached text of type Text, and that Text has a parent field which contains a ref to the Element containing the seq. I assume that such back references are not supported by all the serialization modules, so I would have to update these fields manually?
References:
After replacing the tuple with array it seems to work, at least the serialization.
We get an output from pretty(%* g) like
saveGroup
{
"lines": [
{
"style": "medium",
"p": [
[
370.0,
190.0
],
[
430.0,
220.0
]
],
"at": [],
"hover": true,
"selected": true,
"gx": 0,
"gy": 0,
"isNew": false
}
],
"circs": [],
"texts": [],
"rects": [],
"pads": [],
"paths": [],
"pins": [
{
"style": "pin",
"p": [
[
350.0,
250.0
],
[
380.0,
250.0
]
],
"at": [
{
"text": "13",
"parent": null,
"detached": false,
"sizeInPixel": true,
"style": "medium",
"p": [
[
378.0,
250.0
],
[
390.724609375,
261.640625
],
[
365.275390625,
238.359375
],
[
371.6376953125,
238.359375
],
[
378.0,
238.359375
],
[
365.275390625,
244.1796875
],
[
371.6376953125,
244.1796875
],
[
378.0,
244.1796875
],
[
365.275390625,
250.0
],
[
371.6376953125,
250.0
],
[
378.0,
250.0
]
],
"at": [],
"hover": false,
"selected": false,
"gx": 2,
"gy": 2,
"isNew": false
},
{
"text": "Name",
"parent": null,
"detached": false,
"sizeInPixel": true,
"style": "medium",
"p": [
[
382.0,
250.0
],
[
412.1123046875,
261.640625
],
[
382.0,
244.1796875
],
[
397.05615234375,
244.1796875
],
[
412.1123046875,
244.1796875
],
[
382.0,
250.0
],
[
397.05615234375,
250.0
],
[
412.1123046875,
250.0
],
[
382.0,
255.8203125
],
[
397.05615234375,
255.8203125
],
[
412.1123046875,
255.8203125
]
],
"at": [],
"hover": false,
"selected": false,
"gx": 0,
"gy": 1,
"isNew": false
}
],
"hover": true,
"selected": true,
"gx": 0,
"gy": 0,
"isNew": false
}
],
"traces": [],
"style": "medium",
"p": [
[
350.0,
190.0
],
[
430.0,
250.0
]
],
"at": [],
"hover": true,
"selected": true,
"gx": 0,
"gy": 0,
"isNew": false
}
But for that a lot of changes were necessary. So whenever we intent to use JSON, we should better avoid tuples from the beginning:
$ diff sdt.nim sdt.nim.bak1
15,17c15
< import std/[times, parseutils, strutils, strformat, strscans, json]#, json, jsonutils]
< #import yaml/serialization, streams
<
---
> import std/[times, parseutils, strutils, strformat, strscans, json, jsonutils]
20,21c18
< import gintro/[gtk4, gdk4, glib, gobject, cairo, pango, pangocairo]
< import gintro/gio except hash
---
> import gintro/[gtk4, gdk4, glib, gobject, gio, cairo, pango, pangocairo]
202,203c199
< #V2 = tuple[x, y: float]
< V2 = array[2, float]
---
> V2 = tuple[x, y: float]
209,210c205,206
< a[0] += b[0]
< a[1] += b[1]
---
> a.x += b.x
> a.y += b.y
213c209
< [a[0] + b[0], a[1] + b[1]]
---
> (a.x + b.x, a.y + b.y)
216c212
< [a[0] - b[0], a[1] - b[1]]
---
> (a.x - b.x, a.y - b.y)
262c258
< let (x1, y1, x2, y2, x3, y3) = (p1[0], p1[1], p2[0], p2[1], p[0], p[1])
---
> let (x1, y1, x2, y2, x3, y3) = (p1.x, p1.y, p2.x, p2.y, p.x, p.y)
350,353c346,349
< template x1(e: Element): float = e.p[0][0]
< template y1(e: Element): float = e.p[0][1]
< template x2(e: Element): float = e.p[1][0]
< template y2(e: Element): float = e.p[1][1]
---
> template x1(e: Element): float = e.p[0].x
> template y1(e: Element): float = e.p[0].y
> template x2(e: Element): float = e.p[1].x
> template y2(e: Element): float = e.p[1].y
417,418c413,414
< (result[0][0], result[1][0]) = sortedTuple(p1[0], p2[0])
< (result[0][1], result[1][1]) = sortedTuple(p1[1], p2[1])
---
> (result[0].x, result[1].x) = sortedTuple(p1.x, p2.x)
> (result[0].y, result[1].y) = sortedTuple(p1.y, p2.y)
460,461c456,457
< pin.at[PinNamePos].p[0] = [x + d, y]
< pin.at[PinNamePos].p[1] = [x + d, y]
---
> pin.at[PinNamePos].p[0] = (x + d, y)
> pin.at[PinNamePos].p[1] = (x + d, y)
472,473c468,469
< pin.at[PinNumberPos].p[0] = [x - d, y]
< pin.at[PinNumberPos].p[1] = [x - d, y]
---
> pin.at[PinNumberPos].p[0] = (x - d, y)
> pin.at[PinNumberPos].p[1] = (x - d, y)
487,488c483,484
< pin.at[PinNamePos].p[0] = [x, y + d]
< pin.at[PinNamePos].p[1] = [x, y + d]
---
> pin.at[PinNamePos].p[0] = (x, y + d)
> pin.at[PinNamePos].p[1] = (x, y + d)
499,500c495,496
< pin.at[PinNumberPos].p[0] = [x, y - d]
< pin.at[PinNumberPos].p[1] = [x, y - d]
---
> pin.at[PinNumberPos].p[0] = (x, y - d)
> pin.at[PinNumberPos].p[1] = (x, y - d)
531c527
< [(x1, y1, x1, y2), (x1, y1, x2, y1), (x2, y2, x1, y2), (x2, y2, x2, y1)].mapIt(distanceLinePointSqr([it[0], it[1]], [it[2], it[3]], xy)[1]).min
---
> [(x1, y1, x1, y2), (x1, y1, x2, y1), (x2, y2, x1, y2), (x2, y2, x2, y1)].mapIt(distanceLinePointSqr((it[0], it[1]), (it[2], it[3]), xy)[1]).min
534c530
< if (xy[0] > x1 and xy[0] < x2) and (xy[1] > y1 and xy[1] < y2): # in rectangle
---
> if (xy.x > x1 and xy.x < x2) and (xy.y > y1 and xy.y < y2): # in rectangle
545c541
< max(math.hypot(c.x1 - xy[0], c.y1 - xy[1]) - math.hypot(c.x1 - c.x2, c.y1 - c.y2), 0) ^ 2
---
> max(math.hypot(c.x1 - xy.x, c.y1 - xy.y) - math.hypot(c.x1 - c.x2, c.y1 - c.y2), 0) ^ 2
548,551c544,547
< var (x, y) = (xy[0], xy[1])
< x += (t.p[1][0] - t.p[0][0]) * (t.gx mod 3).float * 0.5
< y += (t.p[1][1] - t.p[0][1]) * (t.gy mod 3).float * 0.5
< sqrDistanceR(t.x1, t.y1, t.x2, t.y2, [x, y]) # caution, this is not xy!
---
> var (x, y) = xy
> x += (t.p[1].x - t.p[0].x) * (t.gx mod 3).float * 0.5
> y += (t.p[1].y - t.p[0].y) * (t.gy mod 3).float * 0.5
> sqrDistanceR(t.x1, t.y1, t.x2, t.y2, (x, y)) # caution, this is not xy!
725,726c721,722
< [(v[0] - pda.hadjustment.upper * 0.5 + pda.hadjustment.value) / (pda.fullScale * pda.userZoom) + pda.dataX + pda.dataWidth * 0.5,
< (v[1] - pda.vadjustment.upper * 0.5 + pda.vadjustment.value) / (pda.fullScale * pda.userZoom) + pda.dataY + pda.dataHeight * 0.5]
---
> ((v.x - pda.hadjustment.upper * 0.5 + pda.hadjustment.value) / (pda.fullScale * pda.userZoom) + pda.dataX + pda.dataWidth * 0.5,
> (v.y - pda.vadjustment.upper * 0.5 + pda.vadjustment.value) / (pda.fullScale * pda.userZoom) + pda.dataY + pda.dataHeight * 0.5)
732c728
< [roundToMultiple(v[0], pda.activeGrid[0]), roundToMultiple(v[1], pda.activeGrid[1])]
---
> (roundToMultiple(v.x, pda.activeGrid.x), roundToMultiple(v.y, pda.activeGrid.y))
741c737
< let (a, b) = (pda.lastButtonDownPosUser[0], pda.lastButtonDownPosUser[1]) #.x, pda.lastButtonDownPosUser.y)
---
> let (a, b) = (pda.lastButtonDownPosUser) #.x, pda.lastButtonDownPosUser.y)
747c743
< let i = minIndexByIt(l.p, math.hypot(a - it[0], b - it[1]))
---
> let i = minIndexByIt(l.p, math.hypot(a - it.x, b - it.y))
749c745
< if (a - p[0]) ^ 2 + (b - p[1]) ^ 2 < (pda.absToScr(GrabDist)) ^ 2:
---
> if (a - p.x) ^ 2 + (b - p.y) ^ 2 < (pda.absToScr(GrabDist)) ^ 2:
812,815c808,809
< var h = getUserCoordinates(pda, [0.0, 0.0])
< var (x1, y1) = (h[0], h[1])
< h = getUserCoordinates(pda, [pda.darea.allocatedWidth.float, pda.darea.allocatedHeight.float])
< var (x2, y2) = (h[0], h[1])
---
> var (x1, y1) = getUserCoordinates(pda, (0.0, 0.0))
> var (x2, y2) = getUserCoordinates(pda, (pda.darea.allocatedWidth.float, pda.darea.allocatedHeight.float))
881,882c875,876
< let dx = -(t.p[1][0] - t.p[0][0]) * (t.gx mod 3).float * 0.5
< let dy = -(t.p[1][1] - t.p[0][1]) * (t.gy mod 3).float * 0.5
---
> let dx = -(t.p[1].x - t.p[0].x) * (t.gx mod 3).float * 0.5
> let dy = -(t.p[1].y - t.p[0].y) * (t.gy mod 3).float * 0.5
886c880
< var (xa, xb, ya, yb) = (l.p[0][0], l.p[0][0], l.p[0][1], l.p[0][1])
---
> var (xa, xb, ya, yb) = (l.p[0].x, l.p[0].x, l.p[0].y, l.p[0].y)
888,891c882,885
< xa = min(xa, el[0])
< xb = max(xb, el[0])
< ya = min(ya, el[1])
< yb = max(yb, el[1])
---
> xa = min(xa, el.x)
> xb = max(xb, el.x)
> ya = min(ya, el.y)
> yb = max(yb, el.y)
971,974c965
< #var s = newFileStream("out.yaml", fmWrite)
< #dump(g, s)
< #s.close()
< #echo $$g
---
>
990c981
< var g = Group(p: @[[box[0].a, box[1].a], [box[0].b, box[1].b]])
---
> var g = Group(p: @[(box[0].a, box[1].a), (box[0].b, box[1].b)])
1017c1008
< pda.cr.lineTo(p[0], p[1])
---
> pda.cr.lineTo(p.x, p.y)
1045c1036
< t.p[1] = t.p[0] + [width, height]
---
> t.p[1] = t.p[0] + (width, height)
1059c1050
< t.grabPos(i) = [x + dx, y + dy]
---
> t.grabPos(i) = (x + dx, y + dy)
1167,1168c1158,1159
< let width = t.p[1][0] - t.p[0][0]
< let height = t.p[1][1] - t.p[0][1]
---
> let width = t.p[1].x - t.p[0].x
> let height = t.p[1].y - t.p[0].y
1174c1165
< pda.drawGrabCirc(t.grabPos(i)[0], t.grabPos(i)[1])
---
> pda.drawGrabCirc(t.grabPos(i).x, t.grabPos(i).y)
1205,1211c1196,1202
< let h = math.hypot(b[0] - a[0], b[1] - a[1])
< let dx = pda.absToScr((b[1] - a[1]) / h * GrabDist)
< let dy = pda.absToScr(-(b[0] - a[0]) / h * GrabDist)
< pda.cr.moveTo(a[0] + dx, a[1] + dy)
< pda.cr.lineTo(b[0] + dx, b[1] + dy)
< pda.cr.moveTo(a[0] - dx, a[1] - dy)
< pda.cr.lineTo(b[0] - dx, b[1] - dy)
---
> let h = math.hypot(b.x - a.x, b.y - a.y)
> let dx = pda.absToScr((b.y - a.y) / h * GrabDist)
> let dy = pda.absToScr(-(b.x - a.x) / h * GrabDist)
> pda.cr.moveTo(a.x + dx, a.y + dy)
> pda.cr.lineTo(b.x + dx, b.y + dy)
> pda.cr.moveTo(a.x - dx, a.y - dy)
> pda.cr.lineTo(b.x - dx, b.y - dy)
1214c1205
< pda.drawGrabCirc(p[0], p[1])
---
> pda.drawGrabCirc(p.x, p.y)
1307c1298
< sqrDistance(el.l, [qo[0], qo[1]])
---
> sqrDistance(el.l, (qo[0], qo[1]))
1412,1413c1403,1404
< cr.rectangle(pda.lastButtonDownPos[0], pda.lastButtonDownPos[1], pda.zoomRect[0] - pda.lastButtonDownPos[0], pda.zoomRect[1] -
< pda.lastButtonDownPos[1])
---
> cr.rectangle(pda.lastButtonDownPos.x, pda.lastButtonDownPos.y, pda.zoomRect.x - pda.lastButtonDownPos.x, pda.zoomRect.y -
> pda.lastButtonDownPos.y)
1481,1484c1472,1474
< let x = pda.legEvXY[0]
< let y = pda.legEvXY[1]
< let h = pda.getUserCoordinates(pda.legEvXY)
< let (a, b) = (h[0], h[1])
---
> let x = pda.legEvXY.x
> let y = pda.legEvXY.y
> let (a, b) = pda.getUserCoordinates(pda.legEvXY)
1491c1481
< if math.hypot(x - pda.lastButtonDownPos[0], y - pda.lastButtonDownPos[1]) > 2:
---
> if math.hypot(x - pda.lastButtonDownPos.x, y - pda.lastButtonDownPos.y) > 2:
1504c1494
< if sqrDistance(el, [a, b]) < (pda.absToScr(GrabDist)) ^ 2:
---
> if sqrDistance(el, (a, b)) < (pda.absToScr(GrabDist)) ^ 2:
1512c1502
< pda.zoomRect = [x, y]
---
> pda.zoomRect = (x, y)
1515,1516c1505,1506
< pda.updateAdjustmentsAndPaint(pda.lastMousePos[0] - x, pda.lastMousePos[1] - y)
< pda.lastMousePos = [x, y]
---
> pda.updateAdjustmentsAndPaint(pda.lastMousePos.x - x, pda.lastMousePos.y - y)
> pda.lastMousePos = (x, y)
1519c1509
< let p = pda.roundToGrid([a, b])
---
> let p = pda.roundToGrid((a, b))
1555,1556c1545,1546
< let x = pda.legEvXY[0]
< let y = pda.legEvXY[1]
---
> let x = pda.legEvXY.x
> let y = pda.legEvXY.y
1568c1558
< (pda.lastButtonDownPosUser[0], pda.lastButtonDownPosUser[1]) = pda.getUserCoordinates(pda.legEvXY)
---
> (pda.lastButtonDownPosUser.x, pda.lastButtonDownPosUser.y) = pda.getUserCoordinates(pda.legEvXY)
1582,1583c1572,1573
< let x = pda.legEvXY[0]
< let y = pda.legEvXY[1]
---
> let x = pda.legEvXY.x
> let y = pda.legEvXY.y
1597,1598c1587,1588
< let width = pda.hover.p[1][0] - pda.hover.p[0][0]
< let height = pda.hover.p[1][1] - pda.hover.p[0][1]
---
> let width = pda.hover.p[1].x - pda.hover.p[0].x
> let height = pda.hover.p[1].y - pda.hover.p[0].y
1602,1604c1592,1593
< let h = pda.getUserCoordinates([x, y])
< let (x, y) = (h[0], h[1])
< if (x - pda.hover.grabPos(i)[0]) ^ 2 + (y - pda.hover.grabPos(i)[1]) ^ 2 < pda.absToScr(GrabDist) ^ 2:
---
> let (x, y) = pda.getUserCoordinates((x, y))
> if (x - pda.hover.grabPos(i).x) ^ 2 + (y - pda.hover.grabPos(i).y) ^ 2 < pda.absToScr(GrabDist) ^ 2:
1612c1601
< var dxdy = pda.roundToGrid([olddx - dx, olddy - dy])
---
> var dxdy = pda.roundToGrid((olddx - dx, olddy - dy))
1635c1624
< let uc = pda.getUserCoordinates([xy[0], xy[1]])
---
> let uc = pda.getUserCoordinates((xy[0], xy[1]))
1699,1700c1688,1689
< let (x1, y1, x2, y2) = (l.p[0][0], l.p[0][1], l.p[1][0], l.p[1][1])
< l.at[PadNamePos] = newText([(x1 + x2) * 0.5, (y1 + y2) * 0.5], [x2, y2], "Name")
---
> let (x1, y1, x2, y2) = (l.p[0].x, l.p[0].y, l.p[1].x, l.p[1].y)
> l.at[PadNamePos] = newText(((x1 + x2) * 0.5, (y1 + y2) * 0.5), (x2, y2), "Name")
1702c1691
< l.at[PadNumberPos] = newText([(x1 + x2) * 0.5, (y1 + y2) * 0.5], [x2, y2], "Num")
---
> l.at[PadNumberPos] = newText(((x1 + x2) * 0.5, (y1 + y2) * 0.5), (x2, y2), "Num")
1719c1708
< pda.legEvXY = [x, y]
---
> pda.legEvXY = (x, y)
1757,1759c1746,1748
< let pad = newPad([x1, y1], [x2, y2])
< pad.at[PadNamePos] = newText([(x1 + x2) * 0.5, (y1 + y2) * 0.5], [x2, y2], name)
< pad.at[PadNumberPos] = newText([(x1 + x2) * 0.5, (y1 + y2) * 0.5], [x2, y2], $num)
---
> let pad = newPad((x1, y1), (x2, y2))
> pad.at[PadNamePos] = newText(((x1 + x2) * 0.5, (y1 + y2) * 0.5), (x2, y2), name)
> pad.at[PadNumberPos] = newText(((x1 + x2) * 0.5, (y1 + y2) * 0.5), (x2, y2), $num)
1787c1776
< let pad = newPin([x1, y1], [x2, y2])
---
> let pad = newPin((x1, y1), (x2, y2))
1834c1823
< pda.activeGrid = [pda.grid[i], pda.grid[i + 2]]
---
> pda.activeGrid = (pda.grid[i], pda.grid[i + 2])
2025,2026c2014,2015
< result.activeGrid[0] = DefaultGrid[0]
< result.activeGrid[1] = DefaultGrid[2]
---
> result.activeGrid.x = DefaultGrid[0]
> result.activeGrid.y = DefaultGrid[2]
Use toJson from std/jsonutils. https://nim-lang.org/docs/jsonutils.html
I saw that module already. But with the example from above and
let j =g.toJson
I get
There is even one more JSON module:
https://github.com/status-im/nim-json-serialization
But that one seems to have no documentation.
I think I will use Nim JSON now, with array avoiding tuples.
Ah wait, not even the first example from
https://nim-lang.org/docs/jsonutils.html
import std/[strtabs,json]
type Foo = ref object
t: bool
z1: int8
let a = (1.5'f32, (b: "b2", a: "a2"), 'x', @[Foo(t: true, z1: -3), nil], [{"name": "John"}.newStringTable])
let j = a.toJson
doAssert j.jsonTo(type(a)).toJson == j
compiles for me:
nim c hhhhh.nim
Hint: used config file '/home/salewski/Nim/config/nim.cfg' [Conf]
Hint: used config file '/home/salewski/Nim/config/config.nims' [Conf]
.................................................................................................
/home/salewski/SDTold/hhhhh.nim(6, 10) Error: undeclared field: 'toJson'
Strange, I must do somethink wrong :-(
Ah, I took the import statement from the example, seem JsonUtils was missing. With
import std/[strtabs,json, jsonutils]
I get
{"lines":[],"circs":[],"texts":[],"rects":[],"pads":[],"paths":[],"pins":[],"traces":[],"p":[],"at":[],"hover":false,"selected":false,"gx":0,"gy":0,"isNew":false}
Thanks for that example. Indeed it works -- as I wrote in my last post, my issue was that I took the import statement verbatim from the first JsonUtils API doc example:
import std/[strtabs,json]
You example gives this output:
[139991162843216, {"lines": [], "circs": [], "texts": [], "rects": [], "pads": [], "paths": [], "pins": [], "traces": [], "p": [{"Field0": 1.0, "Field1": 2.0}], "at": [], "hover": false, "selected": false, "gx": 0, "gy": 0, "isNew": false}]
{
"lines": [],
"circs": [],
"texts": [],
"rects": [],
"pads": [],
"paths": [],
"pins": [],
"traces": [],
"p": [
{
"x": 1.0,
"y": 2.0
}
],
"at": [],
"hover": false,
"selected": false,
"gx": 0,
"gy": 0,
"isNew": false
}
That is fine. I was not aware that json.pretty() can be used with module jsonutils.
The whole JSON stuff was really very confusing for me: Four modules which do very similar tasks: JSON, JsonUtils, Marshal, and finally from status corp. https://github.com/status-im/nim-json-serialization, and additional https://nimyaml.org/
But I think it is solved now -- JsonUtils works fine, and JSON with array instead of tuple too. Thanks.
And for my other question with back references -- for JsonUtils serialization already gives a crash at runtime, so it makes no sense to ask if deserialization would work:
import json, marshal, std/jsonutils
type
V2 = tuple[x, y: float]
type
Element = ref object of RootRef
# style: Styles
p: seq[V2]
at: seq[Text] # attached text
hover: bool
selected: bool
gx, gy: int # text grab
isNew: bool
# type
Text = ref object of Element
text: string
parent: Element # new, for easy reattaching, and maybe a graphical parent indication (arrow)
detached: bool # maybe with new parent field this boolean is redundant.
sizeInPixel: bool
type
Line = ref object of Element
type
Trace = ref object of Element
type
Rect = ref object of Element
type
Circ = ref object of Element
type
Pad = ref object of Element
cornerRadius: float
type
Path = ref object of Element
type
Pin = ref object of Element
type
Group = ref object of Element
lines: seq[Line]
circs: seq[Circ]
texts: seq[Text]
rects: seq[Rect]
pads: seq[Pad]
paths: seq[Path]
pins: seq[Pin]
traces: seq[Trace]
proc main =
var g = Group()
var t = Text(text: "Nim")
var l = Line()
t.parent = l # back reference
l.at.add(t) # attached text
l.p.add((1.0,2.0))
g.lines.add(l)
echo json.pretty(toJson(g))
main()
The critical line is
t.parent = l # back reference
First of since you rely in oop heavily, I should warn you about the limitation marshal and friends have:
Restriction: For objects, their type is not serialized. This means essentially that it does not work if the object has some other runtime type than its compiletime type.
Also cyclic objects would probably blow up all of them, meaning you should use the json module and write your (de)serialization functions by hand.
A faster alternative is probably https://github.com/OpenSystemsLab/jsmn.nim and derivatives https://github.com/OpenSystemsLab/sam.nim , https://github.com/gabbhack/deser_json.
...and a bit of useless promotion :) I have made yet another json lib https://github.com/planetis-m/jsonecs this one is about 20% faster and uses 30% more space than std/json.
Restriction: For objects, their type is not serialized. This means essentially that it does not work if the object has some other runtime type than its compiletime type.
Yes, that is stated very clear for the JSON module, and I think for the Marshal module too. It is sad, but I guess there is no way to solve that.
One solution was suggested in
http://ssalewski.de/nimprogramming.html#_serializationstoring_data_permanently_on_external_storage
When we have to store different data types in one container, then one solution is to use object variants, which should work with the json module. Another obvious possibility is to just copy the data into containers with the appropriate static type before storing to an external medium and copy them back when we read the data back from external storage. Will will show an example for that now:
I intent to follow the second approach, copy the data. That is not too bad for my use case, as I already support groups. So I have to only create a top level group for storing all the content, and copy back from that group into the RTree when reading back.
>Also cyclic objects would probably blow up all
Yes, I have just discovered that, see my post before. Not a big problem, I do not really need that back reference. Would be difficult with ARC, but cursor pragma may fix it. If I would decide that back references would be a big bonus, I could delete them before storing, and recreate them then reading back.
write your (de)serialization functions by hand.
I did that in the Ruby version 12 years ago, that time I tried to be compatible with the gEDA/PCB format, so writing the data manually was indeed needed. But now I would want to avoid that.
yet another json lib
It is really a bit to much. I just learned about jsony from treeform. Will look at yours too.