skip to content

Platform Libraries

The SDK libraries most Dart code leans on: dart:core for text, dates and URIs, dart:convert codecs for JSON and bytes, and dart:io for files and processes. Interviewers check fluency with them.

part ofDartoverview, primer and where to startread it →
on this pageshow

explore

questions

17

In Dart, what does jsonDecode return, and why does decoded JSON need casts or patterns before you can use it as typed data?

level: juniorimportance: must knowfreq 66%

answer

  1. the return type is dynamic
  2. objects become Map<String, dynamic>
  3. arrays become List<dynamic>
  4. as List<String> fails, cast<String>() works
  5. whole numbers decode as int

basics

~10 s

jsonDecode returns dynamic: objects become Map<String, dynamic>, arrays List<dynamic>, numbers int or double. Nothing is checked against your types, so you cast, convert lists with cast() or List.from, or validate with a pattern.

solid answer

~40 s

`jsonDecode(source)` returns `dynamic`, and the value inside is built only from `Map<String, dynamic>`, `List<dynamic>`, `String`, `num`, `bool` and `null`. Dart cannot know your schema, so the compiler lets `dynamic` flow anywhere and failures appear at runtime. Three traps: `as List<String>` throws, because the runtime object is a `List<dynamic>` whatever it contains, so use `.cast<String>()` or `List<String>.from(...)`; a whole number such as `1` decodes as `int`, so `as double` fails and `(x as num).toDouble()` is safer; and a missing key yields `null`. I validate the shape with a Dart 3 pattern such as `if (data case {'theme': String theme})`, or in a `fromJson` factory, and enable `strict-casts` so implicit `dynamic` downcasts show up in analysis. Invalid JSON text throws a `FormatException`.

code

dart · 17 lines
dart
import 'dart:convert';

void main() {
  const text =
      '{"theme": "dark", "fontScale": 1, "pinned": ["java", "dart"], "version": 3}';
  final data = jsonDecode(text); // dynamic

  final map = data as Map<String, dynamic>; // fine: the decoder builds this type
  // map['pinned'] as List<String>; // TypeError: it is a List<dynamic>
  final pinned = (map['pinned'] as List).cast<String>();
  // map['fontScale'] as double; // TypeError: 1 decodes as int
  final fontScale = (map['fontScale'] as num).toDouble();

  if (data case {'theme': String theme, 'version': int version}) {
    print('$theme v$version $pinned $fontScale'); // dark v3 [java, dart] 1.0
  }
}

go deeper

for a junior

Know that jsonDecode returns dynamic with maps and lists inside, and that you must cast values before using them as typed data.

for a middle

Explain why as List<String> fails while cast<String>() or List.from works, the int-versus-double trap, and FormatException for bad text.

for a senior

Validate at the boundary with patterns or fromJson factories, turn on strict-casts, and keep dynamic from leaking into widgets or business logic.

for a principal

Decide where schema checking lives, generated models, hand-written parsers or server contracts, and how the app degrades when a payload does not match.

## What comes out of `jsonDecode` `jsonDecode` in `dart:convert` is shorthand for `json.decode`. Its declared return type is **`dynamic`**, and the `JsonDecoder` documentation lists what the value can contain: | JSON | Dart value | |---|---| | object `{...}` | `Map<String, dynamic>` | | array `[...]` | `List<dynamic>` | | string | `String` | | number | `int` for whole numbers, otherwise `double` | | `true` / `false` | `bool` | | `null` | `null` | The containers are created as `Map<String, dynamic>` and `List<dynamic>` regardless of what they hold. That runtime type is what every cast is checked against. ## Why casts are needed Dart's type system cannot see inside a JSON string, so `dynamic` switches off static checking. Code like `final List<String> tags = jsonDecode(text)['tags'];` compiles, because `dynamic` is implicitly downcast, and then fails when it runs. Three runtime failures account for most bugs: 1. **Generic list casts.** `decoded['pinned'] as List<String>` throws a `TypeError`, since the object's runtime type is `List<dynamic>` even if every element is a string. Use `(decoded['pinned'] as List).cast<String>()`, which returns a view that checks each element when read, or `List<String>.from(...)`, which copies and checks eagerly. 2. **Numbers.** JSON has one number type. `1` decodes as `int`, `1.0` and `1.2` as `double`, so `value as double` breaks as soon as a writer emits `1`. Read numbers as `num` and convert with `toDouble()` or `toInt()`. 3. **Missing or null keys.** `map['fontScale']` is `null` when the key is absent, and a non-nullable cast throws. Decide on defaults explicitly. Casting the top-level object with `as Map<String, dynamic>` does work, because that is exactly the map type the decoder creates. ## Cast idioms that work | Goal | Idiom | |---|---| | a required string | `map['theme'] as String` | | an optional string | `map['note'] as String?` | | any number as a double | `(map['fontScale'] as num).toDouble()` | | a list of strings | `(map['pinned'] as List).cast<String>()` or `List<String>.from(...)` | | a nested object | `map['display'] as Map<String, dynamic>` | | a map of ints | `Map<String, int>.from(map['limits'] as Map)` | Each idiom either succeeds with a correctly typed value or throws at the line that reads the field, which makes a bad payload easy to locate. ## Validating with patterns Dart 3 patterns make shape checks declarative. An `if-case` statement checks types, keys and structure at once and binds typed variables: - `if (data case {'theme': String theme, 'version': int version}) { ... }` confirms `data` is a map, both keys exist, and the values have the stated types. - A `switch` over the decoded value can route different payload shapes to different branches. - Anything that fails the pattern falls through to your error handling instead of throwing deep inside a widget. This is the dart.dev recommendation for data from external sources: validate the structure first rather than trusting it. ## Making the analyzer help - `analyzer: language: strict-casts: true` in `analysis_options.yaml` reports every implicit downcast from `dynamic`, such as passing `jsonDecode(text)` straight to a `List<String>` parameter. - Declaring decoded values as `Object?` instead of `dynamic` forces an explicit check or cast before use. - Model classes with `fromJson` factories, hand-written or generated, keep the casts in one place. ## Errors to expect - **Invalid JSON text**, such as a trailing comma or single quotes, makes `jsonDecode` throw a `FormatException`. - **Wrong shapes** surface as `TypeError`s at the first cast that disagrees, which may be far from the decode call. - Catch and report both at the boundary where data enters the app, so the rest of the code works with typed values. ## Summary for an interview Say that `jsonDecode` returns `dynamic` built from `Map<String, dynamic>`, `List<dynamic>` and primitives, explain why `as List<String>` fails while `cast` works, mention the `int` versus `double` trap, and show a pattern or `fromJson` validation step. That covers what interviewers mean when they ask why decoded JSON needs casts.

  • What is the difference between list.cast<String>() and List<String>.from(list)?
    `cast<String>()` returns a lazy view over the original `List<dynamic>` that checks each element's type when it is read, so a bad element fails later, at access. `List<String>.from(list)` copies the elements into a new list and checks them all immediately, which fails early and gives an independent list.
  • What happens when jsonDecode is given text with a trailing comma?
    It throws a `FormatException`, because trailing commas are not valid JSON. Catch it where the text enters the app, such as after reading a settings file, and fall back to defaults or report the corrupt file.
  • How does strict-casts in analysis_options.yaml help with JSON code?
    With `strict-casts: true` the analyzer stops allowing implicit downcasts from `dynamic`. Passing `jsonDecode(text)` straight to a `List<String>` parameter becomes an error, so every conversion from decoded JSON has to be written explicitly, which is where the runtime failures would otherwise hide.

saying these in an interview costs you the question

  • jsonDecode returns a typed object matching the JSON's structure
  • A JSON array of strings decodes to a List<String>
  • Every JSON number decodes to a double
  • Casting with as List<String> is the safe way to type a decoded list
  • jsonDecode returns null for invalid JSON text
open as a page

In Dart, what is the difference between int.parse and int.tryParse, and which should read a guest count typed into a booking form?

level: juniorimportance: must knowfreq 62%

basics

~10 s

int.parse throws a FormatException when the text is not a valid integer; int.tryParse returns null instead. For user input such as a guest count, use tryParse, handle null, then range-check the value.

open as a page

In Dart, how does jsonEncode handle a custom settings object, and how do toJson(), toEncodable and JsonEncoder.withIndent produce pretty-printed JSON?

level: middleimportance: must knowfreq 52%

basics

~10 s

jsonEncode writes numbers, strings, booleans, null, lists and string-keyed maps directly; for anything else it calls toEncodable, which by default calls the object's toJson(). For pretty output use JsonEncoder.withIndent(' ').convert(settings).

open as a page

In Dart, how do UTC and local DateTime values differ, and how should a booking app parse, store and compare check-in times?

level: middleimportance: must knowfreq 55%

basics

~20 s

A DateTime is anchored either in UTC or in the device's local zone, shown by isUtc. Store and send instants in UTC, convert with toLocal() only for display, and compare with isAtSameMomentAs or compareTo, since == also requires the same zone.

open as a page

Why does a Flutter web build fail when code calls dart:io's Platform.isAndroid, and how do you branch between web and native code safely?

level: middleimportance: must knowfreq 55%

basics

~20 s

dart:io wraps operating-system files, processes and sockets that a browser does not expose, so on the web its APIs, including Platform.isAndroid, are unsupported and fail at runtime. Check kIsWeb first, use defaultTargetPlatform, or split code with conditional imports.

open as a page

In Dart's dart:io, what is the difference between File.readAsString() and File.readAsStringSync(), and when is the Sync variant acceptable?

level: juniorimportance: should knowfreq 45%

basics

~20 s

readAsString() returns a Future<String> so the isolate keeps handling events while the file is read; readAsStringSync() returns the String directly and blocks the isolate until the read finishes. Sync suits short scripts, not a Flutter UI isolate.

open as a page

In Dart, how do the utf8, base64 and latin1 codecs differ, and what happens when utf8.decode receives bytes that are not valid UTF-8?

level: middleimportance: should knowfreq 38%

basics

~10 s

utf8 and latin1 convert between String and bytes; base64 converts bytes to ASCII text and back. utf8.decode throws a FormatException on malformed bytes unless allowMalformed is true, which substitutes U+FFFD.

open as a page

In Dart, why build a long string such as a booking confirmation with StringBuffer instead of += in a loop, and what does StringBuffer offer?

level: middleimportance: should knowfreq 42%

basics

~10 s

Dart strings are immutable, so every += creates a new string and copies what came before. StringBuffer collects pieces with write, writeln and writeAll and joins them once when toString() is called.

open as a page

In Dart, how do you build a request Uri with query parameters safely, and why does Uri.https fail when a query value is an int?

level: middleimportance: should knowfreq 32%

basics

~20 s

Build it with Uri.https(host, path, queryParameters) or the Uri() constructor, which percent-encode every key and value. Each value must be a String, null or an Iterable<String>, so an int fails at runtime; convert it with toString() first.

open as a page

In Dart's dart:io, when should you read a file with File.openRead() rather than readAsString(), and how does File.openWrite() report errors?

level: middleimportance: should knowfreq 35%

basics

~10 s

Use File.openRead() for large files or incremental processing: it returns a Stream<List<int>> of byte chunks instead of loading everything. File.openWrite() returns an IOSink; write errors surface on its flush(), close() and done futures.

open as a page

In Dart, how do Codec.fuse, Converter chaining and LineSplitter let you process a large JSON Lines byte stream without building one giant string?

level: seniorimportance: should knowfreq 24%

basics

~20 s

Converters such as utf8.decoder implement StreamTransformer, so a byte stream can be piped through utf8.decoder and LineSplitter and each line decoded separately. fuse joins codecs or converters, and JSON with UTF-8 fused skips the intermediate string.

open as a page

A Dart booking app counts nights as checkOut.difference(checkIn).inDays on local-midnight DateTimes and is off by one around a daylight-saving change; why, and how do you fix it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

difference() measures elapsed time, and a local day containing a clock change lasts 23 or 25 hours, so two local midnights can be 47 hours apart and inDays truncates to 1. Count calendar days on DateTime.utc dates instead.

open as a page

In a Dart command-line tool that shells out to git, when do you use Process.run versus Process.start, and why can a started child process hang?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Process.run waits for the child to exit and returns a ProcessResult with exitCode, stdout and stderr. Process.start returns a live Process with stdin, stdout and stderr streams; it hangs if you never drain stdout or stderr.

open as a page

In Dart, what does the reviver argument of jsonDecode do, and when is it a better fit than converting values after decoding?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

A reviver is called for every parsed property, list element and finally the root, with a String key, int index or null; its return value replaces the parsed value, for example turning ISO strings into DateTime.

open as a page

In Dart, why does RegExp(r'[A-Z]{3}-\d{4}').hasMatch('xxLIS-0042yy') return true, and how do you validate and extract parts of a booking reference?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

hasMatch searches for the pattern anywhere in the input, so an unanchored pattern accepts text with extra characters around it. Anchor it with ^ and $, then use firstMatch and named groups to pull out the parts.

open as a page

In a Dart command-line program, why is assigning dart:io's top-level exitCode usually safer than calling exit(1) to report failure?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

exit() ends the Dart VM immediately without waiting for asynchronous work or running finally blocks, so buffered output and pending writes can be lost. Setting exitCode records the status and lets the program finish normally, flushing and closing first.

open as a page

In Dart's dart:io, what are the steps of a GET request with HttpClient, and why should a command-line script call close() on the client afterwards?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

getUrl() returns an HttpClientRequest; calling close() on it sends the request and returns an HttpClientResponse, a byte stream you decode. HttpClient keeps idle keep-alive connections for idleTimeout (15 s), so client.close() lets a script exit promptly.

open as a page