-
Notifications
You must be signed in to change notification settings - Fork 182
fix: enhance model property handling for array types in Swift template #1087
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
Conversation
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.
Pull Request Overview
This PR updates the Swift model template to correctly map raw [Any]
arrays into [AnyCodable]
when generating model from(map:)
initializers.
- Adds a special case for properties typed as
[AnyCodable]
to map each element throughAnyCodable(_:)
- Provides a default empty array (
?? []
) for optional[AnyCodable]
properties - Leaves existing behavior for other array‐of‐model mappings unchanged
Comments suppressed due to low confidence (2)
templates/swift/Sources/Models/Model.swift.twig:84
- Using
as! [Any]
will crash if the cast fails. Consider usingas? [Any]
with a fallback (e.g.,map["..."] as? [Any] ?? []
) to avoid runtime errors.
(map["{{property.name }}"] as! [Any]...
templates/swift/Sources/Models/Model.swift.twig:84
- There should be dedicated unit tests covering model decoding for
[AnyCodable]
properties, including empty, non-array, and malformed inputs.
(map["{{property.name }}"] as? [Any] ?? []).map
@@ -81,7 +81,7 @@ open class {{ definition | modelType(spec) | raw }}: Codable { | |||
public static func from(map: [String: Any] ) -> {{ definition.name | caseUcfirst }} { | |||
return {{ definition.name | caseUcfirst }}( | |||
{%~ for property in definition.properties %} | |||
{{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} | |||
{{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}{% if property.type == 'array' and (property | propertyType(spec) | raw) == '[AnyCodable]' %}(map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} [Any]{% if not property.required %} ?? []{% endif %}).map { AnyCodable($0) }{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} |
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.
[nitpick] Detecting [AnyCodable]
via a raw string comparison is brittle. Consider exposing a metadata flag (e.g., property.isAnyCodableArray
) or using the schema to detect this case more reliably.
Copilot uses AI. Check for mistakes.
@@ -81,7 +81,7 @@ open class {{ definition | modelType(spec) | raw }}: Codable { | |||
public static func from(map: [String: Any] ) -> {{ definition.name | caseUcfirst }} { | |||
return {{ definition.name | caseUcfirst }}( | |||
{%~ for property in definition.properties %} | |||
{{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} | |||
{{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}{% if property.type == 'array' and (property | propertyType(spec) | raw) == '[AnyCodable]' %}(map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} [Any]{% if not property.required %} ?? []{% endif %}).map { AnyCodable($0) }{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} |
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.
[nitpick] Defaulting an optional [AnyCodable]
property to an empty array (?? []
) changes its semantic from nil
to []
. If consumer code relies on distinguishing nil
vs. empty, consider preserving nil
for optional arrays.
Copilot uses AI. Check for mistakes.
@@ -81,7 +81,7 @@ open class {{ definition | modelType(spec) | raw }}: Codable { | |||
public static func from(map: [String: Any] ) -> {{ definition.name | caseUcfirst }} { | |||
return {{ definition.name | caseUcfirst }}( | |||
{%~ for property in definition.properties %} | |||
{{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} | |||
{{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}{% if property.type == 'array' and (property | propertyType(spec) | raw) == '[AnyCodable]' %}(map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} [Any]{% if not property.required %} ?? []{% endif %}).map { AnyCodable($0) }{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} |
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.
Hey, as suggested by copilot, you can instead add a function in https://github.com/appwrite/sdk-generator/blob/master/src/SDK/Language/Swift.php That returns a bool. use the existing propertyType function and add function like isCodable
or something that makes sense that checks this case
@@ -81,7 +81,8 @@ open class {{ definition | modelType(spec) | raw }}: Codable { | |||
public static func from(map: [String: Any] ) -> {{ definition.name | caseUcfirst }} { | |||
return {{ definition.name | caseUcfirst }}( | |||
{%~ for property in definition.properties %} | |||
{{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} | |||
{%~ set isDocument = definition.name | lower == 'document' %} |
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 add a comment here, temporary until we fix the BE
What does this PR do?
Fixes handling for array types.
Test Plan
before:
after:
Related PRs and Issues
Have you read the Contributing Guidelines on issues?
yes.