Deserialization is the process of restoring an object from a JSON, XML or Protobuf data stream, essential for any mobile application working with a remote API. According to Apple Developer (2026), incorrect handling of incoming data remains one of the common causes of crashes on devices. JSONDecoder on iOS and Gson on Android are standard tools, but each has its own features and limitations.
Key Takeaways
Deserialization is the process of converting a byte stream or structured text into a programming language object. In mobile development, this process occurs every time an application receives a response from the server: a JSON string turns into an instance of the User, Order or Product class. The stability of screens displaying data to the user directly depends on the correctness of deserialization.
Serialization and deserialization are mutually inverse processes, rarely symmetrical in practice. Serialization converts an object into a string for sending to the server, while deserialization restores the object from the received string. The server may send a field that does not exist in the client model, use a different date format, or return null instead of a number. According to Square Engineering (2025), format asymmetry causes 23% of network layer errors in Android applications. To reduce risk, schema versioning and strict contract specification through OpenAPI are used.
JSON remains the most popular format for mobile APIs due to its human readability and built-in support. Protobuf from Google is used in high-load systems — it is 3-6 times more compact than JSON and parses faster, but requires code generation from .proto files and is unreadable without tools. XML is less common in modern mobile applications, yet it is used in SOAP services of enterprise systems and Android configuration files. MessagePack is a binary format similar to JSON in structure but more compact, popular in real-time systems.
The deserialization process goes through three stages. First, tokenization splits the raw text into tokens: keys, strings, numbers, and delimiters. Then syntactic analysis checks the correctness of the structure — whether brackets are closed, whether the quote type is correct, whether the format complies with RFC 8259. The final stage is mapping to the application’s object model, where each JSON key is assigned a class property considering the naming strategy.
Two approaches to mapping have emerged in mobile development. Reflection (Gson, JSONSerialization) analyzes the class structure at runtime via Java Reflection API or Objective-C runtime — it is flexible and requires no additional configuration, but is slower and consumes more memory. Code generation (Moshi codegen, kotlinx.serialization, Codable) generates code at compile time: faster, type-safe, and does not expose internal structure through reflection. JetBrains and Square recommend code generation for production builds — performance gains reach 2-4 times in Google benchmarks.
struct User: Codable {
let id: Int
let name: String
let email: String
let createdAt: Date
}
let json = """
{
"id": 42,
"name": "Alice",
"email": "alice@example.com",
"created_at": "2026-06-01T12:00:00Z"
}
"""
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let user = try decoder.decode(User.self, from: data)
Example of deserializing JSON into a User model in Swift. The convertFromSnakeCase strategy automatically converts snake_case API keys into camelCase model properties — a standard practice in iOS projects. The data parameter is the raw bytes of the server response received via URLSession. Error handling through try allows catching malformed JSON without crashing the application.
JSONDecoder supports four key strategies: useDefaultKeys (exact match), convertFromSnakeCase (snake_case → camelCase), custom (closure) and convertFromKebabCase (kebab-case → camelCase). For dates, .iso8601, .secondsSince1970, .millisecondsSince1970 and a custom dateFormatter are available. Choosing the right strategy is the first step toward robust deserialization, preventing most format mismatch errors.
JSONDecoder is the standard deserialization mechanism in the iOS SDK, working with the Codable protocol. JSONDecoder automatically parses JSON into struct or class instances, supporting nested objects, arrays, and primitives. For custom logic, the init(from: Decoder) method is used — it allows handling non-standard formats, missing fields in an older API version, or combining several JSON keys into one property.
struct Order: Decodable {
let orderId: String
let amount: Double
let status: OrderStatus
enum OrderStatus: String, Decodable {
case pending, confirmed, shipped, cancelled
}
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let order = try decoder.decode(Order.self, from: jsonData)
DateDecodingStrategy determines how JSONDecoder interprets date strings. .iso8601 is most commonly used — the standard REST API format. The nested enum OrderStatus is automatically decoded from JSON string values. This avoids magic numbers and makes the code self-documenting — the order status always has a strictly defined set of values.
Since Swift 4.2, Codable supports property wrappers for custom deserialization of individual properties. @DefaultValue is a popular wrapper that sets a default value if the field is missing from the JSON. @LosslessString converts a string to a number and vice versa. This is especially useful when the server sends an id as the string "123" but the model expects an Int. Property wrappers reduce boilerplate code in init(from:) and make models cleaner.
On Android, the choice of deserialization library depends on the language and project requirements. Gson from Google is the most common option, working through reflection but having performance issues with complex hierarchies. Moshi from Square supports both reflection and code generation, consuming less memory and processing large responses faster. kotlinx.serialization from JetBrains is a native Kotlin solution with compiler integration that does not use reflection at all.
@Serializable
data class User(
@SerialName("user_id")
val userId: Int,
val name: String,
val email: String,
@SerialName("created_at")
val createdAt: String
)
val json = Json { ignoreUnknownKeys = true }
val user = json.decodeFromString<User>(response)
@Serializable is a Kotlin compiler annotation that activates code generation for the class. The ignoreUnknownKeys parameter prevents crashes if the server sends a field missing from the model. For mapping snake_case keys, @SerialName is used — the equivalent of convertFromSnakeCase from iOS. According to JetBrains (2026), the library supports multiplatform: the same Serializable class works on Android, iOS (KMP), and server-side Kotlin.
The choice between libraries comes down to a speed-flexibility trade-off. Gson is good for prototypes and Java projects — it requires no annotations and works out of the box. Moshi occupies the middle ground: codegen via @JsonClass(generateAdapter = true) gives speed close to kotlinx.serialization, while reflection mode provides Gson’s flexibility. kotlinx.serialization is the fastest option for pure Kotlin projects but requires Kotlin 1.4+ and the Kotlin Serialization plugin in Gradle.
| Library | Mechanism | Speed | KMP |
|---|---|---|---|
| Gson | Reflection | Low | No |
| Moshi | Reflection / Codegen | Medium / High | No |
| kotlinx.serialization | Compiler codegen | High | Yes |
Type mismatch is a situation where JSON contains a value of one type but the model expects another. The server sent the string "42" instead of a number, or the number 1 instead of boolean true. On iOS, JSONDecoder will throw DecodingError.typeMismatch by default; on Android, Gson will attempt conversion, while Moshi and kotlinx.serialization require explicit adapters. The solution is to use lenient strategies or custom deserializers for specific fields.
When the server does not include an optional field, the code crashes with an error. Optional fields in Swift and nullable types in Kotlin solve the problem: if the field is null or missing from the JSON, the property gets nil/null, and the application continues working. For required fields, it is worth checking their presence at the API client level before deserialization. Moshi and kotlinx.serialization require all fields by default — nullable marking and default values remove this restriction.
Changes to the JSON structure on the server are a common source of production crashes. Standard practice is schema versioning through a version field in the root object and supporting 2-3 previous versions on the client. kotlinx.serialization allows declaring several models for different versions and selecting the right one based on the version field after initial parsing into JsonElement. Additional protection includes ignoreUnknownKeys for new fields and default values for fields that may be removed.
| Error | Symptom | Library with Protection |
|---|---|---|
| Type mismatch | DecodingError / Exception | kotlinx — coerceInputValues = true |
| Missing field | Crash on access | Moshi — @Transient + default |
| Incorrect date format | Decoding error | JSONDecoder — dateDecodingStrategy |
| Extra fields | Ignored or crash | kotlinx — ignoreUnknownKeys = true |
| Null in non-null field | Runtime crash | Moshi — lenient with @Nullable |
Logging deserialization errors is a mandatory practice in production. Wrap decode in do/catch, log the raw JSON and the expected model type to Crashlytics or Sentry. This will quickly identify which field of which API broke and on which app version. Without logging, a deserialization error looks like a mysterious crash with no context.
Frequently Asked Questions
Parsing is the analysis of structured text into constituent elements without necessarily creating a typed model. Deserialization is a specific case of parsing whose result is a full-fledged language object with known property types. Parsing can be streaming, deserialization always creates a complete object.
For a pure Kotlin project, kotlinx.serialization is recommended — it is integrated into the compiler, does not use reflection, and supports Kotlin Multiplatform. For an existing Java project — Moshi with code generation. Gson is better left for legacy projects where replacing it would require significant effort.
On iOS, use keyDecodingStrategy = .convertFromSnakeCase in JSONDecoder. On Android with kotlinx.serialization, use @SerialName for each field. In Moshi, apply @Json(name="field_name") or a global JsonAdapter.Factory. A consistent style at the project level is a best practice agreed upon in the API contract.
The most common reason is an unexpected null from the server on a field declared as required. In development, the server returns full data; in production, it returns a shortened response. The solution: mark all potentially missing fields as nullable (Kotlin) or optional (Swift), use ignoreUnknownKeys and default values.
Code generation (Moshi codegen, kotlinx.serialization, Codable) is 2-4 times faster than reflection in Google benchmarks. Besides speed, code generation is more type-safe, no class metadata is required at runtime, and type errors are caught at compile time rather than during deserialization.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also