Skip to content
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

Enforce no nulls in record types unless type is FSharpOption #36

Merged
merged 7 commits into from
Jan 29, 2020

Conversation

drhumlen
Copy link

@drhumlen drhumlen commented Jan 21, 2020

There should probably be check on [<AllowNullLiteral>](?) for the given type on the off chance somebody actually wants nulls in their F# record type.

@cmeeren
Copy link
Contributor

cmeeren commented Jan 22, 2020

I would perhaps improve the error message, saying that it's not nullable or allowed to be null, instead of not "optional". Nullability is a different concern than optionality (i.e. whether or not it's present). Differentiating between these is needed for e.g. JSON:API implementations.

@drhumlen
Copy link
Author

drhumlen commented Jan 22, 2020

Yeah, I agree. I think the non-option(al) feedback was more targeted at the F# developer than the API-consumer/user – since F# developers will understand that it is the option type that is missing.

I will change it so it is more understandable to frontenders by using the word null

I was also thinking that the error message is the same for a missing field and a field with a null. But that depedends on the ignoreNulls config(?).

@drhumlen
Copy link
Author

@cmeeren What do you think about the error message output now? What would be ideal error message in this case? We have the chance to make an excellent error messsage here.

@drhumlen
Copy link
Author

I'm a little worried @Tarmil is on a holiday or something at the moment. Maybe this repo should have a backup maintainer to merge PRs if one person is away..? 🤔 This could become the goto F# library for json.

@cmeeren
Copy link
Contributor

cmeeren commented Jan 24, 2020

@cmeeren What do you think about the error message output now? What would be ideal error message in this case? We have the chance to make an excellent error messsage here.

I have no idea, to be honest. It all depends on who the message is for - front-end (API clients) or back-end devs. I don't want to meddle too much in that; I'll leave it up to @Tarmil.

@Tarmil
Copy link
Owner

Tarmil commented Jan 25, 2020

Thanks for the PR! Not on vacation, just a bit busy :)

I think the error message as it is is good, maybe it could just mention the type of the field too.

Now, for allowing some nulls:

  • At a minimum, we need to check whether the field type is a union with CompilationRepresentationFlags.UseNullAsTrueValue, because ignoring it would straight up break idiomatic F# code. You can just take usesNull from the union converter and move it to Helpers. And since option has this flag, that check can even replace the type check for FSharpOption.

  • Allowing nulls on types with AllowNullLiteral would probably be a good idea too, as you suggested.

  • Finally, maybe we can add an explicit escape hatch in the form of an attribute on the field itself (rather than its type)? I think we can do without for now, but it's something to keep in mind for the future.

I also think that these checks should be precomputed and stored in RecordProperty, so that actual deserialization just has to test whether fieldProps.[i].CanBeNull.

(Also a small optimization: isNull x is faster than x = null)

@drhumlen
Copy link
Author

Screenshot 2020-01-28 at 15 18 26

I tried to add a test for this, but got this error. This means checking for the attribute [<AllowNullLiteral>] is redundant, right? 🤔

…. Also improve error message by specifying expected type.
@@ -72,6 +74,7 @@ type JsonRecordConverter<'T>(options: JsonSerializerOptions) =

override _.Read(reader, typeToConvert, options) =
expectAlreadyRead JsonTokenType.StartObject "JSON object" &reader typeToConvert
let doesAllowNullLiteral = not (isNull (Attribute.GetCustomAttribute(recordType, typeof<AllowNullLiteralAttribute>)))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we can remove this?

@@ -16,8 +16,10 @@ type internal RecordProperty =
type JsonRecordConverter<'T>(options: JsonSerializerOptions) =
inherit JsonConverter<'T>()

let recordType: Type = typeof<'T>
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a reasonable name?

@Tarmil
Copy link
Owner

Tarmil commented Jan 29, 2020

AllowNullLiteral would actually be on the type of the field, not of the record itself. And you're right, it can't be applied on a record, only on a class. But even for a class it's not really a good indication of whether a null can be expected: if a class is defined in F#, then yes, it can be null iff it has AllowNullLiteral attribute; but if a class is defined in C#, then it won't have that attribute regardless of nullability. So I think we should drop the test, and add a flag in the options to bypass this check for people who really need null classes.

@Tarmil Tarmil merged commit 64d6add into Tarmil:master Jan 29, 2020
@drhumlen
Copy link
Author

🎉 😄

@drhumlen drhumlen mentioned this pull request Jan 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants