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.
Added DIRECTIONS_V5_PRECISION6_FIXTURE_ARTIFICIAL_FIELDS just because DirectionsResponse always adds routeIndex and requestUuid.
|
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 |
| public abstract BannerComponents build(); | ||
|
|
||
| @Nullable | ||
| abstract Builder unrecognised(Map<String, Object> value); |
There was a problem hiding this comment.
we know that unrecognized fields are Strings, this way, let's make Map<String, String>
There was a problem hiding this comment.
it's not only strings. It can also be JsonObject, JsonArray, etc.
There was a problem hiding this comment.
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.
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.
@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.
Yea, as discussed internally - I think we should follow the same logic in both places.
There was a problem hiding this comment.
RouteOptions extends DirectionsJsonObject so it already has unrecognised fields
| * @since 3.4.0 | ||
| */ | ||
| public class DirectionsJsonObject implements Serializable { | ||
| public abstract class DirectionsJsonObject implements Serializable { |
There was a problem hiding this comment.
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.
AutoValue won't generate fields for not abstract methods
There was a problem hiding this comment.
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.
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.
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.
@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.
| 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.
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.
That makes sense 👍 I guess the equality of a serialized form is just a side effect, not a guarantee.
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.
@LukasPaczos , let me open it or provide alternative API in separate PR so that we can discuss it separately.
| if (property.equals(ACCESS_TOKEN_URL_PARAM_NAME)) { | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Do we have a test that covers this already?
There was a problem hiding this comment.
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
Goal
Implementation of #1344
Models from
mapbox-javashould not erase unrecognised json properties duringfromJson->toJsonexample
If we have a model with a single property
aand a JSON with properties
aandb{"a":"value", "b":"value"}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
unrecognisedfields map.I will share a link when fork will be publicly available.