Multipart form-data lacks a standardized way to handle arrays and nested objects, while many APIs require this feature. Over the past years, many companies and developers have proposed different solutions, but these often diverge significantly and require extra effort to adopt all of them. This blog post aims to analyze existing approaches and identify one approach that is scalable, straightforward, and production-ready for parsing nested objects and arrays in multipart form-data, which should have already been widely adopted by major companies.
This approach uses repeated key names to represent arrays. For example:
{ "name": "John", "hobbies": ["reading", "coding", "traveling"] }
name=John
hobbies=reading
hobbies=coding
hobbies=traveling
While initially straightforward, this method fails when handling nested structures. For instance:
address[street]=123 Main St
address[street]=456 Main St
address[street]=789 Main St
Could be interpreted as:
{ "address": { "street": ["123 Main St", "456 Main St", "789 Main St"] } }
{ "address": [ { "street": "123 Main St" }, { "street": "456 Main St" }, { "street": "789 Main St" } ] }
This ambiguity makes the approach unsuitable for scalable APIs with complex data structures. This method is documented in Swagger's multiple file upload example (reference).
This approach embeds JSON strings into form-data:
{ "name": "John", "hobbies": ["reading", "coding", "traveling"] }
name=John
hobbies=["reading", "coding", "traveling"]
Although intuitive, this method is rarely supported natively by frameworks and adds overhead for parsing and validation. Form-data is designed for key-value pairs, making this approach impractical for production environments.
MDN encourages using repeated keys (reference), thus JSON encoding approach should be least considered.
This widely adopted approach uses square brackets ([]
) to represent arrays. For example:
{ "name": "John", "hobbies": ["reading", "coding", "traveling"] }
name=John
hobbies[]=reading
hobbies[]=coding
hobbies[]=traveling
For nested objects, brackets allow clear and unambiguous representation:
{ "name": "John", "address": { "street": ["123 Main St", "456 Main St", "789 Main St"] } }
name=John
address[street][]=123 Main St
address[street][]=456 Main St
address[street][]=789 Main St
When representing arrays of objects:
{ "address": [ { "street": "123 Main St" }, { "street": "456 Main St" }, { "street": "789 Main St" } ] }
address[][street]=123 Main St
address[][street]=456 Main St
address[][street]=789 Main St
This method ensures clarity and scalability, making it the most suitable choice for production use.
This approach uses repeated keys with brackets and an index to represent arrays. For example:
{ "name": "John", "hobbies": ["reading", "coding", "traveling"] }
name=John
hobbies[0]=reading
hobbies[1]=coding
hobbies[2]=traveling
This approach is similar to Approach 3, but it adds an index to the key, so it can prevent ambiguity and cases where repeated keys are not allowed (e.g. Python dict
). In addition, AWS does not recommend having repeated keys in multipart uploads (reference), so this could be a great alternative in such cases. Overall, this approach is also scalable and production-ready. Fiber (in Golang) now supports this behavior (reference).
Among the approaches discussed, Repeated Keys with Brackets stands out as the most scalable, production-ready solution for handling arrays and nested objects in multipart form-data. It is widely adopted and avoids the pitfalls of ambiguity and excessive overhead, making it a reliable standard for modern APIs.
January 10, 2025