Here's my implementation of purescript-jtable.
It has all the high-level features in the requirements. Some things are implemented differently though, and I hope you will accept my reasoning below.
I recommend to Try it with different renderers and example data (including the "Comprehensive" example) right away, look at example usage and then read the analysis below.
Types, header
The label hierarchy is unified based on its shape. It becomes a hierarchy of nodes with width. Primitives have width 1, tuples more. The concrete types of the primitives are irrelevant, only the width is used. Arrays are not represented, they are unified into the most general shape hierarchy. Thus it is ensured that all data present in the Json can be rendered sensibly.
Contrast this to the spec, which says "Classify every leaf node using JSemantic [...], based on the most common type of semantic detected in the data". The problems with this step include that a statistical method is suggested to determine the type of data, with no hints as to how to handle divergent leaves. Also, leaf node semantic type, and even distinguishing between primitive json types is irrelevant to the header generation, where only tuple width and label hierarchy matters. In fact, JSemantic is a different issue, independent of JTable (but which combines nicely), as discussed below.
Tuples
There were some undefined aspects of handling tuples. Here's how I chose to resolve them:
Homogenous arrays of equal sizes are rendered vertically, except when their size equals 2, in which case they are rendered horizontally. This is somewhat arbitrary, but usefult for geographic coordinates.
Heterogenous arrays of different sizes are rendered horizontally, with the maximum tuple size becoming the column width
Lists of objects with dijunct keys will be displayed in one row (treating them as tuples of objects).
TableStyle.th
Another difference is: JPath is an array of strings. Arrays (which could have different nestings) are not represented in the header hierarchy, so a [String] suffices for the path instead of the unwieldier JCursor.
type JPath = [String]
The header rendering function gets a JPath as an argument.
TableStyle.th :: JPath -> Markup
The default implementation is equivalent to
noStyle.th = \p -> th $ text $ Data.Array.Unsafe.last p
Where th and text are from Smolder and the path is guaranteed to have at least one element.
Column Ordering
The ColumnOrdering type has been simplified, inOrdering is a value. Easy to roll your own.
JTable does not depend on JSemantic. In fact, I recommend releasing JSemantic as a separate library, since it is orthogonal to JTable and separate from Argonaut (although it uses the JsonPrim type). However, for now I have inluded it here.
Accordingly, td receives a JsonPrim argument that is easy to use.
TableStyle.td :: JCursor -> JsonPrim -> Markup
JSemantic
Data constructors of the type JSemantic have parameters and carry their data with them, converted to a useful type.
data JSemantic = Integral Number
| Fractional Number
| Date Date.Date
| DateTime Date.Date
| Time Date.Date
| Interval Date.Date Date.Date
| Text String
| Bool Boolean
| Percent Number
| Currency Number
| NA
Although String might be a better choice for Currency and Percent, to avoid loss of precision.
And it's easy to combine JTable and JSemantic:
exampleRendererSemantic = renderJTable defJTableOpts {
style = noStyle { td = \c j -> case toSemantic j of
Currency n -> td ! className "money" $ text $ show n
}}
Rendering differently based on path is also easy, dispatching on the JCursor argument, using operations from Argonaut.
Also,
There are unit tests for some edge cases that I fixed along the way, and StrongCheck tests are run as well.
The code is not exceptionally fast or clean and simple, but it's not slow or too complex either. It gets the job done.
I'm sure the examples, the readme, and the above will convince you that the implementation provides a straightforward, intuitive, and correct conceptual mapping between any Json schema and the resulting table, has features equivalent to the requirements, with the differences backed by sound analysis.
Here's my implementation of purescript-jtable. It has all the high-level features in the requirements. Some things are implemented differently though, and I hope you will accept my reasoning below.
I recommend to Try it with different renderers and example data (including the "Comprehensive" example) right away, look at example usage and then read the analysis below.
Types, header
The label hierarchy is unified based on its shape. It becomes a hierarchy of nodes with width. Primitives have width 1, tuples more. The concrete types of the primitives are irrelevant, only the width is used. Arrays are not represented, they are unified into the most general shape hierarchy. Thus it is ensured that all data present in the Json can be rendered sensibly.
Contrast this to the spec, which says "Classify every leaf node using JSemantic [...], based on the most common type of semantic detected in the data". The problems with this step include that a statistical method is suggested to determine the type of data, with no hints as to how to handle divergent leaves. Also, leaf node semantic type, and even distinguishing between primitive json types is irrelevant to the header generation, where only tuple width and label hierarchy matters. In fact, JSemantic is a different issue, independent of JTable (but which combines nicely), as discussed below.
Tuples
There were some undefined aspects of handling tuples. Here's how I chose to resolve them:
Homogenous arrays of equal sizes are rendered vertically, except when their size equals 2, in which case they are rendered horizontally. This is somewhat arbitrary, but usefult for geographic coordinates.
Heterogenous arrays of different sizes are rendered horizontally, with the maximum tuple size becoming the column width
Lists of objects with dijunct keys will be displayed in one row (treating them as tuples of objects).
TableStyle.th
Another difference is: JPath is an array of strings. Arrays (which could have different nestings) are not represented in the header hierarchy, so a [String] suffices for the path instead of the unwieldier JCursor.
The header rendering function gets a JPath as an argument.
The default implementation is equivalent to
Where
th
andtext
are from Smolder and the path is guaranteed to have at least one element.Column Ordering
The
ColumnOrdering
type has been simplified,inOrdering
is a value. Easy to roll your own.TableStyle.td
JTable does not depend on JSemantic. In fact, I recommend releasing JSemantic as a separate library, since it is orthogonal to JTable and separate from Argonaut (although it uses the JsonPrim type). However, for now I have inluded it here.
Accordingly,
td
receives a JsonPrim argument that is easy to use.JSemantic
Data constructors of the type JSemantic have parameters and carry their data with them, converted to a useful type.
Although String might be a better choice for Currency and Percent, to avoid loss of precision.
And it's easy to combine JTable and JSemantic:
Rendering differently based on path is also easy, dispatching on the JCursor argument, using operations from Argonaut.
Also,
There are unit tests for some edge cases that I fixed along the way, and StrongCheck tests are run as well.
The code is not exceptionally fast or clean and simple, but it's not slow or too complex either. It gets the job done.
I'm sure the examples, the readme, and the above will convince you that the implementation provides a straightforward, intuitive, and correct conceptual mapping between any Json schema and the resulting table, has features equivalent to the requirements, with the differences backed by sound analysis.
Let me know if changes or additions are needed.