Open hasanaburayyan opened 1 year ago
All that said, the specific use case of giving instructions to the json parser is something we should explicitly. If we have a list of the list of requirements and things we want to let users control, we can come up with the right language design and/or apis for that.
I recommend to repurpose this issue to something like "json parsing customization" or something like that and continue from there.
As for other types of serialization formats, that's a whole topic we need to cover separately with an RFC.
I don't like that syntax either... Doesn't feel extendable and IDE friendly (auto completion, etc). A system like that would allow de/ser very simple structures, but what about more complex ones?
Structures that contain valid JSON fields should be able to automatically be serialized/deserialized, right? In the case of Person
, the JSON could be {"firstName": "Cristian","lastName": "Pallarés","phone": "777777777"}
. Structures that contain non-json-serializable types would require some sort of implementation. I like the way you implement things in Rust, eg:
struct Person {
firstName: str;
lastName: str;
phone: str;
}
impl JsonSerialize for Person {
serialize(person: Person) {
let json = new Json();
json.add("first_name", person.firstName);
json.add("last_name", person.lastName);
json.add("phone_number", person.phone);
return json;
}
}
The Wing compiler can associate JsonSerialize.serialize
with Person
and use it whenever its needed.
Wing shouldn't do any automatic case conversion (just making sure)
Oh yea for sure no sort of automatic case conversions, maybe I could have been more explicit in the example by mapping firstName
to something like fname
As for other types of serialization formats, that's a whole topic we need to cover separately with an RFC.
I agree, that was not really the topic I wanted to convey in this RFC, I was more interested in discussing this custom parsing and how to make something thats extensible. Ill clean up the RFC to be more specific.
I really don't like Go's syntax...
I am not a huge fan of using annotations for language features. Annotations should be used by userland libraries.
@eladb You've mentioned this before and I'm curious where your feelings on this come from. Most languages I can think of that have annotations/decorators use it both for built-in (std lib) features as well as providing a way for users to create their own. I'm a fan of this personally (not the golang syntax though, yikes)
I also prefer avoiding annotations/attributes for key language features. One reason is that annotations/attributes ought to be usable in userland, and supporting this would require designing a reflection API of some kind (something that might be better to defer until we have a library ecosystem and we've collected more use cases).
There are also several examples where annotations have been used in popular languages in places where a dedicated piece of syntax would be preferable. An obvious one that comes to mind is Java's @Override
tag, which arguably should have been a dedicated keyword if it weren't for backwards compatibility. Some other examples that seem out of place to me are Python's @staticmethod
, @classmethod
, and @abstractmethod
annotations, and C#'s [NotNull]
and [DoesNotReturn]
attributes.
This isn't to say that every feature should have a dedicated syntax; there will be plenty of use cases in "tail" of language usage. But something like how a struct is JSON serialized/deserialized feels like it could be important enough to have its own syntax to me, given the place Json
has in Wing today.
To play the other side, I'm also not sure how many use cases a dedicated syntax would need to support. Maybe listing them out (and seeing which can already be achieved in Wing today) would helps us figure out if field tags or annotations or some other system is the right call after all. Some examples:
I don't think there's a way to do (1) today in Wing (because the field has a space in its name) but the others might have workarounds?
@MarkMcCulloh @Chriscbr @eladb @skyrpex
I updated this RFC to be more specific to Json conversion. I also suggested another syntax closer to Rust's Serde (since everyone hates Go 😂)
I also added in examples of how other languages/libraries handle this problem. Im super open to the idea of adding some sort of more dedicated way to pass instructions to the parser/validator. Just fresh out of suggestions at the moment but Ill ponder more over the weekend.
Hi,
This issue hasn't seen activity in 60 days. Therefore, we are marking this issue as stale for now. It will be closed after 7 days. Feel free to re-open this issue when there's an update or relevant information to be added. Thanks!
Hi,
This issue hasn't seen activity in 60 days. Therefore, we are marking this issue as stale for now. It will be closed after 7 days. Feel free to re-open this issue when there's an update or relevant information to be added. Thanks!
Hi,
This issue hasn't seen activity in 90 days. Therefore, we are marking this issue as stale for now. It will be closed after 7 days. Feel free to re-open this issue when there's an update or relevant information to be added. Thanks!
Thanks for updating the RFC Hasan! thought I'd share some general thoughts / questions
Putting aside the exact syntax, one way to view the options is by asking what we're exposing to the user to customize or override. (Are we giving them control of a schema? Or are we giving them control of a set of parsing or stringifying functions? Or are we giving them control of both, or something in between?)
Like you mentioned, in our current implementation, when a user runs MyStruct.fromJson(data)
, the compiler generates a JSON Schemas for the struct, and runs a JSON Schema validator to check "data" is valid. It doesn't do anything to transform the fields.
Suppose in order to support custom struct fields we added some syntax that lets you change the schema in some way. I'll borrow the syntax from the original issue:
struct Person {
#[json(name=["first_name", "fname"])]
firstName: str;
#[json(name=["last_name", "lname"])]
lastName: str;
age: num;
}
Now, if you tried parsing the object, an updated schema would be used, so the "first_name" field would be validated as satisfying the schema. However, if the data isn't automatically transformed in some way, you wouldn't be able to access it 😱:
let person = Person.fromJson(Json { first_name: "Jeff", last_name: "Bezos", age: 60 });
log(person.firstName); // nil
For serialization into JSON, a similar issue could happen. To fix it, we'd need to generate code at compile time that transforms the "firstName" field into "first_name".
The other note I want to add is that I think renaming fields is just one example of a custom mapping / serialization option. Some other use cases might be:
I don't know if we necessarily need a solution that solves all of these all at once. But we might want to consider "what kinds of solutions would allow us to support these kinds of customizations generally / without requiring language support for each one"?
BTW - one other hybrid syntax could be to add some annotation to the struct that references the transformer(s) in some way:
@serializer(PersonSerializer)
// or maybe @serializer(new PersonSerializer())
struct Person {
firstName: str;
lastName: str;
age: num;
}
class PersonSerializer {
fromJson(obj: Json): Person => {
let person = Person {
firstName: obj["first_name"].asStr(),
lastName: obj["last_name"].asStr(),
age: obj["age"].asNum(),
};
return person;
}
toJson(self): Json => {
return {
"fist_name": self.firstName,
"last_name": self.lastName,
"age": self.age
};
}
}
(I'm not sure if I like this better than any of the other options - but wanted to add it for comparison)
Feature Spec
Wing now offers a way to define mappings between struct fields and their json field equivalent(s)
This allows an instance of person to be created from an of the following Json objects:
The resulting schema uses "anyOf" to ensure validation
Rational
Currently in Wing when we define a Struct this generates a Json schema that can be used for runtime validation of json -> struct conversions. For example the following Struct:
Produces has a json schema defined as:
which means that a Json object that structurally fulfills the schema will be valid when attempting to convert to an instance of
Person
.However, it will be the case that a Json object may have field names that mismatch the Wing struct definition. Its not a guarantee that all data read from the internet will come in the same case convention as Wing.
Usecases
Alternate approaches
Custom Transformer
Add an optional argument to the
toJson
andfromJson
methods that allow a custom transformer to be passed in.Overriding
fromJson
andtoJson
methodsHow do other languages do this?
Golang (uses struct tags):
Rust (serde):
Java (Jackson):
C# (Newtonsoft.Json):