discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

Re: Possible to reverse the JSON import and instead write out a JSON file?

JB
Jordan Brown
Tue, Dec 14, 2021 11:46 PM

It would be easy to write output with echo() that could be trivially
post-processed into JSON, for simple cases.

It would be a little harder to handle cases that require quoting in the
strings.

But what's harder is that OpenSCAD doesn't have the data structures
(yet) that correspond to JSON.  It doesn't have a way for you to have a
name-value-list data structure that would naturally turn into a JSON
object.  That means that you'd have to build your JSON "by hand", doing
your own nesting and whatnot.  Or live with the limits of scalars and
arrays, with no objects.

That will change when we eventually add syntactic support for creating
objects; it will then be more or less straightforward to write a module
that would turn an OpenSCAD value into a series of echo() calls that
could then be trivially post-processed into proper JSON.

Of course, that leaves the long-discussed questions of (a) how to emit
text without echo's decorations, and (b) how to capture the desired
output, and only that output.  But that's kind of orthogonal.

In fact, it's possible today to write a general "echo JSON" module that
will work with scalars, arrays, and objects.  Not sure what to do with
ranges.  If I remember tonight I might write it.

And then you post-process it with something like

sed -n 's/^ECHO: "\(.*\)"$/\1/p'
It would be easy to write output with echo() that could be trivially post-processed into JSON, for simple cases. It would be a little harder to handle cases that require quoting in the strings. But what's harder is that OpenSCAD doesn't have the data structures (yet) that correspond to JSON.  It doesn't have a way for you to have a name-value-list data structure that would naturally turn into a JSON object.  That means that you'd have to build your JSON "by hand", doing your own nesting and whatnot.  Or live with the limits of scalars and arrays, with no objects. That will change when we eventually add syntactic support for creating objects; it will then be more or less straightforward to write a module that would turn an OpenSCAD value into a series of echo() calls that could then be trivially post-processed into proper JSON. Of course, that leaves the long-discussed questions of (a) how to emit text without echo's decorations, and (b) how to capture the desired output, and *only* that output.  But that's kind of orthogonal. In fact, it's possible today to write a general "echo JSON" module that will work with scalars, arrays, and objects.  Not sure what to do with ranges.  If I remember tonight I might write it. And then you post-process it with something like sed -n 's/^ECHO: "\(.*\)"$/\1/p'
JB
Jordan Brown
Wed, Dec 15, 2021 2:54 AM

On 12/14/2021 3:46 PM, Jordan Brown wrote:

In fact, it's possible today to write a general "echo JSON" module
that will work with scalars, arrays, and objects.  Not sure what to do
with ranges.  If I remember tonight I might write it.

And here it is, a function that takes an OpenSCAD value (except
functions and ranges) and returns a JSON string.  Lightly tested.

function json(v) =
    is_undef(v) ? "null"
    : is_bool(v) ? str(v)
    : is_num(v) ? str(v)
    : is_string(v) ? json_string(v)
    : is_list(v) ? json_list(v)
    : is_object(v) ? json_object(v)
    : assert(false, str("Don't know how to turn \"", v, "\" into JSON"));

function json_string(s) =
    let(a = [ for (c=s) json_char(c) ])
    str("\"", join(a), "\"");

function json_char(c) =
    c == "\"" ? "\\\""
    : c == "\\" ? "\\\\"
    : c;

function join(a, sep="", i=0) =
    i >= len(a) ? ""
    : str(i == 0 ? "" : sep, a[i], join(a, sep=sep, i=i+1));

function json_list(a) = str("[", join([ for (v=a) json(v) ], ","), "]");

function json_object(o) = str(
    "{",
    join([ for (k=o) str(json_string(k), "=", json(o[k])) ], ","),
    "}"
);

echo(json([undef, true, 123,["aaa","bbb","\"\\", textmetrics("hello")]]));

which outputs:

ECHO:
"[null,true,123,["aaa","bbb","\"\\",{"position"=[0.96,-0.1408],"size"=[27.8024,10.208],"ascent"=10.0672,"descent"=-0.1408,"offset"=[0,0],"advance"=[29.3443,0]}]]"

Or, reformatted for readability:

ECHO: " [ null, true, 123, [ "aaa", "bbb", "\"\\", {
"position"=[0.96,-0.1408], "size"=[27.8024,10.208],
"ascent"=10.0672, "descent"=-0.1408, "offset"=[0,0],
"advance"=[29.3443,0] } ] ] "

Notes:

  • The function depends on object support that is present only in daily
    builds.  (But might work in earlier builds anyway, as long as you
    don't give it a range.)
  • The test invocation (but not the function) depends on having the
    experimental textmetrics feature turned on.

And then you post-process it with something like

 sed -n 's/^ECHO: "\(.*\)"$/\1/p'
On 12/14/2021 3:46 PM, Jordan Brown wrote: > In fact, it's possible today to write a general "echo JSON" module > that will work with scalars, arrays, and objects.  Not sure what to do > with ranges.  If I remember tonight I might write it. And here it is, a function that takes an OpenSCAD value (except functions and ranges) and returns a JSON string.  Lightly tested. function json(v) = is_undef(v) ? "null" : is_bool(v) ? str(v) : is_num(v) ? str(v) : is_string(v) ? json_string(v) : is_list(v) ? json_list(v) : is_object(v) ? json_object(v) : assert(false, str("Don't know how to turn \"", v, "\" into JSON")); function json_string(s) = let(a = [ for (c=s) json_char(c) ]) str("\"", join(a), "\""); function json_char(c) = c == "\"" ? "\\\"" : c == "\\" ? "\\\\" : c; function join(a, sep="", i=0) = i >= len(a) ? "" : str(i == 0 ? "" : sep, a[i], join(a, sep=sep, i=i+1)); function json_list(a) = str("[", join([ for (v=a) json(v) ], ","), "]"); function json_object(o) = str( "{", join([ for (k=o) str(json_string(k), "=", json(o[k])) ], ","), "}" ); echo(json([undef, true, 123,["aaa","bbb","\"\\", textmetrics("hello")]])); which outputs: ECHO: "[null,true,123,["aaa","bbb","\"\\",{"position"=[0.96,-0.1408],"size"=[27.8024,10.208],"ascent"=10.0672,"descent"=-0.1408,"offset"=[0,0],"advance"=[29.3443,0]}]]" Or, reformatted for readability: ECHO: " [ null, true, 123, [ "aaa", "bbb", "\"\\", { "position"=[0.96,-0.1408], "size"=[27.8024,10.208], "ascent"=10.0672, "descent"=-0.1408, "offset"=[0,0], "advance"=[29.3443,0] } ] ] " Notes: * The function depends on object support that is present only in daily builds.  (But might work in earlier builds anyway, as long as you don't give it a range.) * The test invocation (but not the function) depends on having the experimental textmetrics feature turned on. > And then you post-process it with something like > > sed -n 's/^ECHO: "\(.*\)"$/\1/p' >
JB
Jordan Brown
Wed, Dec 15, 2021 2:57 AM

Oops, got my languages confused; the "=" in objects should be a ":".

function json(v) =
    is_undef(v) ? "null"
    : is_bool(v) ? str(v)
    : is_num(v) ? str(v)
    : is_string(v) ? json_string(v)
    : is_list(v) ? json_list(v)
    : is_object(v) ? json_object(v)
    : assert(false, str("Don't know how to turn \"", v, "\" into JSON"));

function json_string(s) =
    let(a = [ for (c=s) json_char(c) ])
    str("\"", join(a), "\"");

function json_char(c) =
    c == "\"" ? "\\\""
    : c == "\\" ? "\\\\"
    : c;

function join(a, sep="", i=0) =
    i >= len(a) ? ""
    : str(i == 0 ? "" : sep, a[i], join(a, sep=sep, i=i+1));

function json_list(a) = str("[", join([ for (v=a) json(v) ], ","), "]");

function json_object(o) = str(
    "{",
    join([ for (k=o) str(json_string(k), ":", json(o[k])) ], ","),
    "}"
);

echo(json([undef, true, 123,["aaa","bbb","\"\\", textmetrics("hello")]]));
Oops, got my languages confused; the "=" in objects should be a ":". function json(v) = is_undef(v) ? "null" : is_bool(v) ? str(v) : is_num(v) ? str(v) : is_string(v) ? json_string(v) : is_list(v) ? json_list(v) : is_object(v) ? json_object(v) : assert(false, str("Don't know how to turn \"", v, "\" into JSON")); function json_string(s) = let(a = [ for (c=s) json_char(c) ]) str("\"", join(a), "\""); function json_char(c) = c == "\"" ? "\\\"" : c == "\\" ? "\\\\" : c; function join(a, sep="", i=0) = i >= len(a) ? "" : str(i == 0 ? "" : sep, a[i], join(a, sep=sep, i=i+1)); function json_list(a) = str("[", join([ for (v=a) json(v) ], ","), "]"); function json_object(o) = str( "{", join([ for (k=o) str(json_string(k), ":", json(o[k])) ], ","), "}" ); echo(json([undef, true, 123,["aaa","bbb","\"\\", textmetrics("hello")]]));