-
Notifications
You must be signed in to change notification settings - Fork 156
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
MAPSIOS-118 Implement DictionaryEncoder for Layer properties. #1476
Conversation
- Encode Layer properties directly to a dictionary, instead of encoding to data using JSONEncoder and then serializing the data to a JSON dictionary. - Support encoding null values based on encoding context.
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.
Very interesting to see a custom implementation of Swift's encoders and decoders 👍
|
||
let layoutProperties = try XCTUnwrap(rootProperties["layout"] as? [String: Any]) | ||
XCTAssertEqual(layoutProperties["visibility"] as? String, "visible", "visibility is not reset and should keep old 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.
Check for final layer values to be as expected.
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 does not work with the test setup like this, because we would need to get a dictionary of layer's properties then decode it to a Layer, so we could only verify that when we call Style.updateLayer
it will invoke its StyleManager
with the expected parameters, which is a Dictionary of merged old properties and new 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.
Can we have integration tests for this?
} | ||
|
||
func encodeIfPresent<T: Encodable>(_ value: T?, forKey key: Key) throws { | ||
let shouldEncodeNilValue = userInfo[.shouldEncodeNilValues] as? Bool ?? false |
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.
With shouldEncodeNilValues
baked into the source it'll be more clear to expose it as a property on DictionaryEncoder
rather than user info key.
Primary use case for userInfo
is for the user of the API to pass some contextual info down the line.
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.
good point, this is done now!
} | ||
} | ||
|
||
func encodeIfPresent<T: Encodable>(_ value: T?, forKey key: Key) throws { |
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 seems that something is wrong with this - this method does not seem to be called for primitive types(like Double - e.g. minzoom is not encoded).
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.
my bad, I didn't commit encodeIfPresent
for primitive types :-D
["value": 10] | ||
) | ||
XCTAssertEqual( | ||
try sut.encode(DummyNilable<Int>(value: nil)) as? [String: Int?], |
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.
There's some funky stuff going on - this works with a generic struct. But when using a similar specialized struct - the result is an empty dictionary.
private struct DummyFloatEncodable: Encodable {
let value: Float?
}
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.
Maybe a sure way to test these would be to define a dedicated struct for every test 🤔
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.
yes, I am also thinking of using stylegen to generate the tests case for each Layer's struct :-D let's see!
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 test is expected the dictionary to be empty though? Did you mean to comment this in other places?
@@ -344,4 +344,44 @@ final class StyleTests: XCTestCase { | |||
bounds: CoordinateBounds(southwest: .random(), northeast: .random())) | |||
) | |||
} | |||
|
|||
func testStyleCanUpdateLayer() throws { |
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.
Add tests that check that:
- nilling out unmodified properties works correctly
- updating a property value that already has a value works correctly
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.
done!
T.self == Data.self || T.self == NSData.self || | ||
T.self == Date.self || T.self == NSDate.self || | ||
T.self == Decimal.self || T.self == NSDecimalNumber.self || | ||
T.self == URL.self || T.self == NSURL.self |
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.
What does it mean that only these types are supported?
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.
aye, the function name should be changed into something that makes more sense, but this is to check for the type and encode them properly (maybe later will be based on configuration i.e data will be encoded with base64, date will be encoded with ISO8601 etc)
I will rename the function.
- Add shouldEncodeNilValues as a property of DictionaryEncoder. - Fix encodeIfPresent for primitive types when a container types is not generic and update unit tests. - Update Data, Date, Decimal and URL encoding to match the default behavior of JSONEncoder.
} | ||
|
||
func encodeIfPresent(_ value: Bool?, forKey key: Key) throws { | ||
try encodeIfPresent(NilEncodable(value), forKey: key) |
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.
What does wrapping the value into NilEncodable
do here? For me it seems that it is the same as passing value
directly, if the value is nil - nothing will be encoded, non-nil - it will be encoded.
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.
because these are overloading methods with same signature, so I wrap them in a struct so it will trigger func encodeIfPresent<T: Encodable>(_ value: T, forKey key: Key)
instead, otherwise I would have to do the check for shouldEncodeNilValues
in all the methods of encodeIfPresent
Another approach would be having a private method
private func encodeNilIfNeeded<T: Encodable>(_ value: T?, forKey key: Key) throws {
guard value != nil || shouldEncodeNilValues else { return }
try encode(value, forKey: key)
}
then have all the encodeIfPresent
to point to this one, what do you think?
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.
Ah, makes sense 👍 I'm not sure if that is the case of premature optimization, but it seems to me that wrapping every primitive into a whole struct is could impact encoding performance. Because of this I'd prefer to have it as a separate method(like encodeNilIfNeeded
in your comment).
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 done now :)
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.
Well done 👍
NOTE: This will depend on the upstream PR https://github.com/mapbox/mapbox-gl-native-internal/pull/3736 in order to support passing null
StyleTransition
Pull request checklist:
## main
heading near the top).