-
Notifications
You must be signed in to change notification settings - Fork 4
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
[Question] Unbox in records #42
Comments
Can you give me a self-contained example? module Address = {
@spice
type t = {street: string}
}
@spice
type user = {
name: string,
address: Address.t,
}
let user = {name: "wk", address: {street: "munjung"}}
Js.log2("user", user)
let user2 = user->user_encode
Js.log2("user2", user2)
let user3 = switch user2->user_decode {
| Ok(user) => user
| Error(_) => assert false
}
Js.log2("user3", user3)
It seems fine to me |
Hey @mununki , thanks for the response. Yes, that is the default behavior, but I'm not sure if you noticed, but the |
How about using |
@spice
type t = {
name: string,
@spice.key("address_street") addressStreet: string,
} |
I think that the problem remains the same, my biggest issue is that all those decorators have the premise of the json schema being 1:1 to the record model in rescript, and that's not true in my use case, and that is totally ok too, if the default behavior don't work for me, I have no problem in creating custom encoders/decoders manually, I'm just trying to figure it out how. I think that the case you mentioned above wouldn't work because it would create an |
Can you elaborate your data and type definition you want decode json to? Concise example would be better to understand. Let me help you to make custom (d)encoder. |
I tried to do that in the issue description, not sure if I could made myself clear enough. I tried to create a concise example with only 2 fields. I'm consuming an api that have the following JSON schema representing an user: {"name": "My User Name"
"address_street": "my street",
"address_number": 42} I can mirror this same structure in my rescript code and use the type address = {
street: string,
number: int,
}
type user = {
name: string,
address: address
} And I would like to build an encoder/decoder to translate this for me, so for example: let response: Js.Json.t = myApiCall() // the response is the same JSON object described above
let user = response->myMagicDecoder
Js.Console.log(user.name) // My User Name
Js.Console.log(user.address.street) // my street
Js.Console.log(user.address.number) // 42 This is the result that I want to achieve but I couldn't find out just looking at the documentation, because of the reasons that I tried to describe in the previous comments, in the JSON data I have only 1 dictionary with all the fields, but some fields have the same "suffix" (like In this example if I decorate my user type and my address type with Is it clearer now? |
Sorry for the late reply. I think this is definitely possible with a custom (d)encoder. I'll try to come up with some code tonight or tomorrow. |
There are three possible ways to do this
@spice
type address = {
street: string,
number: int,
}
@spice
type user = {
name: string,
address: address,
}
let encoderUser = (v: user) => {
let streetJson = Js.Json.string(v.address.street)
let numberJson = Js.Json.number(v.address.number->Int.toFloat)
let nameJson = Js.Json.string(v.name)
[("name", nameJson), ("address_street", streetJson), ("address_number", numberJson)]
->Js.Dict.fromArray
->Js.Json.object_
}
let decoderUser = json =>
switch json->Js.Json.classify {
| JSONObject(dict) => {
let name = dict->Js.Dict.get("name")->Option.map(Js.Json.classify)
let addressStreet = dict->Js.Dict.get("address_street")->Option.map(Js.Json.classify)
let addressNumber = dict->Js.Dict.get("address_number")->Option.map(Js.Json.classify)
switch (name, addressStreet, addressNumber) {
| (
Some(Js.Json.JSONString(name)),
Some(Js.Json.JSONString(street)),
Some(Js.Json.JSONNumber(number)),
) =>
Ok({name, address: {street, number: number->Int.fromFloat}})
| _ => Error({Spice.path: "", message: "Expected name, street, number ", value: json})
}
}
| _ => Error({Spice.path: "", message: "Expected JSONObject", value: json})
}
let codecUser: Spice.codec<user> = (encoderUser, decoderUser)
@spice
type data = {user: @spice.codec(codecUser) user}
let data = %raw(`
{
"user" : {
"name": "woonki",
"address_street": "Munjung",
"address_number": 8
}
}
`)
let data = data->data_decode
let json = data->Result.getExn->data_encode You can create and utilize a custom codec for values of type user.
@spice
type address = {
street: string,
number: int,
}
@spice
type user = {
name: string,
address: address,
}
@spice
type userRaw = {
name: string,
address_street: string,
address_number: int,
}
let data = %raw(`
{
"name": "woonki",
"address_street": "Munjung",
"address_number": 8
}
`)
let data =
data
->userRaw_decode
->Result.map(userRaw => {
name: userRaw.name,
address: {street: userRaw.address_street, number: userRaw.address_number},
})
let json = data->Result.map(user =>
{
name: user.name,
address_street: user.address.street,
address_number: user.address.number,
}->userRaw_encode
)
Hope this helps. |
I've added the example for the solution 1, 2 in the examples directory. You can run on your local https://github.com/green-labs/ppx_spice/blob/main/examples/src/CustomCodecs2.res https://github.com/green-labs/ppx_spice/blob/main/examples/src/CustomCodecs3.res |
Hey! I tried to find this in documentation or in previous issues but couldn't find anything, so sorry if it's not a proper issue.
Is there any way that I can "unbox" a nested record?
If for example, I have the following in rescript:
and using
ppx_spice
, somehow I get an encoder that creates the following JSON with anuser
record:The text was updated successfully, but these errors were encountered: