rascal icon indicating copy to clipboard operation
rascal copied to clipboard

lang::json::io::asJSON looses name of constructor in case it has arguments

Open DavyLandman opened this issue 2 years ago • 4 comments

Describe the bug

In case a adt has arguments, the name of the constructor is lost when generating a json.

To Reproduce

import IO;
import lang::json::IO;

data EnumSet 
   = flag1(str a)
   | flag2(str a)
   | flag3(int )
   | flag4()
   | flag5()
   ;

void main() {
    println("Correct: ");
    println(asJSON({flag4(), flag5()},indent=2));
    println("Missing labels: ");
    println(asJSON({flag1("a"), flag2("b")},indent=2));
}

prints:

Correct:
[
  "flag5",
  "flag4"
]
Missing labels:
[
  {
    "a": "a"
  },
  {
    "a": "b"
  }
]

Expected behavior I would expect the second one to generate something like:

[
   "flag1": { "a": "a" },
   "flag2": { "a": "b"}
]

since now the information is lost.

Desktop (please complete the following information):

  • Rascal Version: 0.33.5

Additional context

DavyLandman avatar Sep 11 '23 19:09 DavyLandman

Let's have a discussion because I intended this behavior to be just like it is. In typescript, js and JSON objects do not carry their names. So in order to map rascal constructors idiomatically to JSON you get an object with its fields and that's it.

For "enums", which are represented in JSON as strings and in Rascal as nullary constructors, the mapping is made from constructor names to strings, and back. This is the reason why some constructors are represented differently. The nullary ones are treated as enums.

We could make asJSON smarter by consistently mapping to empty objects for the nullary constructors if not all constructors of a type are nullary, but that would break the reversibility of the mapping since all nullary constructors would map to {} and the inverse would be ill-defined. Granted that the inverse is not always defined anyway, but the current design tries to make it happen for most idiomatic uses of JSON.

jurgenvinju avatar Sep 11 '23 19:09 jurgenvinju

So if I want a natural mapping to JSON I define my adt such that the name of the constructor is not a flag

jurgenvinju avatar Sep 11 '23 19:09 jurgenvinju

Some more discussion have pointed towards this as a current best way to model json & rascal interaction for enum flags:

data Enum2
    = flags(str flag1 = "", str flag2 = "", int flag3 = -1, bool flag4 = false, bool flag5 = false);

void main() {
    println(asJSON(flags(flag1="a", flag2="b", flag5=false),indent=2));
}

outputs:

{
  "flag1": "a",
  "flag2": "b",
  "flag5": false
}

which is something that more cleanly maps onto typescript.

DavyLandman avatar Sep 13 '23 07:09 DavyLandman

Yes the current JSON mapper is good at this scenario:

  • you take a TypeScript class that is made in a TypeScript style
  • you create a Rascal data type with a single constructor. The name is irrelevant for the mapping but should be insightful on the Rascal side.
  • you create fields in this constructor for required information (never undefined) and keyword fields for optional information (sometimes undefined)

There is one exception. If your information in TypeScript is an enum then:

  • Create a data type in Rascal
  • Add a constructor with zero arguments for every enum.
  • The constructor name has to be the same as the enum value string.

The JSON mapper is not so good in the scenario where you already have a large AST definition in Rascal and you need to map it to TypeScript. For this, we've now added _constructor and _type fields to make it visible what kind of objects were mapped. However, we have an open project for a master's thesis on mapping Rascal's abstract data grammars to TypeScript class definitions and/or JSON-schemata in a meaningful manner.

So for now it's best to model your Rascal after what you want to see in TypeScript and translate from the information you have in Rascal to what you want to send to TypeScript.

Suggestions and tips and especially example use-cases are very welcome.

jurgenvinju avatar Sep 18 '23 07:09 jurgenvinju