mulesoft-labs / data-weave-rfc

RFC for the data weave language
12 stars 5 forks source link

Allow type casting to like-Object definitions #24

Open manikmagar opened 3 years ago

manikmagar commented 3 years ago

Consider an Object received in an API looks like Following and can be represented as an instance of Employee type -

{
  "firstName": "Manik",
  "lastName": "Magar",
  "spokenLanguages": [
    "English",
    "Marathi",
    "Hindi"
  ],
  "departmentId": 1
}

In inheritance world, it is possible to have a structure like Employee extends Person.

Consider following dataweave script -

%dw 2.0
output application/json

type Employee = {
    firstName: String,
    lastName: String,
    spokenLanguages: Array<String>,
    departmentId: Number
}

type Person = {| firstName: String, lastName: String |}

fun getName(p:Person): String = "$(p.firstName) $(p.lastName)"

var empExample: Employee = {
  "firstName": "Manik",
  "lastName": "Magar",
  "spokenLanguages": [
    "English",
    "Marathi",
    "Hindi"
  ],
  "departmentId": 1
}
---
getName(empExample)

This script fails with following error -

26| getName(empExample)
    ^^^^^^^^^^^^^^^^^^^
Expecting Type: `Person`, but got: `Employee`.

 Reasons :
    - Close object does not allow additional properties.

    Function: `getName(p: `Person`) -> `String``
    Actual  : `getName(p: `Employee`)`

Person is represented as a closed subset of an Employee.

An explicit coercion of Employee to Person getName(empExample as Person) fails with following example -

Cannot coerce Object ({firstName: "Manik",lastName: "Magar",spokenLanguages: ["English", "Marathi",...) to Person

26| getName(empExample as Person)
            ^^^^^^^^^^^^^^^^^^^^
Trace:
  at main::getName (line: 26, column: 9)
  at main::main (line: 26, column: 1)

There are two ways to get this working -

  1. Manually map employee to Person - getName({firstName: empExample.firstName, lastName: empExample.lastName}).
  2. Change Person to be an open object - type Person = {firstName: String, lastName: String}, in which case getName(empExample) works.

Will it make sense to allow type casting objects into other if they are closed subsets - getName(empExample as Person)?

jerneyio commented 3 years ago

I can think of another way to get this working w/ a union type. Would this alleviate the pain point you're experiencing?

fun getName(p : Person | Employee): String ...

Or you could overload

fun getName(p: Person): String ...

fun getName(p: Employee): String ...
manikmagar commented 3 years ago

If I am using a custom utility module from shared resource that provides me many functions to handle Person object, I won't be able to use union types unless I modify that custom module.

I think those approaches will work if you have control over all types or they are known in advance.

menduz commented 3 years ago

Have you tried removing the "closed object" syntax?

-type Person = {| firstName: String, lastName: String |}
+type Person = { firstName: String, lastName: String }

fun getName(p:Person): String = "$(p.firstName) $(p.lastName)"

It should work for your use case.. In most cases, DataWeave have structural types. That means it won't pay attention to the name of the type but yes to the structure of the type. In this specific case, by using {| ... |} you are telling the type system to check that the type only has the properties you specify, nothing else is allowed. Instead, by using a type without the pipes { ... } you are telling the type system to use a type that matches, allowing extra properties.