{"id":13549707,"url":"https://github.com/passsy/deep_pick","last_synced_at":"2025-03-17T14:14:14.296Z","repository":{"id":46290200,"uuid":"197394920","full_name":"passsy/deep_pick","owner":"passsy","description":"Pick values from Dart deep nested data structures (usually json)","archived":false,"fork":false,"pushed_at":"2024-08-30T10:22:52.000Z","size":282,"stargazers_count":117,"open_issues_count":4,"forks_count":14,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-01T23:26:57.917Z","etag":null,"topics":["dart","hacktoberfest","json","parser"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/passsy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["passsy"]}},"created_at":"2019-07-17T13:26:55.000Z","updated_at":"2025-01-08T03:06:52.000Z","dependencies_parsed_at":"2024-12-28T14:12:25.795Z","dependency_job_id":"8169e8d9-556e-438e-a901-769288249e77","html_url":"https://github.com/passsy/deep_pick","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/passsy%2Fdeep_pick","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/passsy%2Fdeep_pick/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/passsy%2Fdeep_pick/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/passsy%2Fdeep_pick/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/passsy","download_url":"https://codeload.github.com/passsy/deep_pick/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244047646,"owners_count":20389206,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dart","hacktoberfest","json","parser"],"created_at":"2024-08-01T12:01:24.648Z","updated_at":"2025-03-17T14:14:14.246Z","avatar_url":"https://github.com/passsy.png","language":"Dart","funding_links":["https://github.com/sponsors/passsy"],"categories":["Dart"],"sub_categories":[],"readme":"# deep_pick\n\n[![Pub](https://img.shields.io/pub/v/deep_pick)](https://pub.dartlang.org/packages/deep_pick)\n[![Pub Likes](https://img.shields.io/pub/likes/deep_pick)](https://pub.dev/packages/deep_pick/score)\n![Build](https://img.shields.io/github/actions/workflow/status/passsy/deep_pick/dart.yml?branch=master)\n![License](https://img.shields.io/github/license/passsy/deep_pick)\n[![style: lint](https://img.shields.io/badge/style-lint-4BC0F5.svg)](https://pub.dev/packages/lint)\n\nSimplifies manual JSON parsing with a type-safe API. \n- No `dynamic`, no manual casting\n- Flexible inputs types, fixed output types\n- Useful parsing error messages\n\n```dart\nimport 'package:deep_pick/deep_pick.dart';\n\npick(json, 'parsing', 'is', 'fun').asBool(); // true\n```\n\n```bash\n$ dart pub add deep_pick\n```\n\n```yaml\ndependencies:\n  deep_pick: ^1.0.0\n```\n\n### Example\n\nThis example demonstrates parsing of an HTTP response using `deep_pick`. You can either use it to parse individual values of a json response or parse whole objects using the `fromPick` constructor.\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\n```dart\n\n  final response = await http.get(Uri.parse('https://api.countapi.xyz/stats'));\n  final json = jsonDecode(response.body);\n\n  // Parse individual fields (nullable)\n  final int? requests = pick(json, 'requests').asIntOrNull();\n  \n  // Require values to be non-null or throw a useful error message\n  final int keys_created = pick(json, 'keys_created').asIntOrThrow();\n  \n  // Pick deep nested values without parsing all objects in between\n  final String? version = pick(json, 'meta', 'version', 'commit').asStringOrNull();\n  \n  \n  // Parse a full object using a fromPick factory constructor\n  final CounterApiStats stats = CounterApiStats.fromPick(pick(json).required());\n\n  \n  // Parse lists with a fromPick constructor \n  final List\u003cCounterApiStats\u003e multipleStats = pick(json, 'items')\n      .asListOrEmpty((pick) =\u003e CounterApiStats.fromPick(pick));\n  \n\n```\n\n\u003c/td\u003e\n\u003ctd\u003e\n\n```dart\n// Http response model\nclass CounterApiStats {\n  const CounterApiStats({\n    required this.requests,\n    required this.keys_created,\n    required this.keys_updated,\n    this.version,\n  });\n\n  final int requests;\n  final int keys_created;\n  final int keys_updated;\n  final String? version;\n\n  factory CounterApiStats.fromPick(RequiredPick pick) {\n    return CounterApiStats(\n      requests: pick('requests').asIntOrThrow(),\n      keys_created: pick('keys_created').asIntOrThrow(),\n      keys_updated: pick('keys_updated').asIntOrThrow(),\n      version: pick('version').asStringOrNull(),\n    );\n  }\n}\n```\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n\n\n\n## Supported types\n\n### `String`\nReturns the picked `Object` as String representation.\nIt doesn't matter if the value is actually a `int`, `double`, `bool` or any other Object.\n`pick` calls the objects `toString` method.\n\n```dart\npick('a').asStringOrThrow(); // \"a\"\npick(1).asStringOrNull(); // \"1\"\npick(1.0).asStringOrNull(); // \"1.0\"\npick(true).asStringOrNull(); // \"true\"\npick(User(name: \"Jason\")).asStringOrNull(); // User{name: Jason}\n```\n\n### `int` \u0026 `double`\n`pick` tries to parse Strings with `int.tryParse` and `double.tryParse`.\nA `int` can be parsed as `double` (no precision loss) but not vice versa because it could lead to mistakes.\n\n```dart\npick(3).asIntOrThrow(); // 3\npick(\"3\").asIntOrNull(); // 3\npick(1).asDoubleOrThrow(); // 1.0\npick(\"2.7\").asDoubleOrNull(); // 2.7\n```\n\n### `bool`\n\nParsing a bool couldn't be easier with those self-explaining methods\n```dart\npick(true).asBoolOrThrow(); // true\npick(false).asBoolOrThrow(); // true\npick(null).asBoolOrTrue(); // true\npick(null).asBoolOrFalse(); // false\npick(null).asBoolOrNull(); // null\npick('true').asBoolOrNull(); // true;\npick('false').asBoolOrNull(); // false;\n```\n\n`deep_pick` does not treat the `int` values `0` and `1` as `bool` as some other languages do.\nWrite your own logic using `.let` instead.\n\n```dart\npick(1).asBoolOrNull(); // null\npick(1).letOrNull((pick) =\u003e pick.value == 1 ? true : pick.value == 0 ? false : null); // true \n```\n\n### `DateTime`\n\nAccepts most common date formats such as \n- [`ISO 8601`](https://www.w3.org/TR/NOTE-datetime) including [`RFC-3339`](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6), \n- [`RFC 1123`](https://www.rfc-editor.org/rfc/rfc1123#page-55) including [HTTP `date`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date) header, [RSS2](https://validator.w3.org/feed/docs/rss2.html) `pubDate`, [`RFC 822`](https://www.rfc-editor.org/rfc/rfc822#section-5), [`RFC 1036`](https://datatracker.ietf.org/doc/html/rfc1036#section-2.1.2) and [`RFC 2822`](https://datatracker.ietf.org/doc/html/rfc2822#page-14), \n- [`RFC 850`](https://www.rfc-editor.org/rfc/rfc850#section-2.1.4) including [`RFC 1036`](https://www.rfc-editor.org/rfc/rfc1036#section-2.1.2), `COOKIE`\n- `ANSI C asctime()`\n\n```dart\npick('2021-11-01T11:53:15Z').asDateTimeOrNull(); // UTC\npick('2021-11-01T11:53:15+0000').asDateTimeOrNull(); // ISO 8601\npick('Monday, 01-Nov-21 11:53:15 UTC').asDateTimeOrThrow(); // RFC 850\npick('Wed, 21 Oct 2015 07:28:00 GMT').asDateTimeOrThrow(); // RFC 1123\npick('Sun Nov  6 08:49:37 1994').asDateTimeOrThrow(); // asctime()\n```\n\n### `List`\n\nWhen the JSON object contains a List of items that List can be mapped to a `List\u003cT\u003e` of objects (`T`).\n\n```dart\npick([]).asListOrNull(SomeObject.fromPick);\npick([]).asListOrThrow(SomeObject.fromPick);\npick([]).asListOrEmpty(SomeObject.fromPick);\n```\n\n```dart\nfinal users = [\n  {'name': 'John Snow'},\n  {'name': 'Daenerys Targaryen'},\n];\nList\u003cPerson\u003e persons = pick(users).asListOrEmpty((pick) {\n  return Person(\n    name: pick('name').required().asString(),\n  );\n});\n\nclass Person {\n  final String name;\n\n  Person({required this.name});\n}\n```\n\n#### Note 1\nExtract the mapper function and use it as a reference allows to write it in a single line again :smile:\n\n```dart\nList\u003cPerson\u003e persons = pick(users).asListOrEmpty(Person.fromPick);\n```\n\nReplacing the static function with a factory constructor doesn't work.\nConstructors cannot be referenced as functions, yet ([dart-lang/language/issues/216](https://github.com/dart-lang/language/issues/216)).\nMeanwhile, use `.asListOrEmpty((it) =\u003e Person.fromPick(it))` when using a factory constructor.\n\n#### Note 2\n`pick` called in the `fromPick` function uses the parameter `pick`, not the top-level function.\nThis is possible because `Pick` implements the `.call()` method.\nThis allows chaining indefinitely on the same `Pick` object while maintaining internal references for useful error messages.\n\nBoth versions produce the same result and shows you're not limited to 10 arguments.\n```dart\npick(json, 'shoes', 1, 'tags', 0).asStringOrThrow();\npick(json)('shoes')(1)('tags')(0).asStringOrThrow();\n```\n\n#### whenNull\n\nTo simplify the `asList` API, the functions ignores `null` values in the `List`.\nThis allows the usage of `RequiredPick` over `Pick` in the `map` function.\n\nWhen `null` is important for your logic you can process the `null` value by providing an optional `whenNull` mapper function.\n\n```dart\npick([1, null, 3]).asListOrNull(\n  (it) =\u003e it.asInt(), \n  whenNull: (Pick pick) =\u003e 25;\n); \n// [1, 25, 3]\n``` \n\n### `Map`\n\nPicking the `Map` is rarely used, because `Pick` itself grants further picking using the `.call(args)` method.\nConverting back to a `Map` is usually only used for existing `fromMap` mapper functions.\n\n```dart\npick(json).asMapOrNull\u003cString, dynamic\u003e();\npick(json).asMapOrThrow\u003cString, dynamic\u003e();\npick(json).asMapOrEmpty\u003cString, dynamic\u003e();\n```\n\n\n## Custom parsers\n\nParsers in `deep_pick` are based on extension functions on the classes `Pick`.\nThis makes it flexible and easy for 3rd-party types to add custom parsers.\n\nThis example parses a `int` as Firestore `Timestamp`.\n```dart\nimport 'package:cloud_firestore/cloud_firestore.dart';\nimport 'package:deep_pick/deep_pick.dart';\n\nextension TimestampPick on Pick {\n  Timestamp asFirestoreTimeStampOrThrow() {\n    final value = required().value;\n    if (value is Timestamp) {\n      return value;\n    }\n    if (value is int) {\n      return Timestamp.fromMillisecondsSinceEpoch(value);\n    }\n    throw PickException(\"value $value at $debugParsingExit can't be casted to Timestamp\");\n  }\n\n  Timestamp? asFirestoreTimeStampOrNull() {\n    if (value == null) return null;\n    try {\n      return asFirestoreTimeStampOrThrow();\n    } catch (_) {\n      return null;\n    }\n  }\n}\n```\n\n### let\n\nWhen using a custom type in only a few places, it might be overkill to create all the extensions.\nFor those cases use the [let function](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/let.html) borrowed from Kotlin to creating neat one-liners.\n\n```dart\nfinal UserId id = pick(json, 'id').letOrNull((it) =\u003e UserId(it.asString()));\nfinal Timestamp timestamp = pick(json, 'time')\n    .letOrThrow((it) =\u003e Timestamp.fromMillisecondsSinceEpoch(it.asInt()));\n```\n\n## Examples\n\n### Reading documents from Firestore\nPicking values from a Firebase `DocumentSnapshot` is usually very selective.\nOnly a fraction of the properties have to be parsed.\nIn this scenario it would be overkill to map the whole document to a Dart object.\nInstead, parse the values in place while staying type-safe.\n\nUse `.asStringOrThrow()` when confident that the value is never `null` and **always** exists.\nThe return type then becomes non-nullable (`String` instead of `String?`).\nWhen the `data` doesn't contain the `full_name` field (against your assumption) it would crash throwing a `PickException`.\n\n```dart\nfinal DocumentSnapshot userDoc = \n    await FirebaseFirestore.instance.collection('users').doc(userId).get();\nfinal data = userDoc.data();\nfinal String fullName = pick(data, 'full_name').asStringOrThrow();\nfinal String? level = pick(data, 'level').asIntOrNull();\n```\n\n`deep_pick` offers an alternative `required()` API with the same result. \nThis is useful to make sure a value exists before parsing it. \nIn case it is `null` or absent a useful error message is printed.\n\n```dart\nfinal String fullName = pick(data, 'full_name').required().asString();\n```\n\n## Background \u0026 Justification\n\nBefore Dart 2.12 and the new `?[]` operator one had to write a lot of code to prevent crashes when a value isn't set! \nReducing this boilerplate was the origin of `deep_pick`.\n```dart\nString milestoneCreator;\nfinal milestone = json['milestore'];\nif (milestone != null) {\n  final creator = json['creator'];\n  if (creator != null) {\n    final login = creator['login'];\n    if (login is String) {\n      milestoneCreator = login;\n    }\n  }\n}\nprint(milestoneCreator); // octocat\n```\nThis example of parses an `issue` object of the [GitHub v3 API](https://developer.github.com/v3/issues/#get-an-issue).\n\nToday with Dart 2.12+ parsing Dart data structures has become way easier with the introduction of the `?[]` operator. \n```dart\nfinal json = jsonDecode(response.data);\nfinal milestoneCreator = json?['milestone']?['creator']?['login'] as String?;\nprint(milestoneCreator); // octocat\n```\n\n`deep_pick` backports this short syntax to previous Dart versions (`\u003c2.12`).\n\n```dart\nfinal milestoneCreator = pick(json, 'milestone', 'creator', 'login').asStringOrNull();\nprint(milestoneCreator); // octocat  \n```\n\n## Still better than vanilla\n\nBut even with the latest Dart version, `deep_pick` offers fantastic features over vanilla parsing using the `?[]` operators:\n\n### 1. Flexible input types \n\nDifferent languages and their JSON libraries generate different JSON. \nSometimes `id`s are `String`, sometimes `int`. Booleans are provided as `true` or with quotes as String `\"true\"`. \nThe meaning is the same but from a type perspective they are not.\n\n`deep_pick` does the basic conversions automatically. \nBy requesting a specific return type, apps won't break when a \"price\" usually returns `double` (`0.99`) but for whole numbers `int` (`1` instead of `1.0`).\n\n```dart\npick(2).asIntOrNull(); // 2\npick('2').asIntOrNull(); // 2 (Sting -\u003e int)\n\npick(42.0).asDoubleOrNull(); // 42.0\npick(42).asDoubleOrNull(); // 42.0 (double -\u003e int)\n\npick(true).asBoolOrFalse(); // true\npick('true').asBoolOrFalse(); // true (String -\u003e bool) \n```\n\n### 2. No RangeError for Lists\nUsing the `?[]` operator can crash for Lists. \nAccessing a list item by `index` outside of the available range causes a `RangeError`.\nYou can't access index 23 when the `List` has only 10 items.\n\n```dart\njson['shoes']?[23]?['id'] as String?;\n\n// Unhandled exception:\n// RangeError (index): Invalid value: Not in inclusive range 0..10: 23\n```\n\n`pick` automatically catches the `RangeError` and returns `null`.\n\n```dart\npick(json, 'shoes', 23, 'id').asStringOrNull(); // null\n```\n\n### 3. Useful error message\n\nVanilla Dart returns a type error because `null` is not a `String`.\nThere is no information available which part is `null` or missing. \n\n```dart\nfinal milestoneCreator = json?['milestone']?['creator']?['login'] as String;\n\n// Unhandled exception:\n// type 'Null' is not a subtype of type 'String' in type cast\n```\n\n`deep_pick` shows the exact location where parsing failed, making it easy to report errors to the API team.\n\n```dart\nfinal milestoneCreator = pick(json, 'milestone', 'creator', 'login').asStringOrThrow();\n\n// Unhandled exception:\n// PickException(\n//   Expected a non-null value but location \"milestone\" in pick(json, \"milestone\" (absent), \"creator\", \"login\") is absent. \n//   Use asStringOrNull() when the value may be null at some point (String?).\n// )\n```\n\nNotice the distinction between \"absent\" and \"null\" when you see such errors.\n- `\"absent\"` means the key isn't found in a Map or a List has no item at the requested index\n- `\"null\"` means the value at that position is actually `null`\n\n### 4. Null is default, crashes intentional\n\nParsing objects from external systems isn't type-safe. \nAPI changes happen, and it is up to the consumer to decide how to handle them.\nConsumer always have to assume the worst, such as missing values.\n\nIt's so easy to accidentally cast a value to `String` in the happy path, instead of `String?` accounting for all possible cases.\nEasy to write, easy to miss in code reviews.\n\nForgetting that `null` could be a valid return type results in a type error:\n\n```dart\njson?['milestone']?['creator']?['login'] as String;\n//                                      ----^\n// Unhandled exception:\n// type 'Null' is not a subtype of type 'String' in type cast\n``` \n\nWith `deep_pick`, all casting methods (`.as*()`) have `null` in mind.\nFor each type you have to choose between at least two ways to deal with `null`.\n\n```dart\npick(json, ...).asStringOrNull();\npick(json, ...).asStringOrThrow();\n\npick(json, ...).asBoolOrNull();\npick(json, ...).asBoolOrFalse();\n\npick(json, ...).asListOrNull(SomeClass.fromPick);\npick(json, ...).asListOrEmpty(SomeClass.fromPick);\n```\n\nHaving \"Throw\" and \"Null\" in the method name, clearly indicates the possible outcome in case the values couldn't be picked.\nThrowing is not a bad habit, some properties are essential for the business logic and throwing an error the correct handling.\nBut throwing should be done intentional, not accidental.\n\n### 5. Map Objects with let\n\nEven with the new `?[]` operator, mapping a value to a new Object (i.e. when wrapping it in a domain Object) can't be done in a single line.\n\n```dart\nfinal value = json?['id'] as String?;\nfinal UserId id = value == null ? null : UserId(value);\n```\n\n`deep_pick` borrows the [let function](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/let.html) from Kotlin creating a neat one-liner\n\n```dart\nfinal UserId id = pick(json, 'id').letOrNull((it) =\u003e UserId(it.asString()));\n```\n\n## License\n\n```\nCopyright 2019 Pascal Welsch\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpasssy%2Fdeep_pick","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpasssy%2Fdeep_pick","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpasssy%2Fdeep_pick/lists"}