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

add support for jsonUnknown trait #1574

Merged

Conversation

benoitlouy
Copy link
Contributor

@benoitlouy benoitlouy commented Aug 9, 2024

Implement support for jsonUnknown trait defined in disneystreaming/alloy#180

@@ -1445,14 +1480,21 @@ private[smithy4s] class SchemaVisitorJCodec(
cursor: Cursor,
in: JsonReader
): scala.collection.Map[String, Any] => Z = {
val unknownValues =
if (keepUnknown) ListBuffer[(String, Document)]() else null
Copy link
Contributor

Choose a reason for hiding this comment

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

I appreciate the effort to minimise allocations 👍 . I would personally have gone for distinct JCodec implementations based on whether the structure has a @jsonUnknown field or not, which does induce some copy/pasting, but completely shaves off some checks on the critical path.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are now 2 JCodec implementations to separately handle structs with a jsonUnknown member and ones without.

else None
} else default
} else {
if (f.isRequired) unknownValue else Some(unknownValue)
Copy link
Contributor

@Baccata Baccata Aug 12, 2024

Choose a reason for hiding this comment

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

you cannot make the assumption that !required implies scala.Option : we're planning on relaxing our Schema model to allow for other ways of modelling optionality (to cater for java.util.Optional, for instance).

Moreover, maybe the user applies other type of customisations to what ends up annotated with jsonUnknown. Maybe they have a bijection from Document to circe's Json in place, etc.

So what you need to do instead is to compile the Document.Decoder associated to the field that is annotated with @jsonUnkown, in order to run the unknownValue (converted to Document) through it to get a value that'll be guaranteed to be of the correct type, without having to make any assumption.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has been addressed and the decoding now uses what you described.

if (unknownValue == null) {
if (default == null) {
if (f.isRequired) Map.empty
else None
Copy link
Contributor

Choose a reason for hiding this comment

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

See my comment here

.decode(Document.obj())
.getOrElse(
throw new RuntimeException(
s"${cursor.getPath(Nil)} Failed translating a Document.DObject to the type targeted by ${f.label}."
Copy link
Contributor

@Baccata Baccata Aug 13, 2024

Choose a reason for hiding this comment

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

use in.decodeError instead, to throw the exception

.decode(unknownValue)
.getOrElse(
throw new RuntimeException(
s"${cursor.getPath(Nil)} Failed translating a Document.DObject to the type targeted by ${f.label}."
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment on lines 1416 to 1425
case m: Map[_, _] =>
m.foreach { case (label: String, value: Document) =>
writeLabel(label, out)
documentJCodec.encodeValue(value, out)
}
case Some(m: Map[_, _]) =>
m.foreach { case (label: String, value: Document) =>
writeLabel(label, out)
documentJCodec.encodeValue(value, out)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I should have signposted this bit as well, but this is falling under the similar notion to what I was describing here :

  1. You cannot make the assumption that a smithy map necessarily translates to a Scala map
  2. You cannot make the assumption that the absence of required translates to Scala Option
  3. You cannot even make the assumption that at this level, you're dealing with Documents

Therefore you need to pre-compile the Document.Encoder and encode the field value against it, and pattern-match against the produced document to check it's a DObject, in which case you run the exact same foreach you have there

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This makes sense, thanks for the directions.

@Baccata
Copy link
Contributor

Baccata commented Aug 13, 2024

This is shaping up well ! This code is quite tricky and it's a very valuable PR, thank you very much for taking this on 👍

Copy link
Contributor

@Baccata Baccata left a comment

Choose a reason for hiding this comment

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

Giving a pre-emptive approval for this, for @lewisjkl to consider when the upstream work (alloy) has been merged and released

Copy link
Contributor

@lewisjkl lewisjkl left a comment

Choose a reason for hiding this comment

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

This looks good to me as well, great work on this one!

@Baccata Baccata merged commit d4edb32 into disneystreaming:series/0.18 Aug 23, 2024
11 checks passed
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