Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added optional storage of constructor names and datatype names while serializing to JSON. Wired this through the different webservers as well #1862

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion src/org/rascalmpl/library/Content.rsc
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ which involves a handy, automatic, encoding of Rascal values into json values.
data Response
= response(Status status, str mimeType, map[str,str] header, str content)
| fileResponse(loc file, str mimeType, map[str,str] header)
| jsonResponse(Status status, map[str,str] header, value val, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'")
| jsonResponse(Status status, map[str,str] header, value val, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'", bool explicitConstructorNames=false, bool explicitDataTypes=false)
;


Expand Down
19 changes: 12 additions & 7 deletions src/org/rascalmpl/library/lang/json/IO.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,16 @@
}


public IValue readJSON(IValue type, ISourceLocation loc, IString dateTimeFormat, IBool lenient, IBool trackOrigins) {
public IValue readJSON(IValue type, ISourceLocation loc, IString dateTimeFormat, IBool lenient, IBool trackOrigins, IBool explicitConstructorNames, IBool explicitDataTypes) {
TypeStore store = new TypeStore();
Type start = new TypeReifier(values).valueToType((IConstructor) type, store);

try (JsonReader in = new JsonReader(URIResolverRegistry.getInstance().getCharacterReader(loc))) {
in.setLenient(lenient.getValue());
return new JsonValueReader(values, store, monitor, trackOrigins.getValue() ? loc : null)
.setCalendarFormat(dateTimeFormat.getValue())
.setExplicitConstructorNames(explicitConstructorNames.getValue())
.setExplicitDataTypes(explicitDataTypes.getValue())
.read(in, start);
}
catch (IOException e) {
Expand All @@ -119,14 +121,16 @@
}
}

public IValue parseJSON(IValue type, IString src, IString dateTimeFormat, IBool lenient, IBool trackOrigins) {
public IValue parseJSON(IValue type, IString src, IString dateTimeFormat, IBool lenient, IBool trackOrigins, IBool explicitConstructorNames, IBool explicitDataTypes) {
TypeStore store = new TypeStore();
Type start = new TypeReifier(values).valueToType((IConstructor) type, store);

try (JsonReader in = new JsonReader(new StringReader(src.getValue()))) {
in.setLenient(lenient.getValue());
return new JsonValueReader(values, store, monitor, trackOrigins.getValue() ? URIUtil.rootLocation("unknown") : null)
.setCalendarFormat(dateTimeFormat.getValue())
.setExplicitConstructorNames(explicitConstructorNames.getValue())
.setExplicitDataTypes(explicitDataTypes.getValue())

Check warning on line 133 in src/org/rascalmpl/library/lang/json/IO.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/IO.java#L132-L133

Added lines #L132 - L133 were not covered by tests
.read(in, start);
}
catch (IOException e) {
Expand All @@ -137,7 +141,7 @@
}
}

public void writeJSON(ISourceLocation loc, IValue value, IBool unpackedLocations, IString dateTimeFormat, IBool dateTimeAsInt, IInteger indent, IBool dropOrigins) {
public void writeJSON(ISourceLocation loc, IValue value, IBool unpackedLocations, IString dateTimeFormat, IBool dateTimeAsInt, IInteger indent, IBool dropOrigins, IBool explicitConstructorNames, IBool explicitDataTypes) {
try (JsonWriter out = new JsonWriter(new OutputStreamWriter(URIResolverRegistry.getInstance().getOutputStream(loc, false), Charset.forName("UTF8")))) {
if (indent.intValue() > 0) {
out.setIndent(" ".substring(0, indent.intValue() % 9));
Expand All @@ -148,13 +152,15 @@
.setDatesAsInt(dateTimeAsInt.getValue())
.setUnpackedLocations(unpackedLocations.getValue())
.setDropOrigins(dropOrigins.getValue())
.setExplicitConstructorNames(explicitConstructorNames.getValue())
.setExplicitDataTypes(explicitDataTypes.getValue())

Check warning on line 156 in src/org/rascalmpl/library/lang/json/IO.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/IO.java#L155-L156

Added lines #L155 - L156 were not covered by tests
.write(out, value);
} catch (IOException e) {
throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null);
}
}

public IString asJSON(IValue value, IBool unpackedLocations, IString dateTimeFormat, IBool dateTimeAsInt, IInteger indent, IBool dropOrigins) {
public IString asJSON(IValue value, IBool unpackedLocations, IString dateTimeFormat, IBool dateTimeAsInt, IInteger indent, IBool dropOrigins, IBool explicitConstructorNames, IBool explicitDataTypes) {
StringWriter string = new StringWriter();

try (JsonWriter out = new JsonWriter(string)) {
Expand All @@ -166,14 +172,13 @@
.setDatesAsInt(dateTimeAsInt.getValue())
.setUnpackedLocations(unpackedLocations.getValue())
.setDropOrigins(dropOrigins.getValue())
.setExplicitConstructorNames(explicitConstructorNames.getValue())
.setExplicitDataTypes(explicitDataTypes.getValue())

Check warning on line 176 in src/org/rascalmpl/library/lang/json/IO.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/IO.java#L175-L176

Added lines #L175 - L176 were not covered by tests
.write(out, value);

return values.string(string.toString());
} catch (IOException e) {
throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null);
}
}



}
49 changes: 35 additions & 14 deletions src/org/rascalmpl/library/lang/json/IO.rsc
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,20 @@
module lang::json::IO

@javaClass{org.rascalmpl.library.lang.json.IO}
@deprecated{
use writeJSON
}
@deprecated{use writeJSON}
public java str toJSON(value v);

@javaClass{org.rascalmpl.library.lang.json.IO}
@deprecated{
use asJSON
}
@deprecated{use asJSON}
public java str toJSON(value v, bool compact);

@javaClass{org.rascalmpl.library.lang.json.IO}
@deprecated{
use readJSON
}
@deprecated{use readJSON}
public java &T fromJSON(type[&T] typ, str src);

@javaClass{org.rascalmpl.library.lang.json.IO}
@synopsis{reads JSON values from a stream
@synopsis{reads JSON values from a stream}
@description{
In general the translation behaves as follows:
* Objects translate to map[str,value] by default, unless a node is expected (properties are then translated to keyword fields)
* Arrays translate to lists by default, or to a set if that is expected or a tuple if that is expected. Arrays may also be interpreted as constructors or nodes (see below)
Expand All @@ -44,15 +39,41 @@ In general the translation behaves as follows:
* If num, int, real or rat are expected both strings and number values are mapped
* If loc is expected than strings which look like URI are parsed (containing :/) or a file:/// URI is build, or if an object is found each separate field of
a location object is read from the respective properties: { scheme : str, authority: str?, path: str?, fragment: str?, query: str?, offset: int, length: int, begin: [bl, bc], end: [el, ec]}}
java &T readJSON(type[&T] expected, loc src, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ssZZZZZ", bool lenient=false, bool trackOrigins=false);
java &T readJSON(type[&T] expected, loc src, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ssZZZZZ", bool lenient=false, bool trackOrigins=false, bool explicitConstructorNames=false, bool explicitDataTypes=false);

@javaClass{org.rascalmpl.library.lang.json.IO}
@synopsis{parses JSON values from a string
In general the translation behaves as the same as for ((readJSON)).}
java &T parseJSON(type[&T] expected, str src, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ssZZZZZ", bool lenient=false, bool trackOrigins=false);
java &T parseJSON(type[&T] expected, str src, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ssZZZZZ", bool lenient=false, bool trackOrigins=false, bool explicitConstructorNames=false, bool explicitDataTypes=false);

@javaClass{org.rascalmpl.library.lang.json.IO}
java void writeJSON(loc target, value val, bool unpackedLocations=false, str dateTimeFormat="yyyy-MM-dd\'T\'HH:mm:ssZZZZZ", bool dateTimeAsInt=false, int indent=0, bool dropOrigins=true);
@synopsis{Serializes a value as a JSON string and stream it}
@description{
This function tries to map Rascal values to JSON values in a natural way.
In particular it tries to create a value that has the same number of recursive levels,
such that one constructor maps to one object. The serialization is typically _lossy_ since
JSON values by default do not explicitly encode the class or constructor while Rascal data types do.

If you need the names of constructors or data-types in your result, then use the parameters:
* `explicitConstructorNames=true` will store the name of every constructor in a field `_constructor`
* `explicitDataTypes=true` will store the name of the ADT in a field called `_type`

The `dateTimeFormat` parameter dictates how `datetime` values will be printed.

The `unpackedLocations` parameter will produce an object with many fields for every property of a `loc` value, but
if set to false a `loc` will be printed as a string.

:::warning
It is understood that Rascal's number types have arbitrary precision, but this is not supported by the JSON writer.
As such when an `int` is printed that does not fit into a JVM `long`, there will be truncation to the lower 64 bits.
For `real` numbers that are larger than JVM's double you get "negative infinity" or "positive infinity" as a result.
:::
}
java void writeJSON(loc target, value val, bool unpackedLocations=false, str dateTimeFormat="yyyy-MM-dd\'T\'HH:mm:ssZZZZZ", bool dateTimeAsInt=false, int indent=0, bool dropOrigins=true, bool explicitConstructorNames=false, bool explicitDataTypes=false);

@javaClass{org.rascalmpl.library.lang.json.IO}
java str asJSON(value val, bool unpackedLocations=false, str dateTimeFormat="yyyy-MM-dd\'T\'HH:mm:ssZZZZZ", bool dateTimeAsInt=false, int indent = 0, bool dropOrigins=true);
@synopsis{Serializes a value as a JSON string and stores it as a string}
@description{
This function uses `writeJSON` and stores the result in a string.
}
java str asJSON(value val, bool unpackedLocations=false, str dateTimeFormat="yyyy-MM-dd\'T\'HH:mm:ssZZZZZ", bool dateTimeAsInt=false, int indent = 0, bool dropOrigins=true, bool explicitConstructorNames=false, bool explicitDataTypes=false);
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.rascalmpl.debug.IRascalMonitor;
import org.rascalmpl.uri.URIUtil;
Expand Down Expand Up @@ -59,6 +60,8 @@
private VarHandle posHandler;
private VarHandle lineHandler;
private VarHandle lineStartHandler;
private boolean explicitConstructorNames;
private boolean explicitDataTypes;

/**
* @param vf factory which will be used to construct values
Expand Down Expand Up @@ -105,6 +108,17 @@
return this;
}

public JsonValueReader setExplicitConstructorNames(boolean value) {
this.explicitConstructorNames = value;
return this;
}

public JsonValueReader setExplicitDataTypes(boolean value) {
this.explicitDataTypes = value;
return this;
}


/**
* Read and validate a Json stream as an IValue
* @param in json stream
Expand Down Expand Up @@ -495,7 +509,37 @@

assert in.peek() == JsonToken.BEGIN_OBJECT;

Set<Type> alternatives = store.lookupAlternatives(type);
Set<Type> alternatives = null;

Check warning on line 512 in src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java#L512

Added line #L512 was not covered by tests

// use explicit information in the JSON to select and filter constructors from the TypeStore
// we expect always to have the field _constructor before _type.
if (explicitConstructorNames) {
String consLabel = in.nextName();

Check warning on line 517 in src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java#L517

Added line #L517 was not covered by tests
if ("_constructor".equals(consLabel)) {
String consName = in.nextString();

Check warning on line 519 in src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java#L519

Added line #L519 was not covered by tests

alternatives = store.lookupConstructors(consName);

Check warning on line 521 in src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java#L521

Added line #L521 was not covered by tests

if (explicitDataTypes) {
String dtLabel = in.nextName();

Check warning on line 524 in src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java#L524

Added line #L524 was not covered by tests

if ("_type".equals(dtLabel)) {
String dtValue = in.nextString();

Check warning on line 527 in src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java#L527

Added line #L527 was not covered by tests
alternatives = alternatives.stream().filter(t -> t.isAbstractData() && t.getName().equals(dtValue)).collect(Collectors.toSet());
}

Check warning on line 529 in src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java#L529

Added line #L529 was not covered by tests
else {
throw new IOException("Expected _type field but got " + dtLabel);

Check warning on line 531 in src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java#L531

Added line #L531 was not covered by tests
}
}
}

Check warning on line 534 in src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java#L534

Added line #L534 was not covered by tests
else {
throw new IOException("Expected _constructor field but got " + consLabel);

Check warning on line 536 in src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java#L536

Added line #L536 was not covered by tests
}
}

Check warning on line 538 in src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java#L538

Added line #L538 was not covered by tests
else {
alternatives = store.lookupAlternatives(type);

Check warning on line 540 in src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueReader.java#L540

Added line #L540 was not covered by tests
}

if (alternatives.size() > 1) {
monitor.warning("selecting arbitrary constructor for " + type, vf.sourceLocation(in.getPath()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
private boolean datesAsInts = true;
private boolean unpackedLocations = false;
private boolean dropOrigins = true;
private boolean explicitConstructorNames = false;

Check warning on line 47 in src/org/rascalmpl/library/lang/json/internal/JsonValueWriter.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueWriter.java#L47

Added line #L47 was not covered by tests
private boolean explicitDataTypes;

public JsonValueWriter() {
setCalendarFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
Expand Down Expand Up @@ -78,6 +80,16 @@
return this;
}

public JsonValueWriter setExplicitConstructorNames(boolean setting) {
this.explicitConstructorNames = setting;
return this;

Check warning on line 85 in src/org/rascalmpl/library/lang/json/internal/JsonValueWriter.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueWriter.java#L84-L85

Added lines #L84 - L85 were not covered by tests
}

public JsonValueWriter setExplicitDataTypes(boolean setting) {
this.explicitDataTypes = setting;
return this;

Check warning on line 90 in src/org/rascalmpl/library/lang/json/internal/JsonValueWriter.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueWriter.java#L89-L90

Added lines #L89 - L90 were not covered by tests
}

public void write(JsonWriter out, IValue value) throws IOException {
value.accept(new IValueVisitor<Void, IOException>() {

Expand Down Expand Up @@ -222,13 +234,24 @@

@Override
public Void visitConstructor(IConstructor o) throws IOException {
if (o.getConstructorType().getArity() == 0 && !o.asWithKeywordParameters().hasParameters()) {
if (!explicitConstructorNames && o.getConstructorType().getArity() == 0 && !o.asWithKeywordParameters().hasParameters()) {
// enums!
out.value(o.getName());
return null;
}

out.beginObject();

if (explicitConstructorNames || explicitDataTypes) {
out.name("_constructor");
out.value(o.getName());

Check warning on line 247 in src/org/rascalmpl/library/lang/json/internal/JsonValueWriter.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueWriter.java#L246-L247

Added lines #L246 - L247 were not covered by tests
}

if (explicitDataTypes) {
out.name("_type");
out.value(o.getType().getName());

Check warning on line 252 in src/org/rascalmpl/library/lang/json/internal/JsonValueWriter.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/lang/json/internal/JsonValueWriter.java#L251-L252

Added lines #L251 - L252 were not covered by tests
}

int i = 0;
for (IValue arg : o) {
out.name(o.getConstructorType().getFieldName(i));
Expand Down
9 changes: 7 additions & 2 deletions src/org/rascalmpl/library/util/TermREPL.java
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,15 @@

IValue dtf = kws.getParameter("dateTimeFormat");
IValue dai = kws.getParameter("dateTimeAsInt");

IValue ecn = kws.getParameter("explicitConstructorNames");
IValue edt = kws.getParameter("explicitDataTypes");

Check warning on line 242 in src/org/rascalmpl/library/util/TermREPL.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/util/TermREPL.java#L241-L242

Added lines #L241 - L242 were not covered by tests

JsonValueWriter writer = new JsonValueWriter()
.setCalendarFormat(dtf != null ? ((IString) dtf).getValue() : "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'")
.setDatesAsInt(dai != null ? ((IBool) dai).getValue() : true);
.setDatesAsInt(dai != null ? ((IBool) dai).getValue() : true)
.setExplicitConstructorNames(ecn != null ? ((IBool) ecn).getValue() : false)
.setExplicitDataTypes(edt != null ? ((IBool) edt).getValue() : false)
;

final ByteArrayOutputStream baos = new ByteArrayOutputStream();

Expand Down
7 changes: 6 additions & 1 deletion src/org/rascalmpl/library/util/Webserver.java
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,15 @@

IValue dtf = kws.getParameter("dateTimeFormat");
IValue dai = kws.getParameter("dateTimeAsInt");
IValue ecn = kws.getParameter("explicitConstructorNames");
IValue edt = kws.getParameter("explicitDataTypes");

Check warning on line 242 in src/org/rascalmpl/library/util/Webserver.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/library/util/Webserver.java#L241-L242

Added lines #L241 - L242 were not covered by tests

JsonValueWriter writer = new JsonValueWriter()
.setCalendarFormat(dtf != null ? ((IString) dtf).getValue() : "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'")
.setDatesAsInt(dai != null ? ((IBool) dai).getValue() : true);
.setDatesAsInt(dai != null ? ((IBool) dai).getValue() : true)
.setExplicitConstructorNames(ecn != null ? ((IBool) ecn).getValue() : false)
.setExplicitDataTypes(edt != null ? ((IBool) edt).getValue() : false)
;

try {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
Expand Down
7 changes: 6 additions & 1 deletion src/org/rascalmpl/repl/REPLContentServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,15 @@

IValue dtf = kws.getParameter("dateTimeFormat");
IValue dai = kws.getParameter("dateTimeAsInt");
IValue ecn = kws.getParameter("explicitConstructorNames");
IValue edt = kws.getParameter("explicitDataTypes");

Check warning on line 139 in src/org/rascalmpl/repl/REPLContentServer.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/REPLContentServer.java#L138-L139

Added lines #L138 - L139 were not covered by tests

JsonValueWriter writer = new JsonValueWriter()
.setCalendarFormat(dtf != null ? ((IString) dtf).getValue() : "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'")
.setDatesAsInt(dai != null ? ((IBool) dai).getValue() : true);
.setDatesAsInt(dai != null ? ((IBool) dai).getValue() : true)
.setExplicitConstructorNames(ecn != null ? ((IBool) ecn).getValue() : false)
.setExplicitDataTypes(edt != null ? ((IBool) edt).getValue() : false)
;

try {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
Expand Down
Loading