rescript-labs / decco

Bucklescript PPX which generates JSON (de)serializers for user-defined types
MIT License
228 stars 27 forks source link

Encode None to undefined?! #26

Open strdr4605 opened 4 years ago

strdr4605 commented 4 years ago

I have 2 types:

[@decco]
type attachment = {
  title: string,
  text: string,
  pretext: string,
  color: string,
  fallback: string,
};

[@bs.deriving jsConverter]
[@decco]
type slack_command_response = {
  response_type: string,
  text: string,
  attachments: option(array(attachment))
};

and use them like:

let generateSlackJSONResponse = payload_record => {
  let response = {
    response_type: "ephemeral",
    text: "I process the command: " ++ payload_record.command,
    attachments: None,
  };
  slack_command_response_encode(response);
};

// ...

Axios.postData(
        payload_record.response_url,
        {
          slack_command_responseToJs(response);
        },
      )

Type attachment must have almost all fields optional. To not write everywhere field: None ... I want to use @bs.deriving abstract but as I understand I can't have dublicate @bs.deriving. What should I do if need slack_command_responseToJs for Axios but also want optional fields?!

Update:

To fix first problem I used:

Axios.postData(
        payload_record.response_url,
-        {
-          slack_command_responseToJs(response);
-        },
+       slack_command_response_encode(response) |> Obj.magic,
      )

Also attachments: None, created a json field "attachments": null but I need to not be at all or "attachments": undefined

Is it possible to do it with ppx_decco? Maybe some new attribute like [@decco.undefined] only for options

@decco] type mytype = {
    s: string,
    i: int,
    o: option(int),
    [@decco.undefined] u: option(int)
};

let encoded = mytype_encode({
    s: "hello",
    i: 12,
    o: None,
    u: None,
});

Js.log(Js.Json.stringifyWithSpace(encoded, 2));
/* {
     "s": "hello",
     "i": 12,
     "o": null,
     "u": undefined,
  } 
or
{
     "s": "hello",
     "i": 12,
     "o": null,
  } 
*/
thangngoc89 commented 4 years ago

Undefined property will be removed in JSON so I don't think this is the desired behavior

strdr4605 commented 4 years ago

How about not including the key in JSON if it's None and prefixed with [@decco.undefined] u: option(int) ?!

ryb73 commented 4 years ago

This is a tricky one. Just omitting the key might make sense. For now, you could do something like this:

let encodeUndefined = (encoder, value) =>
    switch value {
        | None => [%bs.raw "undefined"]
        | Some(v) => encoder(v)
    };

let decodeUndefined = (decoder, json) =>
    (json !== [%bs.raw "undefined"])
        ? Decco.optionFromJson(decoder, json)
        : Ok(None);

[@decco] type mytype = {
    u: [@decco.codec (encodeUndefined, decodeUndefined)] option(int),
};
strdr4605 commented 4 years ago

Thank you, will try https://github.com/ryb73/ppx_decco/issues/26#issuecomment-537703530 in the near future. Do you plan to add [@decco.undefined] as a new attribute?! If no, can you still give me some hints on how to extend ppx_decco and implement [@decco.undefined] to not use [@decco.codec (encodeUndefined, decodeUndefined)] every time. If yes,

can you still give ...

and I will try to make a pull request?!

ryb73 commented 4 years ago

Hey @strdr4605- I don't plan on working on this in the foreseeable future, and considering there's a pretty reasonable workaround, I'm wary about adding new functionality to handle this case unless this issue gets more traction. However, if you'd like to give it a go anyway, my comment in #6 might be helpful.

ryb73 commented 4 years ago

BTW, you maybe figured this out already, but if you don't want to repeat encodeUndefined and decodeUndefined everywhere, you can put them in your own module, say "MyCustomCodecs", along with let undefined = (encodeUndefined, decodeUndefined) and use [@decco.codec MyCustomCodecs.undefined] which would be a little more brief and would be about as convenient as using the proposed [@decco.undefined] feature.

ryb73 commented 4 years ago

You could also do something like

[@decco]
type undefined('a) = [@decco.codec (encodeUndefined, decodeUndefined)] option('a);

and use the undefined type in place of option whenever you need a field that encodes to undefined