-
Notifications
You must be signed in to change notification settings - Fork 120
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
Roundtrip of unrecognised JSON properties #1394
Conversation
DirectionsResponse responseFromJson1 = DirectionsResponse.fromJson(json); | ||
public void testToFromJsonWithRealResponse() throws Exception { | ||
Gson gson = new GsonBuilder().create(); | ||
String originalJson = loadJsonFixture(DIRECTIONS_V5_PRECISION6_FIXTURE_ARTIFICIAL_FIELDS); |
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 DIRECTIONS_V5_PRECISION6_FIXTURE_ARTIFICIAL_FIELDS
just because DirectionsResponse
always adds routeIndex
and requestUuid
.
services-directions-models/src/main/java/com/mapbox/api/directions/v5/models/Bearing.java
Show resolved
Hide resolved
Did rough testing of performance. No microbenchmarks was involved, but I guess we can less or more understand performance impact. Test 1Deserialised and serialised directions repose 1000 times: for (int i = 0; i < 1000; i++) {
DirectionsResponse response = DirectionsResponse.fromJson(regularJsonString);
String json = response.toJson();
} 2210 ms Test 2Deserialised and serialised mutated directions repose 1000 times: for (int i = 0; i < 1000; i++) {
DirectionsResponse response = DirectionsResponse.fromJson(mutatedJsonString);
String json = response.toJson();
} 3075 ms Test 3Tested mutated json serialisation and deserialisation 1000 times on main branch: for (int i = 0; i < 1000; i++) {
DirectionsResponse response = DirectionsResponse.fromJson(mutatedJson);
String back = response.toJson();
} 2333 ms |
...es-directions-models/src/main/java/com/mapbox/api/directions/v5/models/BannerComponents.java
Outdated
Show resolved
Hide resolved
@@ -501,5 +502,8 @@ public abstract static class Builder { | |||
* @since 3.0.0 | |||
*/ | |||
public abstract BannerComponents build(); | |||
|
|||
@Nullable | |||
abstract Builder unrecognised(Map<String, Object> value); |
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.
we know that unrecognized fields are Strings, this way, let's make Map<String, String>
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 not only strings. It can also be JsonObject
, JsonArray
, etc.
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 do you want to operate with these fields as JsonObject or JsonArrays? Looks like we only need to serialize/deserialize them. So no need to keep Objects
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 was actually thinking whether we could consider making this map public (no in builder but in the resulting type), so that if someone would like to access the unrecognized fields, they don't need to serialize the type first. I believe this will be needed soon (for example for experimental EV 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.
@LukasPaczos Experimental EV and other options are allocated in RouteOptions. We don't extend RouteOptions with Unrecognized fields I think 🤔 Suppose, it's separate initiative, not really connected with this PR
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.
Yea, as discussed internally - I think we should follow the same logic in both places.
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.
RouteOptions
extends DirectionsJsonObject
so it already has unrecognised fields
services-directions-models/src/main/java/com/mapbox/api/directions/v5/models/Bearing.java
Show resolved
Hide resolved
|
||
/** | ||
* Provides a base class for Directions model classes. | ||
* | ||
* @since 3.4.0 | ||
*/ | ||
public class DirectionsJsonObject implements Serializable { | ||
public abstract class DirectionsJsonObject 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.
This is a breaking change (not sure how impactful it would actually be in practice). Could we make the methods return empty and implement downstream, how would it look like?
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.
nice catch 👍
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.
AutoValue won't generate fields for not abstract 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.
I see a few possible solutions right now.
Do breaking change
Maybe we can make ourself believe that it's not really a breaking change using following arguments: it more like a fix of a bug, nobody should have created an instance of DirectionsJsonObject
, it doesn't make any sense to create an instance of DirectionsJsonObject
. But abstract
do really changes API.
Workaround
I can create an abstract class BaseDirectionsJsonObject
which is going to be a parent of DirectionsJsonObject
and move unrecognised
filed there. I find this solution ugly but it won't break API.
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 think that we need to decide if somebody could be affected by not being able to instantiate DirectionsJsonObject
. If we're sure that we will affect nobody, and even if we will we're ready to reply in their issues on github, then I let's break the rule.
If we don't feel confident, let's workaround
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 feel confident. I can't imaging a case when somebody instantiate DirectionsJsonObject
but if somebody does it, I'd like to learn their case when they open an issue on our github 🙂
@LukasPaczos , @RingerJK , what do you think about it?
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.
@VysotskiVadim I agree. There's no practical reason to extend the DirectionsJsonObject
for developers, I think we can break here. Let's clearly state this in the changelog and go from there.
JsonObject originalJsonObject = gson.fromJson(originalJson, JsonObject.class); | ||
|
||
DirectionsResponse model = DirectionsResponse.fromJson(originalJson); | ||
JsonObject jsonFromObject = gson.fromJson(model.toJson(), JsonObject.class); | ||
|
||
assertEquals(originalJsonObject, jsonFromObject); |
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 originalJsonObject = gson.fromJson(originalJson, JsonObject.class); | |
DirectionsResponse model = DirectionsResponse.fromJson(originalJson); | |
JsonObject jsonFromObject = gson.fromJson(model.toJson(), JsonObject.class); | |
assertEquals(originalJsonObject, jsonFromObject); | |
DirectionsResponse model = DirectionsResponse.fromJson(originalJson); | |
String jsonFromModel = model.toJson(); | |
assertEquals(originalJson, jsonFromModel); |
We want to ensure the resulting strings are correct, the object wrapping step seems unnecessary.
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.
Strings won't be the same. An order of fields changes when json gets serialised to java model and then gets deserialised back. And I think it's acceptable behaviour. JsonObject
compares that 2 JSONs has the same set of fields with the same value and it doesn't care about order.
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.
That makes sense 👍 I guess the equality of a serialized form is just a side effect, not a guarantee.
services-directions-models/src/test/java/com/mapbox/api/directions/v5/utils/MutateJsonUtil.java
Outdated
Show resolved
Hide resolved
5c1ca3a
to
91ded5c
Compare
91ded5c
to
047da92
Compare
Codecov Report
@@ Coverage Diff @@
## main #1394 +/- ##
============================================
+ Coverage 75.23% 75.45% +0.22%
- Complexity 920 926 +6
============================================
Files 125 125
Lines 3957 3960 +3
Branches 645 646 +1
============================================
+ Hits 2977 2988 +11
+ Misses 706 698 -8
Partials 274 274
|
|
||
@Nullable | ||
@UnrecognizedJsonProperties | ||
abstract Map<String, SerializableJsonElement> unrecognised(); |
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.
@LukasPaczos , let me open it or provide alternative API in separate PR so that we can discuss it separately.
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.
...irections-models/src/main/java/com/mapbox/api/directions/v5/models/DirectionsJsonObject.java
Outdated
Show resolved
Hide resolved
...irections-models/src/main/java/com/mapbox/api/directions/v5/models/DirectionsJsonObject.java
Outdated
Show resolved
Hide resolved
if (property.equals(ACCESS_TOKEN_URL_PARAM_NAME)) { | ||
continue; | ||
} |
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.
Do we have a test that covers this already?
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.
yep, I added this line because one of tests failed
@RingerJK , @LukasPaczos , please review companion PR mapbox/auto-value-gson#2 |
06a3461
to
a8d931f
Compare
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, nice work!
Goal
Implementation of #1344
Models from
mapbox-java
should not erase unrecognised json properties duringfromJson
->toJson
example
If we have a model with a single property
a
and a JSON with properties
a
andb
the model should let unrecognised fields to do a roundtrip
Implementation
I forked the library which generates gson type adapters for us. Now if there are some unrecognised fields a type adapter put them into
unrecognised
fields map.I will share a link when fork will be publicly available.