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
Support unrecognised fields #1
Conversation
…plicated adapter factories
@LukasPaczos , @RingerJK , because of repo recreation old comments were removed. I will transfer unresolved ones manually |
...alue-gson-extension/src/main/java/com/ryanharter/auto/value/gson/AutoValueGsonExtension.java
Outdated
Show resolved
Hide resolved
...-value-gson-runtime/src/main/java/com/ryanharter/auto/value/gson/GsonTypeAdapterFactory.java
Show resolved
Hide resolved
...alue-gson-extension/src/main/java/com/ryanharter/auto/value/gson/AutoValueGsonExtension.java
Outdated
Show resolved
Hide resolved
...ue-gson-runtime/src/main/java/com/ryanharter/auto/value/gson/UnrecognisedJsonProperties.java
Outdated
Show resolved
Hide resolved
...alue-gson-extension/src/main/java/com/ryanharter/auto/value/gson/AutoValueGsonExtension.java
Outdated
Show resolved
Hide resolved
example/src/main/java/com/ryanharter/auto/value/gson/example/UnrecognisedExample.java
Outdated
Show resolved
Hide resolved
import java.io.Serializable; | ||
import java.util.Objects; | ||
|
||
public class SerializableWrapper implements Serializable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain why do we need this class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JsonObject
and other classes from gson aren't serialisable. If your model is Serializable
, unrecognised fields breaks java serialisation. Serializable wrapper is a workaround to let unrecognised fields to be serialised.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to SerializableJsonElement
.findFirst() | ||
.orElse(null); | ||
if (unrecognisedJsonPropertiesContainer != null) { | ||
TypeName mapOfObjects = ParameterizedTypeName.get(LinkedHashMap.class, String.class, Object.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LinkedHashMap
- why is the order important so that we need to use a linked map?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's convenient for testing when an order of unrecognised fields preserves during deserialisation/serialisation. See UnrecognisedFieldsTest
.
Do you see any problem with LinkedHashMap
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Capturing from docs:
Performance is likely to be just slightly below that of HashMap
That's my only consideration - we don't take real advantage of the ordering in production, so we could probably drop it. If it significantly improves debugging and testing then let's leave it in place, with the amount of data we're dealing with here it's probably an insignificant difference anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first answer from stack overflow says that it takes 30% more time to create LinkedHashMap
than HashMap
but iteration over LinkedHashMap
is 50% faster (BTW I don't trust house test in terms of access, LinkedHashMap
can't be faster in access). For round trip we need both creation and iteration.
...alue-gson-extension/src/main/java/com/ryanharter/auto/value/gson/AutoValueGsonExtension.java
Outdated
Show resolved
Hide resolved
This reverts commit 8da4673.
.findFirst() | ||
.orElse(null); | ||
if (unrecognisedJsonPropertiesContainer != null) { | ||
TypeName mapOfObjects = ParameterizedTypeName.get(LinkedHashMap.class, String.class, Object.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Capturing from docs:
Performance is likely to be just slightly below that of HashMap
That's my only consideration - we don't take real advantage of the ordering in production, so we could probably drop it. If it significantly improves debugging and testing then let's leave it in place, with the amount of data we're dealing with here it's probably an insignificant difference anyway.
String.format("Only one method can be annotated with %s", UnrecognizedJsonProperties.class.getSimpleName()), | ||
unrecognisedProperties.get(1).element | ||
); | ||
return unrecognisedProperties.get(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not throw here and stop processing? It's an obvious mistake that we shouldn't really continue with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't dig deep in this questions, but here's what I've learned about annotation processors so far:
The doc recommends to prefer error reporting to uncaught exceptions.
processor should throw an uncaught exception only in situations where no error recovery or reporting is feasible.
I guess it's because users would like to see an error like "Only one method can be annotated with ..." rather than "Annotation processor X failed with an exception".
I also noticed that this processor doesn't throw exceptions in case of other errors. It logs error(logged error fails compilation for user) and carries on with generation. I decided to just stick to existing approach and don't spend time for digging deeper. Any way, if you find this question very important I'm ready to dig deeper and learn more about best practises in annotation processing.
|
||
@Nullable | ||
@UnrecognizedJsonProperties | ||
abstract Map<String, Object> unknownProperties(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
abstract Map<String, Object> unknownProperties(); | |
abstract Map<String, JsonElement> unknownProperties(); |
Is that always true? I understand yes, if that's the case we can even enforce it as part of the annotation processor (that the annotated type is always Map<String, JsonElement>
). That could be the next step though, not a blocker.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be if we didn't need java serialization. Right now it's always SerializableWrapper
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, is transition later from Map<String, Object>
to Map<String, JsonElement>
can be considered as a breaking change? Java's Map
isn't covariant like Kotlin's one. Scenarios like object.unknownProperties().put("new key", "new value")
could be broken. I will check if I can make the map immutable under the hood
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to abstract Map<String, SerializableJsonElement> unknownProperties();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, only a request to update the docs.
import java.io.Serializable; | ||
import java.util.Objects; | ||
|
||
public class SerializableJsonElement implements Serializable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a short doc entry for this class and its public methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
import static java.lang.annotation.RetentionPolicy.SOURCE; | ||
|
||
/** | ||
* Indicates that a filed of type Map<String, Object> is a container for unrecognised JSON properties. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* Indicates that a filed of type Map<String, Object> is a container for unrecognised JSON properties. | |
* Indicates that the annotated field is a container for unrecognised JSON properties. The field has to be of type Map<String, SerializableJsonElement>. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated, thanks
Companion PR of mapbox/mapbox-java#1394
The problem
I want to process a
json
file, for example:having a model which isn't aware of all the properties from the
json
file, for example:but when I serialize the model back to
json
after processing I want the unrecognized propertyb
to be present injson
.For example, when I deserialize, update property
a
to a value"c"
, and serialize json back I want the following output:See more mapbox/mapbox-java#1344
Desired behaviour in code
Idea
I can manually add an
unrecognized
property to the model and mark it with@UnrecognisedJsonProperties
annotation:If the generated type adapters were aware of the
unrecognized
property and put all the unrecognized fields from a parsed json there during reading, and wrote unrecognized properties from back to json file during writing I would get desired behavior.