Skip to content

Commit 9ef87a9

Browse files
authored
Merge pull request #674 from compas-dev/json
json convenience functions
2 parents d2cd5f0 + 9d09420 commit 9ef87a9

File tree

6 files changed

+211
-3
lines changed

6 files changed

+211
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727

2828
* Added `remap_values` to `compas_utilities`.
2929
* Added `compas.datastructures.mesh_slice_plane`.
30+
* Added `compas.json_dump`, `compas.json_dumps`, `compas.json_load`, `compas.json_loads`.
3031

3132
### Changed
3233

3334
* Fixed bug in `compas.datastructures.Network.delete_node`.
3435
* Fixed bug in `compas.datastructures.Network.delete_edge`.
3536
* Fixed bug in select functions for individual objects in `compas_rhino.utilities`.
3637
* Fixed bug in `compas.datastructures.mesh_merge_faces`.
38+
* changed base of `compas.geometry.Transformation` to `compas.base.Base`.
3739

3840
### Removed
3941

src/compas/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import decimal
2929

3030
import compas._os
31+
from compas._json import json_dump, json_dumps, json_load, json_loads
3132

3233

3334
__author__ = 'Tom Van Mele and many others (see AUTHORS.md)'
@@ -72,7 +73,11 @@
7273
pass
7374

7475

75-
__all__ = ['WINDOWS', 'LINUX', 'MONO', 'IPY', 'RHINO', 'BLENDER', 'set_precision', 'get']
76+
__all__ = [
77+
'WINDOWS', 'LINUX', 'MONO', 'IPY', 'RHINO', 'BLENDER',
78+
'set_precision',
79+
'get',
80+
'json_dump', 'json_load', 'json_dumps', 'json_loads']
7681

7782

7883
def is_windows():

src/compas/_json.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
from __future__ import print_function
2+
from __future__ import absolute_import
3+
from __future__ import division
4+
5+
import json
6+
from compas.utilities import DataEncoder
7+
from compas.utilities import DataDecoder
8+
9+
10+
__all__ = [
11+
'json_dump',
12+
'json_dumps',
13+
'json_load',
14+
'json_loads'
15+
]
16+
17+
18+
def json_dump(data, fp):
19+
"""Write a collection of COMPAS object data to a JSON file.
20+
21+
Parameters
22+
----------
23+
data : any
24+
Any JSON serializable object.
25+
This includes any (combination of) COMPAS object(s).
26+
fp : file-like object or path
27+
A writeable file-like object or the path to a file.
28+
29+
Returns
30+
-------
31+
None
32+
33+
Examples
34+
--------
35+
>>> import compas
36+
>>> from compas.geometry import Point, Vector
37+
>>> data1 = [Point(0, 0, 0), Vector(0, 0, 0)]
38+
>>> compas.json_dump(data1, 'data.json')
39+
>>> data2 = compas.json_load('data.json')
40+
>>> data1 == data2
41+
True
42+
"""
43+
if hasattr(fp, 'write'):
44+
return json.dump(data, fp, cls=DataEncoder)
45+
with open(fp, 'w') as fp:
46+
return json.dump(data, fp, cls=DataEncoder)
47+
48+
49+
def json_dumps(data):
50+
"""Write a collection of COMPAS objects to a JSON string.
51+
52+
Parameters
53+
----------
54+
data : any
55+
Any JSON serializable object.
56+
This includes any (combination of) COMPAS object(s).
57+
58+
Returns
59+
-------
60+
str
61+
62+
Examples
63+
--------
64+
>>> import compas
65+
>>> from compas.geometry import Point, Vector
66+
>>> data1 = [Point(0, 0, 0), Vector(0, 0, 0)]
67+
>>> s = compas.json_dumps(data1)
68+
>>> data2 compas.json_loads(s)
69+
>>> data1 == data2
70+
True
71+
"""
72+
return json.dumps(data, cls=DataEncoder)
73+
74+
75+
def json_load(fp):
76+
"""Read COMPAS object data from a JSON file.
77+
78+
Parameters
79+
----------
80+
fp : file-like object or path
81+
A writeable file-like object or the path to a file.
82+
83+
Returns
84+
-------
85+
data
86+
The (COMPAS) data contained in the file.
87+
88+
Examples
89+
--------
90+
>>> import compas
91+
>>> from compas.geometry import Point, Vector
92+
>>> data1 = [Point(0, 0, 0), Vector(0, 0, 0)]
93+
>>> compas.json_dump(data1, 'data.json')
94+
>>> data2 = compas.json_load('data.json')
95+
>>> data1 == data2
96+
True
97+
"""
98+
if hasattr(fp, 'read'):
99+
return json.load(fp, cls=DataDecoder)
100+
with open(fp, 'r') as fp:
101+
return json.load(fp, cls=DataDecoder)
102+
103+
104+
def json_loads(s):
105+
"""Read COMPAS object data from a JSON string.
106+
107+
Parameters
108+
----------
109+
s : str
110+
A JSON data string.
111+
112+
Returns
113+
-------
114+
data
115+
The (COMPAS) data contained in the string.
116+
117+
Examples
118+
--------
119+
>>> import compas
120+
>>> from compas.geometry import Point, Vector
121+
>>> data1 = [Point(0, 0, 0), Vector(0, 0, 0)]
122+
>>> s = compas.json_dumps(data1)
123+
>>> data2 = compas.json_loads()
124+
>>> data1 == data2
125+
True
126+
"""
127+
return json.loads(s, cls=DataDecoder)
128+
129+
130+
# ==============================================================================
131+
# Main
132+
# ==============================================================================
133+
134+
if __name__ == '__main__':
135+
import doctest
136+
137+
doctest.testmod(globs=globals())

src/compas/geometry/transformations/transformation.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
"""
1313
import math
1414

15+
from compas.base import Base
16+
1517
from compas.geometry import multiply_matrices
1618
from compas.geometry import transpose_matrix
1719

@@ -29,7 +31,7 @@
2931
__all__ = ['Transformation']
3032

3133

32-
class Transformation(object):
34+
class Transformation(Base):
3335
"""The ``Transformation`` represents a 4x4 transformation matrix.
3436
3537
It is the base class for transformations like :class:`Rotation`,
@@ -60,6 +62,8 @@ class Transformation(object):
6062
def __init__(self, matrix=None):
6163
"""Construct a transformation from a 4x4 transformation matrix.
6264
"""
65+
super(Transformation, self).__init__()
66+
6367
if not matrix:
6468
matrix = identity_matrix(4)
6569
self.matrix = matrix

src/compas/utilities/encoders.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,12 @@ class DataEncoder(json.JSONEncoder):
5252
def default(self, o):
5353
if hasattr(o, 'to_data'):
5454
value = o.to_data()
55+
if hasattr(o, 'dtype'):
56+
dtype = o.dtype
57+
else:
58+
dtype = "{}/{}".format(".".join(o.__class__.__module__.split(".")[:-1]), o.__class__.__name__)
5559
return {
56-
'dtype': "{}/{}".format(".".join(o.__class__.__module__.split(".")[:-1]), o.__class__.__name__),
60+
'dtype': dtype,
5761
'value': value
5862
}
5963

tests/compas/test_json.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import compas
2+
3+
from compas.geometry import Point, Vector, Frame
4+
from compas.geometry import Box
5+
from compas.geometry import Transformation
6+
7+
from compas.datastructures import Mesh
8+
9+
10+
def test_json_native():
11+
before = [[], (), {}, '', 1, 1.0, True, None]
12+
after = compas.json_loads(compas.json_dumps(before))
13+
assert after == [[], [], {}, '', 1, 1.0, True, None]
14+
15+
16+
if not compas.IPY:
17+
import numpy as np
18+
19+
def test_json_numpy():
20+
before = [np.array([1, 2, 3]), np.array([1.0, 2.0, 3.0]), np.float64(1.0), np.int32(1)]
21+
after = compas.json_loads(compas.json_dumps(before))
22+
assert after == [[1, 2, 3], [1.0, 2.0, 3.0], 1.0, 1]
23+
24+
25+
def test_json_primitive():
26+
before = Point(0, 0, 0)
27+
after = compas.json_loads(compas.json_dumps(before))
28+
assert before.dtype == after.dtype
29+
assert all(a == b for a, b in zip(before, after))
30+
31+
32+
def test_json_shape():
33+
before = Box(Frame(Point(0, 0, 0), Vector(1, 0, 0), Vector(0, 1, 0)), 1, 1, 1)
34+
after = compas.json_loads(compas.json_dumps(before))
35+
assert before.dtype == after.dtype
36+
assert all(a == b for a, b in zip(before.vertices, after.vertices))
37+
38+
39+
def test_json_xform():
40+
before = Transformation.from_frame_to_frame(Frame.worldXY(), Frame.worldXY())
41+
after = compas.json_loads(compas.json_dumps(before))
42+
assert before.dtype == after.dtype
43+
assert all(a == b for a, b in zip(before, after))
44+
45+
46+
def test_json_mesh():
47+
before = Mesh.from_vertices_and_faces([[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]], [[0, 1, 2, 3]])
48+
after = compas.json_loads(compas.json_dumps(before))
49+
assert before.dtype == after.dtype
50+
assert all(before.has_vertex(vertex) for vertex in after.vertices())
51+
assert all(after.has_vertex(vertex) for vertex in before.vertices())
52+
assert all(before.has_face(face) for face in after.faces())
53+
assert all(after.has_face(face) for face in before.faces())
54+
assert all(before.has_edge(edge) for edge in after.edges())
55+
assert all(after.has_edge(edge) for edge in before.edges())
56+
assert all(before.face_vertices(a) == after.face_vertices(b) for a, b in zip(before.faces(), after.faces()))

0 commit comments

Comments
 (0)