diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d2ccc67..f8fcf34 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,7 +10,12 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dart-lang/setup-dart@v1 + - run: sudo apt-get install lcov -y + - run: mkdir -p ./build/coverage - run: dart pub get - run: dart format --output=none --set-exit-if-changed . - run: dart analyze - - run: dart test + - run: dart pub global activate coverage + - run: dart test test/main.dart --coverage=./build/coverage + - run: dart pub global run coverage:format_coverage --lcov --check-ignore --in=./build/coverage --out=./build/lcov.info --packages=./.dart_tool/package_config.json --report-on=lib + - run: "sudo genhtml ./build/lcov.info --output=.build/coverage/html | grep 'lines.*: 100.0%'" diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f7291a..cbf6ef5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,28 @@ -## 1.0.5 +# 2.0.0 +- If a element of map of a list is not JSON serializable it'll be ignore but the subsequence elements will be added to the resulting `Json` - `Json` and `JsonPayload` equal operator override to handle comparison by instance or by value -## 1.0.4 +## Breaking changes +- `ofType`, `listOf` and `mapOf` will wrap the element in a `Json` when calling `builder` instead of the raw value +- When expecting a single type when raw value is heterogeneous list and maps, matching type element will remain and the rest will be discarded instead of returning empty list/map + +# 1.0.4 - `Json` and `JsonPayload` are json encodable, meaning they can be use with `darat:convert jsonEncode` -## 1.0.3 +# 1.0.3 - Fixed `JsonPayload` nested assignment -## 1.0.2 +# 1.0.2 - Fixed `JsonPayload` nested assignment -## 1.0.1 +# 1.0.1 - `.float` returns a value when actual value is an integer -## 1.0.0 +# 1.0.0 - Initial version. diff --git a/Makefile b/Makefile index 922e6bf..624fe55 100644 --- a/Makefile +++ b/Makefile @@ -2,4 +2,4 @@ test_with_coverage: dart pub global activate coverage dart test test/main.dart --coverage=./build/coverage dart pub global run coverage:format_coverage --lcov --check-ignore --in=./build/coverage --out=./build/lcov.info --packages=./.dart_tool/package_config.json --report-on=lib - genhtml ./build/lcov.info --output=./build/coverage/html \ No newline at end of file + genhtml ./build/lcov.info --output=./build/coverage/html diff --git a/amber.png b/amber.png new file mode 100644 index 0000000..2cab170 Binary files /dev/null and b/amber.png differ diff --git a/cmd_line b/cmd_line new file mode 100644 index 0000000..4335b72 --- /dev/null +++ b/cmd_line @@ -0,0 +1 @@ +genhtml ./build/lcov.info diff --git a/emerald.png b/emerald.png new file mode 100644 index 0000000..38ad4f4 Binary files /dev/null and b/emerald.png differ diff --git a/gcov.css b/gcov.css new file mode 100644 index 0000000..6c23ba9 --- /dev/null +++ b/gcov.css @@ -0,0 +1,1101 @@ +/* All views: initial background and text color */ +body +{ + color: #000000; + background-color: #ffffff; +} + +/* All views: standard link format*/ +a:link +{ + color: #284fa8; + text-decoration: underline; +} + +/* All views: standard link - visited format */ +a:visited +{ + color: #00cb40; + text-decoration: underline; +} + +/* All views: standard link - activated format */ +a:active +{ + color: #ff0040; + text-decoration: underline; +} + +/* All views: main title format */ +td.title +{ + text-align: center; + padding-bottom: 10px; + font-family: sans-serif; + font-size: 20pt; + font-style: italic; + font-weight: bold; +} +/* "Line coverage date bins" leader */ +td.subTableHeader +{ + text-align: center; + padding-bottom: 6px; + font-family: sans-serif; + font-weight: bold; + vertical-align: center; +} + +/* All views: header item format */ +td.headerItem +{ + text-align: right; + padding-right: 6px; + font-family: sans-serif; + font-weight: bold; + vertical-align: top; + white-space: nowrap; +} + +/* All views: header item value format */ +td.headerValue +{ + text-align: left; + color: #284fa8; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; +} + +/* All views: header item coverage table heading */ +td.headerCovTableHead +{ + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; +} + +/* All views: header item coverage table entry */ +td.headerCovTableEntry +{ + text-align: right; + color: #284fa8; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #dae7fe; +} + +/* All views: header item coverage table entry for high coverage rate */ +td.headerCovTableEntryHi +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #a7fc9d; +} + +/* All views: header item coverage table entry for medium coverage rate */ +td.headerCovTableEntryMed +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #ffea20; +} + +/* All views: header item coverage table entry for ow coverage rate */ +td.headerCovTableEntryLo +{ + text-align: right; + color: #000000; + font-family: sans-serif; + font-weight: bold; + white-space: nowrap; + padding-left: 12px; + padding-right: 4px; + background-color: #ff0000; +} + +/* All views: header legend value for legend entry */ +td.headerValueLeg +{ + text-align: left; + color: #000000; + font-family: sans-serif; + font-size: 80%; + white-space: nowrap; + padding-top: 4px; +} + +/* All views: color of horizontal ruler */ +td.ruler +{ + background-color: #6688d4; +} + +/* All views: version string format */ +td.versionInfo +{ + text-align: center; + padding-top: 2px; + font-family: sans-serif; + font-style: italic; +} + +/* Directory view/File view (all)/Test case descriptions: + table headline format */ +td.tableHead +{ + text-align: center; + color: #ffffff; + background-color: #6688d4; + font-family: sans-serif; + font-size: 120%; + font-weight: bold; + white-space: nowrap; + padding-left: 4px; + padding-right: 4px; +} + +span.tableHeadSort +{ + padding-right: 4px; +} + +/* Directory view/File view (all): filename entry format */ +td.coverFile +{ + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284fa8; + background-color: #dae7fe; + font-family: monospace; +} + +/* Directory view/File view (all): directory name entry format */ +td.coverDirectory +{ + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284fa8; + background-color: #b8d0ff; + font-family: monospace; +} + +/* Directory view/File view (all): filename entry format */ +td.overallOwner +{ + text-align: center; + font-weight: bold; + font-family: sans-serif; + background-color: #dae7fe; + padding-right: 10px; + padding-left: 10px; +} + +/* Directory view/File view (all): filename entry format */ +td.ownerName +{ + text-align: right; + font-style: italic; + font-family: sans-serif; + background-color: #E5DBDB; + padding-right: 10px; + padding-left: 20px; +} + +/* Directory view/File view (all): bar-graph entry format*/ +td.coverBar +{ + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; +} + +/* Directory view/File view (all): bar-graph entry format*/ +td.owner_coverBar +{ + padding-left: 10px; + padding-right: 10px; + background-color: #E5DBDB; +} + +/* Directory view/File view (all): bar-graph outline color */ +td.coverBarOutline +{ + background-color: #000000; +} + +/* Directory view/File view (all): percentage entry for files with + high coverage rate */ +td.coverPerHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #a7fc9d; + font-weight: bold; + font-family: sans-serif; +} + +/* 'owner' entry: slightly lighter color than 'coverPerHi' */ +td.owner_coverPerHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #82E0AA; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry */ +td.coverNumDflt +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + white-space: nowrap; + font-family: sans-serif; +} + +/* td background color and font for the 'owner' section of the table */ +td.ownerTla +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #E5DBDB; + white-space: nowrap; + font-family: sans-serif; + font-style: italic; +} + +/* Directory view/File view (all): line count entry for files with + high coverage rate */ +td.coverNumHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #a7fc9d; + white-space: nowrap; + font-family: sans-serif; +} + +td.owner_coverNumHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #82E0AA; + white-space: nowrap; + font-family: sans-serif; +} + +/* Directory view/File view (all): percentage entry for files with + medium coverage rate */ +td.coverPerMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ffea20; + font-weight: bold; + font-family: sans-serif; +} + +td.owner_coverPerMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #F9E79F; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry for files with + medium coverage rate */ +td.coverNumMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ffea20; + white-space: nowrap; + font-family: sans-serif; +} + +td.owner_coverNumMed +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #F9E79F; + white-space: nowrap; + font-family: sans-serif; +} + +/* Directory view/File view (all): percentage entry for files with + low coverage rate */ +td.coverPerLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ff0000; + font-weight: bold; + font-family: sans-serif; +} + +td.owner_coverPerLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #EC7063; + font-weight: bold; + font-family: sans-serif; +} + +/* Directory view/File view (all): line count entry for files with + low coverage rate */ +td.coverNumLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ff0000; + white-space: nowrap; + font-family: sans-serif; +} + +td.owner_coverNumLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #EC7063; + white-space: nowrap; + font-family: sans-serif; +} + +/* File view (all): "show/hide details" link format */ +a.detail:link +{ + color: #b8d0ff; + font-size:80%; +} + +/* File view (all): "show/hide details" link - visited format */ +a.detail:visited +{ + color: #b8d0ff; + font-size:80%; +} + +/* File view (all): "show/hide details" link - activated format */ +a.detail:active +{ + color: #ffffff; + font-size:80%; +} + +/* File view (detail): test name entry */ +td.testName +{ + text-align: right; + padding-right: 10px; + background-color: #dae7fe; + font-family: sans-serif; +} + +/* File view (detail): test percentage entry */ +td.testPer +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + font-family: sans-serif; +} + +/* File view (detail): test lines count entry */ +td.testNum +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + font-family: sans-serif; +} + +/* Test case descriptions: test name format*/ +dt +{ + font-family: sans-serif; + font-weight: bold; +} + +/* Test case descriptions: description table body */ +td.testDescription +{ + padding-top: 10px; + padding-left: 30px; + padding-bottom: 10px; + padding-right: 30px; + background-color: #dae7fe; +} + +/* Source code view: function entry */ +td.coverFn +{ + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284fa8; + background-color: #dae7fe; + font-family: monospace; +} + +/* Source code view: function entry zero count*/ +td.coverFnLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #ff0000; + font-weight: bold; + font-family: sans-serif; +} + +/* Source code view: function entry nonzero count*/ +td.coverFnHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + font-weight: bold; + font-family: sans-serif; +} + +td.coverFnAlias +{ + text-align: right; + padding-left: 10px; + padding-right: 20px; + color: #284fa8; + /* make this a slightly different color than the leader - otherwise, + otherwise the alias is hard to distinguish in the table */ + background-color: #E5DBDB; /* very light pale grey/blue */ + font-family: monospace; +} + +/* Source code view: function entry zero count*/ +td.coverFnAliasLo +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #EC7063; /* lighter red */ + font-family: sans-serif; +} + +/* Source code view: function entry nonzero count*/ +td.coverFnAliasHi +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #dae7fe; + font-weight: bold; + font-family: sans-serif; +} + +/* Source code view: source code format */ +pre.source +{ + font-family: monospace; + white-space: pre; + margin-top: 2px; +} + +/* elided/removed code */ +span.elidedSource +{ + font-family: sans-serif; + /*font-size: 8pt; */ + font-style: italic; + background-color: lightgrey; +} + +/* Source code view: line number format */ +span.lineNum +{ + background-color: #efe383; +} + +/* Source code view: line number format when there are deleted + lines in the corresponding location */ +span.lineNumWithDelete +{ + foreground-color: #efe383; + background-color: lightgrey; +} + +/* Source code view: format for Cov legend */ +span.coverLegendCov +{ + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + background-color: #cad7fe; +} + +/* Source code view: format for NoCov legend */ +span.coverLegendNoCov +{ + padding-left: 10px; + padding-right: 10px; + padding-bottom: 2px; + background-color: #ff6230; +} + +/* Source code view: format for the source code heading line */ +pre.sourceHeading +{ + white-space: pre; + font-family: monospace; + font-weight: bold; + margin: 0px; +} + +/* All views: header legend value for low rate */ +td.headerValueLegL +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 4px; + padding-right: 2px; + background-color: #ff0000; + font-size: 80%; +} + +/* All views: header legend value for med rate */ +td.headerValueLegM +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 2px; + padding-right: 2px; + background-color: #ffea20; + font-size: 80%; +} + +/* All views: header legend value for hi rate */ +td.headerValueLegH +{ + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 2px; + padding-right: 4px; + background-color: #a7fc9d; + font-size: 80%; +} + +/* All views except source code view: legend format for low coverage */ +span.coverLegendCovLo +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #ff0000; +} + +/* All views except source code view: legend format for med coverage */ +span.coverLegendCovMed +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #ffea20; +} + +/* All views except source code view: legend format for hi coverage */ +span.coverLegendCovHi +{ + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #a7fc9d; +} + +a.branchTla:link +{ + color: #000000; +} + +a.branchTla:visited +{ + color: #000000; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered New Code (+ => 0): +Newly added code is not tested" */ +td.tlaUNC +{ + text-align: right; + background-color: #FF6230; +} +td.tlaBgUNC { + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered New Code (+ => 0): +Newly added code is not tested" */ +span.tlaUNC +{ + text-align: left; + background-color: #FF6230; +} +span.tlaBgUNC { + background-color: #FF6230; +} +a.tlaBgUNC { + background-color: #FF6230; + color: #000000; +} + +td.headerCovTableHeadUNC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Lost Baseline Coverage (1 => 0): +Unchanged code is no longer tested" */ +td.tlaLBC +{ + text-align: right; + background-color: #FF6230; +} +td.tlaBgLBC { + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Lost Baseline Coverage (1 => 0): +Unchanged code is no longer tested" */ +span.tlaLBC +{ + text-align: left; + background-color: #FF6230; +} +span.tlaBgLBC { + background-color: #FF6230; +} +a.tlaBgLBC { + background-color: #FF6230; + color: #000000; +} + +td.headerCovTableHeadLBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Included Code (# => 0): +Previously unused code is untested" */ +td.tlaUIC +{ + text-align: right; + background-color: #FF6230; +} +td.tlaBgUIC { + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Included Code (# => 0): +Previously unused code is untested" */ +span.tlaUIC +{ + text-align: left; + background-color: #FF6230; +} +span.tlaBgUIC { + background-color: #FF6230; +} +a.tlaBgUIC { + background-color: #FF6230; + color: #000000; +} + +td.headerCovTableHeadUIC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Baseline Code (0 => 0): +Unchanged code was untested before, is untested now" */ +td.tlaUBC +{ + text-align: right; + background-color: #FF6230; +} +td.tlaBgUBC { + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Baseline Code (0 => 0): +Unchanged code was untested before, is untested now" */ +span.tlaUBC +{ + text-align: left; + background-color: #FF6230; +} +span.tlaBgUBC { + background-color: #FF6230; +} +a.tlaBgUBC { + background-color: #FF6230; + color: #000000; +} + +td.headerCovTableHeadUBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FF6230; +} + +/* Source code view/table entry background: format for lines classified as "Gained Baseline Coverage (0 => 1): +Unchanged code is tested now" */ +td.tlaGBC +{ + text-align: right; + background-color: #CAD7FE; +} +td.tlaBgGBC { + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained Baseline Coverage (0 => 1): +Unchanged code is tested now" */ +span.tlaGBC +{ + text-align: left; + background-color: #CAD7FE; +} +span.tlaBgGBC { + background-color: #CAD7FE; +} +a.tlaBgGBC { + background-color: #CAD7FE; + color: #000000; +} + +td.headerCovTableHeadGBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained Included Coverage (# => 1): +Previously unused code is tested now" */ +td.tlaGIC +{ + text-align: right; + background-color: #CAD7FE; +} +td.tlaBgGIC { + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained Included Coverage (# => 1): +Previously unused code is tested now" */ +span.tlaGIC +{ + text-align: left; + background-color: #CAD7FE; +} +span.tlaBgGIC { + background-color: #CAD7FE; +} +a.tlaBgGIC { + background-color: #CAD7FE; + color: #000000; +} + +td.headerCovTableHeadGIC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained New Coverage (+ => 1): +Newly added code is tested" */ +td.tlaGNC +{ + text-align: right; + background-color: #CAD7FE; +} +td.tlaBgGNC { + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Gained New Coverage (+ => 1): +Newly added code is tested" */ +span.tlaGNC +{ + text-align: left; + background-color: #CAD7FE; +} +span.tlaBgGNC { + background-color: #CAD7FE; +} +a.tlaBgGNC { + background-color: #CAD7FE; + color: #000000; +} + +td.headerCovTableHeadGNC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Covered Baseline Code (1 => 1): +Unchanged code was tested before and is still tested" */ +td.tlaCBC +{ + text-align: right; + background-color: #CAD7FE; +} +td.tlaBgCBC { + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Covered Baseline Code (1 => 1): +Unchanged code was tested before and is still tested" */ +span.tlaCBC +{ + text-align: left; + background-color: #CAD7FE; +} +span.tlaBgCBC { + background-color: #CAD7FE; +} +a.tlaBgCBC { + background-color: #CAD7FE; + color: #000000; +} + +td.headerCovTableHeadCBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #CAD7FE; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Uncovered Baseline (0 => #): +Previously untested code is unused now" */ +td.tlaEUB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgEUB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Uncovered Baseline (0 => #): +Previously untested code is unused now" */ +span.tlaEUB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgEUB { + background-color: #FFFFFF; +} +a.tlaBgEUB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadEUB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Covered Baseline (1 => #): +Previously tested code is unused now" */ +td.tlaECB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgECB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Covered Baseline (1 => #): +Previously tested code is unused now" */ +span.tlaECB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgECB { + background-color: #FFFFFF; +} +a.tlaBgECB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadECB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Uncovered Baseline (0 => -): +Previously untested code has been deleted" */ +td.tlaDUB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgDUB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Uncovered Baseline (0 => -): +Previously untested code has been deleted" */ +span.tlaDUB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgDUB { + background-color: #FFFFFF; +} +a.tlaBgDUB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadDUB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Covered Baseline (1 => -): +Previously tested code has been deleted" */ +td.tlaDCB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgDCB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Covered Baseline (1 => -): +Previously tested code has been deleted" */ +span.tlaDCB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgDCB { + background-color: #FFFFFF; +} +a.tlaBgDCB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadDCB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view: format for date/owner bin that is not hit */ +span.missBins +{ + background-color: #ff0000 /* red */ +} diff --git a/glass.png b/glass.png new file mode 100644 index 0000000..e1abc00 Binary files /dev/null and b/glass.png differ diff --git a/index-sort-f.html b/index-sort-f.html new file mode 100644 index 0000000..c17f4be --- /dev/null +++ b/index-sort-f.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelCoverageTotalHit
Test:lcov.infoLines:99.6 %253252
Test Date:2025-02-04 14:37:05Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
src/ +
99.6%99.6%
+
99.6 %253252-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/index-sort-l.html b/index-sort-l.html new file mode 100644 index 0000000..8633fa1 --- /dev/null +++ b/index-sort-l.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelCoverageTotalHit
Test:lcov.infoLines:99.6 %253252
Test Date:2025-02-04 14:37:05Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
src/ +
99.6%99.6%
+
99.6 %253252-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..ed592b3 --- /dev/null +++ b/index.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top levelCoverageTotalHit
Test:lcov.infoLines:99.6 %253252
Test Date:2025-02-04 14:37:05Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Directory Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
src/ +
99.6%99.6%
+
99.6 %253252-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/lib/src/json.dart b/lib/src/json.dart index 5c16c87..a9d98ce 100644 --- a/lib/src/json.dart +++ b/lib/src/json.dart @@ -15,7 +15,7 @@ class Json { JsonException? exception; /// Empty [Json] - Json() : _rawValue = {}; + Json(); /// Decodes the string with [JsonDecoder] and wraps it with [Json] Json.fromString(String json) : _rawValue = Json.decoder.convert(json); @@ -42,11 +42,12 @@ class Json { } else if (asIterable != null) { _rawValue = []; int i = 0; - for (var item in asIterable) { + for (final item in asIterable) { try { _set(i++, item); } on JsonException catch (error) { exception = exception ?? error; + i--; } } } else if (_rawValue is! int && @@ -60,12 +61,7 @@ class Json { } /// Create [Json] from a [Map] - Json.fromMap(Map map, {bool initial = true}) { - if (!initial) { - _rawValue = map; - return; - } - + Json.fromMap(Map map) { _rawValue = {}; map.forEach((String key, Any value) { @@ -78,7 +74,7 @@ class Json { } /// Create [Json] from a [List] - Json.fromList(List list, {bool initial = true}) { + Json.fromList(List list) { _rawValue = []; int i = 0; @@ -87,6 +83,7 @@ class Json { _set(i++, item); } on JsonException catch (error) { exception = exception ?? error; + i--; } } } @@ -98,6 +95,14 @@ class Json { return; } + assert(other._rawValue is int || + other._rawValue is double || + other._rawValue is bool || + other._rawValue is String || + other._rawValue == null || + other._rawValue is Map || + other._rawValue is List); + if (other._rawValue is int || other._rawValue is double || other._rawValue is bool || @@ -108,9 +113,8 @@ class Json { _rawValue = Map.from(other._rawValue as Map); } else if (other._rawValue is List) { _rawValue = List.from(other._rawValue as List); - } else { - assert(false); } + exception = other.exception; } @@ -121,10 +125,6 @@ class Json { @override String toString() => jsonEncode(_rawValue); - Any toJson() { - return tryCast(_rawValue)?.toJson() ?? _rawValue; - } - /// Compare [Json] by instance or by value @override bool operator ==(Object other) => @@ -210,24 +210,21 @@ class Json { } return Json.fromDynamic(rawList.elementAtOrNull(index), initial: false); - } else if (rawMap != null) { - final index = key.toString(); - final result = Json.fromDynamic(rawMap[index], initial: false); - if (!rawMap.containsKey(index)) { - result.exception = exception ?? - JsonException( - JsonError.notExist, - userReason: 'JSON Error: key `$index` does not exists', - ); - } - - return result; } - // Unreachable - assert(false, 'Should be unreachable'); + assert(rawMap != null); - return jsonNull; + final index = key.toString(); + final result = Json.fromDynamic(rawMap![index], initial: false); + if (!rawMap.containsKey(index)) { + result.exception = exception ?? + JsonException( + JsonError.notExist, + userReason: 'JSON Error: key `$index` does not exists', + ); + } + + return result; } /// Returns [true] if [exception] is [null] @@ -241,14 +238,12 @@ class Json { String? get string => (_rawValue is String) ? _rawValue as String : null; /// Returns a [String] or an empty [String] if [rawValue] is not a [String] - String get stringValue => switch (_rawValue) { - const (String) || - const (bool) || - const (int) || - const (double) => - _rawValue?.toString() ?? '', - _ => '', - }; + String get stringValue => _rawValue is String || + _rawValue is bool || + _rawValue is int || + _rawValue is double + ? _rawValue?.toString() ?? '' + : ''; /// Returns a [bool] or [null] if [rawValue] is not a [bool] bool? get boolean => (_rawValue is bool) ? _rawValue as bool : null; @@ -313,11 +308,11 @@ class Json { /// Returns a [T] or [null] if [rawValue] is not a [T] /// If actual data is not [T], calls [builder] to get one - T? ofType([T? Function(Any)? builder]) { + T? ofType([T? Function(Json)? builder]) { if (_rawValue is T) { return _rawValue as T; } else if (builder != null) { - return builder(_rawValue); + return builder(this); } return null; @@ -325,11 +320,11 @@ class Json { /// Returns a [T] or [defaultValue] if [rawValue] is not a [T] /// If actual data is not [T], calls [builder] to get one - T ofTypeValue(T defaultValue, [T? Function(Any)? builder]) { + T ofTypeValue(T defaultValue, [T? Function(Json)? builder]) { if (_rawValue is T) { return _rawValue as T; } else if (builder != null) { - T? built = builder(_rawValue); + T? built = builder(this); if (built != null) { return built; @@ -361,32 +356,20 @@ class Json { /// Returns a [List] of [T] or empty list if [rawValue] is not a [List] of [T] /// If actual data is not a [List] of [T], calls [builder] to get one - List? listOf([T? Function(Any)? builder]) { + List? listOf([T? Function(Json)? builder]) { if (_rawValue is List) { - try { - return (_rawValue as List).map((Any e) { - if (e is T) { - return e; - } else if (builder != null) { - T? built = builder(e); - - if (built != null) { - return built; + return (_rawValue as List) + .map((Any e) { + if (e is T) { + return e; + } else if (builder != null) { + return builder(Json.fromDynamic(e)); } - } - - exception = exception ?? - JsonException( - JsonError.wrongType, - userReason: - 'JSON Error: at least one element is not of type `$T`', - ); - - throw exception!; - }).toList(); - } on JsonException catch (_) { - return null; - } + + return null; + }) + .whereType() + .toList(); } return null; @@ -394,7 +377,7 @@ class Json { /// Returns a [List] of [T] or an empty [List] if [rawValue] is not a [List] of [T] /// If actual data is not a [List] of [T], calls [builder] to get one - List listOfValue([T? Function(Any)? builder]) { + List listOfValue([T? Function(Json)? builder]) { return listOf(builder) ?? []; } @@ -426,40 +409,29 @@ class Json { /// Returns a [Map] of [T] or [null] if [rawValue] is not a [Map] of [T] /// If actual data is not a [Map] of [T], calls [builder] to get one - Map? mapOf([T? Function(Any)? builder]) { + Map? mapOf([T? Function(Json)? builder]) { if (_rawValue is Map) { - try { - return (_rawValue as Map).map((Any key, Any value) { - if (key is! String) { - exception = exception ?? - JsonException( - JsonError.wrongType, - userReason: 'JSON Error: key must be a String', - ); - - throw exception!; - } else if (value is T) { - return MapEntry(key, value); - } else if (builder != null) { - T? built = builder(value); - - if (built != null) { - return MapEntry(key, built); - } - } - - exception = exception ?? - JsonException( - JsonError.wrongType, - userReason: - 'JSON Error: at least one element is not of type `$T`', - ); - - throw exception!; - }); - } on JsonException catch (_) { - return null; - } + return {}..addEntries( + (_rawValue as Map) + .entries + .map?>((entry) { + if (entry.key is! String) { + return null; + } else if (entry.value is T) { + return MapEntry( + entry.key as String, entry.value as T); + } else if (builder != null) { + final element = builder(Json.fromDynamic(entry.value)); + if (element != null) { + return MapEntry(entry.key as String, element); + } + } + + return null; + }) + .whereType>() + .toList(), + ); } return null; @@ -467,8 +439,8 @@ class Json { /// Returns a [List] of [T] or empty [Map] if [rawValue] is not a [List] of [T] /// If actual data is not a [List] of [T], calls [builder] to get one - Map mapOfValue() { - return mapOf() ?? {}; + Map mapOfValue([T? Function(Json)? builder]) { + return mapOf(builder) ?? {}; } } @@ -478,8 +450,8 @@ class JsonPayload extends Json { JsonPayload.fromString(super.json) : super.fromString(); JsonPayload.fromDynamic(super.rawValue, {super.initial}) : super.fromDynamic(); - JsonPayload.fromMap(super.map, {super.initial}) : super.fromMap(); - JsonPayload.fromList(super.list, {super.initial}) : super.fromList(); + JsonPayload.fromMap(super.map) : super.fromMap(); + JsonPayload.fromList(super.list) : super.fromList(); JsonPayload.from(super.other, {super.initial}) : super.from(); set rawValue(Any newValue) { @@ -487,6 +459,11 @@ class JsonPayload extends Json { } void operator []=(Any key, Any value) { + if (_rawValue is! List && _rawValue is! Map) { + throw JsonException(JsonError.wrongType, + userReason: 'Underlying value is neither a map or a list'); + } + _set(key, value); } @@ -505,11 +482,7 @@ class JsonPayload extends Json { : null; @override - List get listValue => (_rawValue is List) - ? (_rawValue as List) - .map((Any e) => JsonPayload.fromDynamic(e)) - .toList() - : []; + List get listValue => list ?? []; @override Map? get map => (_rawValue is Map) @@ -518,10 +491,7 @@ class JsonPayload extends Json { : null; @override - Map get mapValue => (_rawValue is Map) - ? (_rawValue as Map).map((Any key, Any value) => - MapEntry('$key', JsonPayload.fromDynamic(value))) - : {}; + Map get mapValue => map ?? {}; /// Remove element under [key] void removeElementWithKey(Any key) { @@ -546,6 +516,13 @@ class JsonPayload extends Json { index = key as int; } + if (index < 0 || index > rawList.length) { + throw JsonException( + JsonError.indexOutOfBounds, + userReason: 'JSON Error: index `$index` is out of bounds', + ); + } + rawList.removeAt(index); } else if (_rawValue is Map) { final rawMap = _rawValue as Map; diff --git a/ruby.png b/ruby.png new file mode 100644 index 0000000..991b6d4 Binary files /dev/null and b/ruby.png differ diff --git a/snow.png b/snow.png new file mode 100644 index 0000000..2cdae10 Binary files /dev/null and b/snow.png differ diff --git a/src/index-sort-f.html b/src/index-sort-f.html new file mode 100644 index 0000000..7bd3d49 --- /dev/null +++ b/src/index-sort-f.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - src + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - srcCoverageTotalHit
Test:lcov.infoLines:99.6 %253252
Test Date:2025-02-04 14:37:05Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
json.dart +
99.6%99.6%
+
99.6 %253252-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/src/index-sort-l.html b/src/index-sort-l.html new file mode 100644 index 0000000..5f96252 --- /dev/null +++ b/src/index-sort-l.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - src + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - srcCoverageTotalHit
Test:lcov.infoLines:99.6 %253252
Test Date:2025-02-04 14:37:05Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
json.dart +
99.6%99.6%
+
99.6 %253252-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..0f73ff0 --- /dev/null +++ b/src/index.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov.info - src + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - srcCoverageTotalHit
Test:lcov.infoLines:99.6 %253252
Test Date:2025-02-04 14:37:05Functions:-00
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

File Sort by file nameLine Coverage Sort by line coverageFunction Coverage Sort by function coverage
Rate Total Hit Rate Total Hit
json.dart +
99.6%99.6%
+
99.6 %253252-
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/src/json.dart.func-c.html b/src/json.dart.func-c.html new file mode 100644 index 0000000..5291c58 --- /dev/null +++ b/src/json.dart.func-c.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - src/json.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src - json.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:99.6 %253252
Test Date:2025-02-04 14:37:05Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/src/json.dart.func.html b/src/json.dart.func.html new file mode 100644 index 0000000..7165a65 --- /dev/null +++ b/src/json.dart.func.html @@ -0,0 +1,75 @@ + + + + + + + LCOV - lcov.info - src/json.dart - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src - json.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:99.6 %253252
Test Date:2025-02-04 14:37:05Functions:-00
+
+ +
+ + + + + + + + + +

Function Name Sort by function nameHit count Sort by function hit count
+
+
+ + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/src/json.dart.gcov.html b/src/json.dart.gcov.html new file mode 100644 index 0000000..fb30acc --- /dev/null +++ b/src/json.dart.gcov.html @@ -0,0 +1,660 @@ + + + + + + + LCOV - lcov.info - src/json.dart + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src - json.dart (source / functions)CoverageTotalHit
Test:lcov.infoLines:99.6 %253252
Test Date:2025-02-04 14:37:05Functions:-00
+
+ + + + + + + + +

+
            Line data    Source code
+
+       1              : import 'dart:convert';
+       2              : 
+       3              : typedef Any = Object?;
+       4              : 
+       5              : /// Wraps Json decoded data
+       6              : class Json {
+       7              :   static const decoder = JsonDecoder();
+       8              : 
+       9              :   Any _rawValue;
+      10              : 
+      11              :   /// Actual value
+      12            2 :   Any get rawValue => _rawValue;
+      13              : 
+      14              :   /// Exceptions are never thrown, instead they are silently stored in the [Json] instance
+      15              :   JsonException? exception;
+      16              : 
+      17              :   /// Empty [Json]
+      18            1 :   Json();
+      19              : 
+      20              :   /// Decodes the string with [JsonDecoder] and wraps it with [Json]
+      21            2 :   Json.fromString(String json) : _rawValue = Json.decoder.convert(json);
+      22              : 
+      23              :   /// Create a [Json] instance from any value
+      24            1 :   Json.fromDynamic(this._rawValue, {bool initial = true}) {
+      25              :     if (!initial) {
+      26              :       // Type checking has already been done
+      27              :       return;
+      28              :     }
+      29              : 
+      30            2 :     final asMap = tryCast<Map<String, Any>>(_rawValue);
+      31            2 :     final asIterable = tryCast<Iterable<Any>>(_rawValue);
+      32              : 
+      33              :     if (asMap != null) {
+      34            2 :       _rawValue = <String, Any>{};
+      35            2 :       asMap.forEach((String key, Any value) {
+      36              :         try {
+      37            1 :           _set(key, value);
+      38            1 :         } on JsonException catch (error) {
+      39            2 :           exception = exception ?? error;
+      40              :         }
+      41              :       });
+      42              :     } else if (asIterable != null) {
+      43            2 :       _rawValue = <Any>[];
+      44              :       int i = 0;
+      45            2 :       for (final item in asIterable) {
+      46              :         try {
+      47            2 :           _set(i++, item);
+      48            1 :         } on JsonException catch (error) {
+      49            2 :           exception = exception ?? error;
+      50            1 :           i--;
+      51              :         }
+      52              :       }
+      53            2 :     } else if (_rawValue is! int &&
+      54            2 :         _rawValue is! double &&
+      55            2 :         _rawValue is! String &&
+      56            1 :         _rawValue != null &&
+      57            2 :         _rawValue is! bool) {
+      58            3 :       exception = exception ?? JsonException(JsonError.unsupportedType);
+      59            1 :       _rawValue = null;
+      60              :     }
+      61              :   }
+      62              : 
+      63              :   /// Create [Json] from a [Map]
+      64            1 :   Json.fromMap(Map<String, Any> map) {
+      65            2 :     _rawValue = <String, Any>{};
+      66              : 
+      67            2 :     map.forEach((String key, Any value) {
+      68              :       try {
+      69            1 :         _set(key, value);
+      70            1 :       } on JsonException catch (error) {
+      71            2 :         exception = exception ?? error;
+      72              :       }
+      73              :     });
+      74              :   }
+      75              : 
+      76              :   /// Create [Json] from a [List]
+      77            1 :   Json.fromList(List<Any> list) {
+      78            2 :     _rawValue = <Any>[];
+      79              : 
+      80              :     int i = 0;
+      81            2 :     for (var item in list) {
+      82              :       try {
+      83            2 :         _set(i++, item);
+      84            1 :       } on JsonException catch (error) {
+      85            2 :         exception = exception ?? error;
+      86            1 :         i--;
+      87              :       }
+      88              :     }
+      89              :   }
+      90              : 
+      91              :   /// Create [Json] from another
+      92            1 :   Json.from(Json other, {bool initial = true}) {
+      93              :     if (!initial) {
+      94            2 :       _rawValue = other._rawValue;
+      95              :       return;
+      96              :     }
+      97              : 
+      98            3 :     assert(other._rawValue is int ||
+      99            2 :         other._rawValue is double ||
+     100            2 :         other._rawValue is bool ||
+     101            2 :         other._rawValue is String ||
+     102            1 :         other._rawValue == null ||
+     103            2 :         other._rawValue is Map ||
+     104            2 :         other._rawValue is List);
+     105              : 
+     106            2 :     if (other._rawValue is int ||
+     107            2 :         other._rawValue is double ||
+     108            2 :         other._rawValue is bool ||
+     109            2 :         other._rawValue is String ||
+     110            1 :         other._rawValue == null) {
+     111            2 :       _rawValue = other._rawValue;
+     112            2 :     } else if (other._rawValue is Map) {
+     113            3 :       _rawValue = Map<String, Any>.from(other._rawValue as Map);
+     114            2 :     } else if (other._rawValue is List) {
+     115            3 :       _rawValue = List<Any>.from(other._rawValue as List);
+     116              :     }
+     117              : 
+     118            2 :     exception = other.exception;
+     119              :   }
+     120              : 
+     121              :   /// Create a [Json] with a [null] [rawValue]
+     122            2 :   Json get jsonNull => Json.fromDynamic(null);
+     123              : 
+     124              :   /// Returns actual Json
+     125            1 :   @override
+     126            2 :   String toString() => jsonEncode(_rawValue);
+     127              : 
+     128              :   /// Compare [Json] by instance or by value
+     129            1 :   @override
+     130              :   bool operator ==(Object other) =>
+     131            4 :       identical(this, other) || other is Json && toString() == other.toString();
+     132              : 
+     133            1 :   @override
+     134            2 :   int get hashCode => toString().hashCode;
+     135              : 
+     136            1 :   void _set(Any key, Any value) {
+     137              :     // If not a json encodable type fail
+     138            1 :     if (value is! String &&
+     139            1 :         value is! int &&
+     140            1 :         value is! double &&
+     141            1 :         value is! bool &&
+     142            1 :         value is! List &&
+     143            1 :         value is! Map &&
+     144              :         value != null) {
+     145            1 :       throw JsonException(JsonError.unsupportedType);
+     146              :     }
+     147              : 
+     148              :     if (key == null) return;
+     149              : 
+     150            2 :     final rawMap = tryCast<Map<Any, Any>>(_rawValue);
+     151            2 :     final rawList = tryCast<List<Any>>(_rawValue);
+     152              :     if (rawList != null) {
+     153            3 :       int? index = (key is String) ? int.tryParse(key) : tryCast<int>(key);
+     154              :       if (index == null) {
+     155            1 :         throw JsonException(
+     156              :           JsonError.wrongType,
+     157            2 :           userReason: 'JSON Error: index must be int, ${key.runtimeType} given',
+     158              :         );
+     159              :       }
+     160              : 
+     161            3 :       if (index < 0 || index > rawList.length) {
+     162            1 :         throw JsonException(
+     163              :           JsonError.indexOutOfBounds,
+     164            1 :           userReason: 'JSON Error: index `$index` is out of bounds',
+     165              :         );
+     166            2 :       } else if (index == rawList.length) {
+     167            1 :         rawList.add(value);
+     168              :       } else {
+     169            1 :         rawList[index] = value;
+     170              :       }
+     171              :     } else if (rawMap != null) {
+     172            2 :       rawMap[key.toString()] = value;
+     173              :     }
+     174              :   }
+     175              : 
+     176              :   /// Returns a [Json] wrapping the data under [key]. If [key] does not exist, returns a empty [Json] instance with an [exception]
+     177            1 :   Json operator [](Object key) {
+     178            2 :     if ((key is! String && key is! int) ||
+     179            4 :         (_rawValue is! List && _rawValue is! Map)) {
+     180            1 :       var result = jsonNull;
+     181            3 :       result.exception = exception ?? JsonException(JsonError.wrongType);
+     182              :       return result;
+     183              :     }
+     184              : 
+     185            2 :     final rawList = tryCast<List<Any>>(_rawValue);
+     186            2 :     final rawMap = tryCast<Map<Any, Any>>(_rawValue);
+     187              : 
+     188              :     if (rawList != null) {
+     189            3 :       int? index = key is String ? int.tryParse(key) : tryCast<int>(key);
+     190              : 
+     191              :       if (index == null) {
+     192            1 :         var result = jsonNull;
+     193            2 :         result.exception = exception ??
+     194            1 :             JsonException(
+     195              :               JsonError.wrongType,
+     196              :               userReason:
+     197            2 :                   'JSON Error: index must be int, ${key.runtimeType} given',
+     198              :             );
+     199              :         return result;
+     200              :       }
+     201              : 
+     202            3 :       if (index < 0 || index >= rawList.length) {
+     203            1 :         var result = jsonNull;
+     204            2 :         result.exception = exception ??
+     205            1 :             JsonException(
+     206              :               JsonError.indexOutOfBounds,
+     207            1 :               userReason: 'JSON Error: index `$index` is out of bounds',
+     208              :             );
+     209              :         return result;
+     210              :       }
+     211              : 
+     212            2 :       return Json.fromDynamic(rawList.elementAtOrNull(index), initial: false);
+     213              :     }
+     214              : 
+     215            1 :     assert(rawMap != null);
+     216              : 
+     217            1 :     final index = key.toString();
+     218            2 :     final result = Json.fromDynamic(rawMap![index], initial: false);
+     219            1 :     if (!rawMap.containsKey(index)) {
+     220            2 :       result.exception = exception ??
+     221            1 :           JsonException(
+     222              :             JsonError.notExist,
+     223            1 :             userReason: 'JSON Error: key `$index` does not exists',
+     224              :           );
+     225              :     }
+     226              : 
+     227              :     return result;
+     228              :   }
+     229              : 
+     230              :   /// Returns [true] if [exception] is [null]
+     231            1 :   bool exists([Any key]) {
+     232              :     return key != null
+     233            4 :         ? this[key].exception == null && this[key].rawValue != null
+     234            2 :         : exception == null && rawValue != null;
+     235              :   }
+     236              : 
+     237              :   /// Returns a [String] or [null] if [rawValue] is not a [String]
+     238            4 :   String? get string => (_rawValue is String) ? _rawValue as String : null;
+     239              : 
+     240              :   /// Returns a [String] or an empty [String] if [rawValue] is not a [String]
+     241            3 :   String get stringValue => _rawValue is String ||
+     242            2 :           _rawValue is bool ||
+     243            2 :           _rawValue is int ||
+     244            2 :           _rawValue is double
+     245            2 :       ? _rawValue?.toString() ?? ''
+     246              :       : '';
+     247              : 
+     248              :   /// Returns a [bool] or [null] if [rawValue] is not a [bool]
+     249            4 :   bool? get boolean => (_rawValue is bool) ? _rawValue as bool : null;
+     250              : 
+     251              :   /// Returns a [bool] or [false] if [rawValue] is not thruthy
+     252            1 :   bool get booleanValue {
+     253            2 :     if (_rawValue is bool) {
+     254            1 :       return _rawValue as bool;
+     255            4 :     } else if (_rawValue is int || _rawValue is double) {
+     256            2 :       return _rawValue == 1;
+     257            2 :     } else if (_rawValue is String) {
+     258            1 :       return ['true', 'y', 't', 'yes', '1']
+     259            3 :           .contains((_rawValue as String).toLowerCase());
+     260              :     }
+     261              : 
+     262              :     return false;
+     263              :   }
+     264              : 
+     265              :   /// Returns a [int] or [null] if [rawValue] is not a [int]
+     266            4 :   int? get integer => (_rawValue is int) ? _rawValue as int : null;
+     267              : 
+     268              :   /// Returns a [int] or 0 if [rawValue] is not a [int]
+     269            1 :   int get integerValue {
+     270            2 :     if (_rawValue is int) {
+     271            1 :       return _rawValue as int;
+     272            2 :     } else if (_rawValue is bool) {
+     273            1 :       return _rawValue as bool ? 1 : 0;
+     274            2 :     } else if (_rawValue is double) {
+     275            2 :       return (_rawValue as double).toInt();
+     276            2 :     } else if (_rawValue is String) {
+     277              :       try {
+     278            2 :         return int.parse(_rawValue as String);
+     279              :       } catch (error) {
+     280              :         return 0;
+     281              :       }
+     282              :     }
+     283              : 
+     284              :     return 0;
+     285              :   }
+     286              : 
+     287            1 :   double? get float =>
+     288            4 :       (_rawValue is num) ? (_rawValue as num).toDouble() : null;
+     289              : 
+     290              :   /// Returns a [double] 0 if [rawValue] is not a [double]
+     291            1 :   double get floatValue {
+     292            2 :     if (_rawValue is double) {
+     293            1 :       return _rawValue as double;
+     294            2 :     } else if (_rawValue is int) {
+     295            2 :       return (_rawValue as int).toDouble();
+     296            2 :     } else if (_rawValue is bool) {
+     297            1 :       return _rawValue as bool ? 1 : 0;
+     298            2 :     } else if (_rawValue is String) {
+     299              :       try {
+     300            2 :         return double.parse(_rawValue as String);
+     301              :       } catch (error) {
+     302              :         return 0;
+     303              :       }
+     304              :     }
+     305              : 
+     306              :     return 0;
+     307              :   }
+     308              : 
+     309              :   /// Returns a [T] or [null] if [rawValue] is not a [T]
+     310              :   /// If actual data is not [T], calls [builder] to get one
+     311            1 :   T? ofType<T>([T? Function(Json)? builder]) {
+     312            2 :     if (_rawValue is T) {
+     313            1 :       return _rawValue as T;
+     314              :     } else if (builder != null) {
+     315            1 :       return builder(this);
+     316              :     }
+     317              : 
+     318              :     return null;
+     319              :   }
+     320              : 
+     321              :   /// Returns a [T] or [defaultValue] if [rawValue] is not a [T]
+     322              :   /// If actual data is not [T], calls [builder] to get one
+     323            1 :   T ofTypeValue<T>(T defaultValue, [T? Function(Json)? builder]) {
+     324            2 :     if (_rawValue is T) {
+     325            1 :       return _rawValue as T;
+     326              :     } else if (builder != null) {
+     327            1 :       T? built = builder(this);
+     328              : 
+     329              :       if (built != null) {
+     330              :         return built;
+     331              :       }
+     332              :     }
+     333              : 
+     334              :     return defaultValue;
+     335              :   }
+     336              : 
+     337              :   /// Returns a [List] or [null] if [rawValue] is not a [List]
+     338              :   /// Each element of the list is wrapped in a [Json] instance
+     339            3 :   List<Json>? get list => (_rawValue is List)
+     340            5 :       ? (_rawValue as List).map<Json>((Any e) => Json.fromDynamic(e)).toList()
+     341              :       : null;
+     342              : 
+     343              :   /// Returns a [List] or an empty [List] if [rawValue] is not a [List]
+     344              :   /// Each element of the list is wrapped in a [Json] instance
+     345            3 :   List<Json> get listValue => (_rawValue is List)
+     346            5 :       ? (_rawValue as List).map<Json>((Any e) => Json.fromDynamic(e)).toList()
+     347            0 :       : [];
+     348              : 
+     349              :   /// Returns a [List] or [null] if [rawValue] is not a [List]
+     350              :   /// Leaves list items untouched
+     351            4 :   List<Any>? get listObject => (_rawValue is List) ? _rawValue as List : null;
+     352              : 
+     353              :   /// Returns a [List] or an empty [List] if [rawValue] is not a [List]
+     354              :   /// Leaves list items untouched
+     355            5 :   List<Any> get listObjectValue => (_rawValue is List) ? _rawValue as List : [];
+     356              : 
+     357              :   /// Returns a [List] of [T] or empty list if [rawValue] is not a [List] of [T]
+     358              :   /// If actual data is not a [List] of [T], calls [builder] to get one
+     359            1 :   List<T>? listOf<T>([T? Function(Json)? builder]) {
+     360            2 :     if (_rawValue is List) {
+     361            1 :       return (_rawValue as List)
+     362            2 :           .map<T?>((Any e) {
+     363            1 :             if (e is T) {
+     364              :               return e;
+     365              :             } else if (builder != null) {
+     366            2 :               return builder(Json.fromDynamic(e));
+     367              :             }
+     368              : 
+     369              :             return null;
+     370              :           })
+     371            1 :           .whereType<T>()
+     372            1 :           .toList();
+     373              :     }
+     374              : 
+     375              :     return null;
+     376              :   }
+     377              : 
+     378              :   /// Returns a [List] of [T] or an empty [List] if [rawValue] is not a [List] of [T]
+     379              :   /// If actual data is not a [List] of [T], calls [builder] to get one
+     380            1 :   List<T> listOfValue<T>([T? Function(Json)? builder]) {
+     381            1 :     return listOf(builder) ?? [];
+     382              :   }
+     383              : 
+     384              :   /// Returns a [Map] or [null] if [rawValue] is not a [Map]
+     385              :   /// Each value of the map is wrapped in a [Json]
+     386            3 :   Map<String, Json>? get map => (_rawValue is Map)
+     387            2 :       ? (_rawValue as Map).map(
+     388            4 :           (Any key, Any value) => MapEntry('$key', Json.fromDynamic(value)),
+     389              :         )
+     390              :       : null;
+     391              : 
+     392              :   /// Returns a [Map] or an empty [Map] if [rawValue] is not a [Map]
+     393              :   /// Each value of the map is wrapped in a [Json]
+     394            3 :   Map<String, Json> get mapValue => (_rawValue is Map)
+     395            2 :       ? (_rawValue as Map).map(
+     396            4 :           (Any key, Any value) => MapEntry('$key', Json.fromDynamic(value)),
+     397              :         )
+     398            1 :       : {};
+     399              : 
+     400              :   /// Returns a [Map] or [null] if [rawValue] is not a [Map]
+     401              :   /// Leaves map values untouched
+     402            1 :   Map<String, Any>? get mapObject =>
+     403            3 :       (_rawValue is Map) ? _rawValue as Map<String, Any> : null;
+     404              : 
+     405              :   /// Returns a [Map] of [T] or empty [Map] if [rawValue] is not a [Map] of [T]
+     406              :   /// Leaves map values untouched
+     407            1 :   Map<String, Any> get mapObjectValue =>
+     408            4 :       (_rawValue is Map) ? _rawValue as Map<String, Any> : {};
+     409              : 
+     410              :   /// Returns a [Map] of [T] or [null] if [rawValue] is not a [Map] of [T]
+     411              :   /// If actual data is not a [Map] of [T], calls [builder] to get one
+     412            1 :   Map<String, T>? mapOf<T>([T? Function(Json)? builder]) {
+     413            2 :     if (_rawValue is Map) {
+     414            2 :       return <String, T>{}..addEntries(
+     415            1 :           (_rawValue as Map)
+     416            1 :               .entries
+     417            2 :               .map<MapEntry<String, T>?>((entry) {
+     418            2 :                 if (entry.key is! String) {
+     419              :                   return null;
+     420            2 :                 } else if (entry.value is T) {
+     421            1 :                   return MapEntry<String, T>(
+     422            2 :                       entry.key as String, entry.value as T);
+     423              :                 } else if (builder != null) {
+     424            3 :                   final element = builder(Json.fromDynamic(entry.value));
+     425              :                   if (element != null) {
+     426            2 :                     return MapEntry(entry.key as String, element);
+     427              :                   }
+     428              :                 }
+     429              : 
+     430              :                 return null;
+     431              :               })
+     432            1 :               .whereType<MapEntry<String, T>>()
+     433            1 :               .toList(),
+     434              :         );
+     435              :     }
+     436              : 
+     437              :     return null;
+     438              :   }
+     439              : 
+     440              :   /// Returns a [List] of [T] or empty [Map] if [rawValue] is not a [List] of [T]
+     441              :   /// If actual data is not a [List] of [T], calls [builder] to get one
+     442            1 :   Map<String, T> mapOfValue<T>([T? Function(Json)? builder]) {
+     443            1 :     return mapOf(builder) ?? {};
+     444              :   }
+     445              : }
+     446              : 
+     447              : /// A mutable Json payload that enforce it'll always be able to json encode it's content
+     448              : class JsonPayload extends Json {
+     449            1 :   JsonPayload();
+     450            2 :   JsonPayload.fromString(super.json) : super.fromString();
+     451            1 :   JsonPayload.fromDynamic(super.rawValue, {super.initial})
+     452            1 :       : super.fromDynamic();
+     453            2 :   JsonPayload.fromMap(super.map) : super.fromMap();
+     454            2 :   JsonPayload.fromList(super.list) : super.fromList();
+     455            2 :   JsonPayload.from(super.other, {super.initial}) : super.from();
+     456              : 
+     457            1 :   set rawValue(Any newValue) {
+     458            1 :     _rawValue = newValue;
+     459              :   }
+     460              : 
+     461            1 :   void operator []=(Any key, Any value) {
+     462            4 :     if (_rawValue is! List && _rawValue is! Map) {
+     463            1 :       throw JsonException(JsonError.wrongType,
+     464              :           userReason: 'Underlying value is neither a map or a list');
+     465              :     }
+     466              : 
+     467            1 :     _set(key, value);
+     468              :   }
+     469              : 
+     470            1 :   @override
+     471            1 :   JsonPayload get jsonNull => JsonPayload.fromDynamic(null);
+     472              : 
+     473            1 :   @override
+     474              :   JsonPayload operator [](Object key) =>
+     475            2 :       JsonPayload.from(super[key], initial: false);
+     476              : 
+     477            1 :   @override
+     478            2 :   List<JsonPayload>? get list => (_rawValue is List)
+     479            1 :       ? (_rawValue as List)
+     480            3 :           .map<JsonPayload>((Any e) => JsonPayload.fromDynamic(e))
+     481            1 :           .toList()
+     482              :       : null;
+     483              : 
+     484            1 :   @override
+     485            1 :   List<JsonPayload> get listValue => list ?? [];
+     486              : 
+     487            1 :   @override
+     488            2 :   Map<String, JsonPayload>? get map => (_rawValue is Map)
+     489            3 :       ? (_rawValue as Map).map((Any key, Any value) =>
+     490            3 :           MapEntry('$key', JsonPayload.fromDynamic(value)))
+     491              :       : null;
+     492              : 
+     493            1 :   @override
+     494            1 :   Map<String, JsonPayload> get mapValue => map ?? {};
+     495              : 
+     496              :   /// Remove element under [key]
+     497            1 :   void removeElementWithKey(Any key) {
+     498            2 :     if (_rawValue is List) {
+     499            1 :       final rawList = _rawValue as List;
+     500              :       late int index;
+     501              : 
+     502            1 :       if (key is String) {
+     503              :         try {
+     504            1 :           index = int.parse(key);
+     505              :         } catch (_, trace) {
+     506            1 :           Error.throwWithStackTrace(
+     507            1 :             JsonException(
+     508              :               JsonError.wrongType,
+     509              :               userReason:
+     510            2 :                   'JSON Error: index must be int, ${key.runtimeType} given',
+     511              :             ),
+     512              :             trace,
+     513              :           );
+     514              :         }
+     515              :       } else {
+     516              :         index = key as int;
+     517              :       }
+     518              : 
+     519            3 :       if (index < 0 || index > rawList.length) {
+     520            1 :         throw JsonException(
+     521              :           JsonError.indexOutOfBounds,
+     522            1 :           userReason: 'JSON Error: index `$index` is out of bounds',
+     523              :         );
+     524              :       }
+     525              : 
+     526            1 :       rawList.removeAt(index);
+     527            2 :     } else if (_rawValue is Map) {
+     528            1 :       final rawMap = _rawValue as Map;
+     529              :       late String index;
+     530              : 
+     531            1 :       if (key is String) {
+     532              :         index = key;
+     533              :       } else {
+     534            1 :         index = '$key';
+     535              :       }
+     536              : 
+     537            1 :       rawMap.remove(index);
+     538              :     } else {
+     539            1 :       throw throw JsonException(
+     540              :         JsonError.wrongType,
+     541              :         userReason: '_rawValue is not a List or a Map',
+     542              :       );
+     543              :     }
+     544              :   }
+     545              : }
+     546              : 
+     547            2 : T? tryCast<T>(Any object) => object is T ? object : null;
+     548              : 
+     549              : /// Json error types
+     550              : enum JsonError {
+     551              :   /// Type is not json encodable
+     552              :   unsupportedType(reason: 'JSON Error: not a valid JSON value'),
+     553              : 
+     554              :   /// Out of bound access to list
+     555              :   indexOutOfBounds(reason: 'JSON Error: index out of bounds'),
+     556              : 
+     557              :   /// Unexpected type
+     558              :   wrongType(
+     559              :     reason:
+     560              :         'JSON Error: either key is not a index type or value is not indexable',
+     561              :   ),
+     562              : 
+     563              :   /// Entry does not exists
+     564              :   notExist(reason: 'JSON Error: key does\'t not exists');
+     565              : 
+     566              :   final String reason;
+     567              : 
+     568              :   const JsonError({required this.reason});
+     569              : }
+     570              : 
+     571              : /// Exceptions are never thrown, instead they are silently stored in the [Json] instance
+     572              : class JsonException implements Exception {
+     573              :   /// What error is this
+     574              :   final JsonError error;
+     575              : 
+     576              :   /// Error message
+     577              :   final String reason;
+     578              : 
+     579            1 :   JsonException(this.error, {String? userReason})
+     580            1 :       : reason = userReason ?? error.reason;
+     581              : 
+     582            1 :   @override
+     583            3 :   String toString() => 'JsonException{error: $error, reason: $reason}';
+     584              : }
+        
+
+
+ + + + +
Generated by: LCOV version 2.1-1
+
+ + + diff --git a/test/darty_json_test.dart b/test/darty_json_test.dart index 1a57dde..832e44d 100644 --- a/test/darty_json_test.dart +++ b/test/darty_json_test.dart @@ -1,16 +1,24 @@ -import 'dart:convert'; - import 'package:darty_json/darty_json.dart'; import 'package:test/test.dart'; +class Person { + String name; + int age; + + Person(this.name, this.age); +} + void main() { group('Json', () { test('Json', () { - var jsonString = """ + final jsonString = """ { "astring": "hello world", "anint": 12, + "intishstring": "12", "afloat": 12.12, + "floatish": true, + "floatishstring": "12.12", "alist": [1, 2, 3, "hello", "world"], "aintlist": [1, 2, 3], "amapofint": { @@ -20,25 +28,38 @@ void main() { "amap": { "hello": "world", "yo": 10 - } + }, + "aboolean": true, + "booleanish": "y", + "booleanishint": 1, + "booleanishdouble": 1.0 } """; - Json json = Json.fromString(jsonString); + final json = Json.fromString(jsonString); expect(json['astring'].string, 'hello world'); expect(json['anint'].integer, 12); + expect(json['anint'].integerValue, 12); + expect(json['afloat'].integerValue, 12); + expect(json['intishstring'].integerValue, 12); + expect(json['floatish'].integerValue, 1); expect(json['afloat'].float, 12.12); expect(json['anint'].float, 12); + expect(json['anint'].floatValue, 12); + expect(json['afloat'].floatValue, 12.12); + expect(json['floatishstring'].floatValue, 12.12); + expect(json['floatish'].floatValue, 1); - expect(json['alist'].list is List, true); + expect(json['alist'].list, TypeMatcher>()); expect(json['alist'].listValue.elementAtOrNull(3)?.string, 'hello'); + expect(json['afloat'].listValue, isEmpty); - expect(json['amap']['hello'].string, 'world'); + expect(json['amap']['hello'].stringValue, 'world'); - expect(json['amap']['hello']['doesnexists'].rawValue, null); + expect(json['amap']['hello']['doesnexists'].rawValue, isNull); expect(json['amap']['doesnexists'].exception?.error, JsonError.notExist); expect( @@ -48,18 +69,33 @@ void main() { expect(json['alist'][1000].exception?.error, JsonError.indexOutOfBounds); expect(json['alist']['hello'].exception?.error, JsonError.wrongType); - expect((json['aintlist'].listOf()?.length ?? 0) > 0, true); - expect((json['amapofint'].mapOf()?.values.length ?? 0) > 0, true); + expect((json['aintlist'].listOf()?.length ?? 0) > 0, isTrue); + expect((json['amapofint'].mapOf()?.values.length ?? 0) > 0, isTrue); + + // Heterogeneous list/map ignore non-matching elements + expect(json['alist'].listOf(), [1, 2, 3]); + expect(json['amap'].mapOf(), {'yo': 10}); - // Heterogeneous list/map don't work - expect(json['alist'].listOf(), null); - expect(json['amap'].mapOf(), null); + expect(json['amap'].map, isNotEmpty); + expect(json['amap'].mapValue, isNotEmpty); + expect(json['alist'].mapValue, isEmpty); + + expect(json['aboolean'].boolean, isTrue); + expect(json['aboolean'].booleanValue, isTrue); + expect(json['booleanish'].booleanValue, isTrue); + expect(json['booleanishint'].booleanValue, isTrue); + expect(json['booleanishdouble'].booleanValue, isTrue); }); }); test('Payload', () { - JsonPayload json = JsonPayload(); + final json = JsonPayload(); + + // Empty payload is null, we can't set any key on it + expect(() => json['newdata'] = 'hello', throwsException); + expect(() => json.removeElementWithKey('one'), throwsException); + json.rawValue = {}; json['newdata'] = 'hello'; expect(json['newdata'].string, 'hello'); @@ -72,7 +108,7 @@ void main() { expect(json['notallowed'].rawValue, null); - JsonPayload jsonFromMap = JsonPayload.fromMap( + final jsonFromMap = JsonPayload.fromMap( { "astring": "hello world", "anint": 12, @@ -82,17 +118,35 @@ void main() { "amapofint": {"yo": 1, "lo": 2}, "amap": {"hello": "world", "yo": 10}, "notallowed": () => "wat", + "true": true, }, ); + expect(jsonFromMap.map, TypeMatcher>()); + expect(jsonFromMap.mapValue, TypeMatcher>()); + expect(jsonFromMap['alist'].list, TypeMatcher>()); + expect(jsonFromMap['alist'].listValue, TypeMatcher>()); expect(jsonFromMap['astring'].string, 'hello world'); expect(jsonFromMap['notallowed'].rawValue, null); expect(jsonFromMap.exception?.error, JsonError.unsupportedType); + expect((jsonFromMap..removeElementWithKey('astring')).exists('astring'), + isFalse); + + expect((jsonFromMap..removeElementWithKey(true)).exists('true'), isFalse); + + jsonFromMap['alist'].removeElementWithKey(0); + expect(jsonFromMap['alist'].listValue.firstOrNull?.rawValue, 2); + + expect( + () => jsonFromMap['alist'].removeElementWithKey(100), throwsException); + expect(() => jsonFromMap['alist'].removeElementWithKey('nope'), + throwsException); + jsonFromMap['newkey'] = 'hello'; expect(jsonFromMap['newkey'].string, 'hello'); - JsonPayload jsonFromList = JsonPayload.fromList([1, 2, 3, 4]); + final jsonFromList = JsonPayload.fromList([1, 2, 3, 4]); jsonFromList[4] = 'hello'; @@ -100,7 +154,7 @@ void main() { }); test('Nested payload modification', () { - var jsonString = ''' + final jsonString = ''' { "request": { @@ -108,7 +162,7 @@ void main() { } }'''; - var jsonPayload = JsonPayload.fromString(jsonString); + final jsonPayload = JsonPayload.fromString(jsonString); jsonPayload["request"]["pathName"] = "toto"; @@ -116,30 +170,243 @@ void main() { }); test('Expect JSON object to be jsonEncodable', () { - var requestString = '{"pathName":"yolo"}'; - var jsonInputString = '''{"request":$requestString}'''; + final requestString = '{"pathName":"yolo"}'; + final jsonInputString = '{"request":$requestString}'; final jsonPayload = JsonPayload.fromString(jsonInputString); - expect(jsonEncode(jsonPayload), jsonInputString); - expect(jsonEncode(jsonPayload["request"]), requestString); + expect(jsonPayload.toString(), jsonInputString); + expect(jsonPayload["request"].toString(), requestString); jsonPayload["request"]["pathName"] = "toto"; - expect(jsonEncode(jsonPayload["request"]), '{"pathName":"toto"}'); - expect(jsonEncode(jsonPayload["baaaaaaad"]), "null"); + expect(jsonPayload["request"].toString(), '{"pathName":"toto"}'); + expect(jsonPayload["baaaaaaad"].toString(), "null"); }); test('Expect JSON object to be equals', () { final jsonPayload1 = - JsonPayload.fromString('''{"request":{"pathName":"yolo"}}'''); + JsonPayload.fromString('{"request":{"pathName":"yolo"}}'); final jsonPayload2 = - JsonPayload.fromString('''{"request":{"pathName":"yolo"}}'''); + JsonPayload.fromString('{"request":{"pathName":"yolo"}}'); final jsonPayload3 = - JsonPayload.fromString('''{"request":{"pathName":"yolo2"}}'''); + JsonPayload.fromString('{"request":{"pathName":"yolo2"}}'); expect(jsonPayload1, jsonPayload1); expect(jsonPayload1, jsonPayload2); - expect(jsonPayload1 != jsonPayload3, true); + expect(jsonPayload1 != jsonPayload3, isTrue); + }); + + test('fromXXX', () { + final payload = Json.fromDynamic( + { + "one": 1, + "more": [1, 2, 3], + }, + ); + + expect(payload.toString(), '{"one":1,"more":[1,2,3]}'); + + final invalidPayload = Json.fromDynamic( + { + "one": 1, + "invalid": Exception('invalid'), + "more": [1, 2, 3], + }, + ); + + expect(invalidPayload.toString(), '{"one":1,"more":[1,2,3]}'); + expect(invalidPayload.exception, TypeMatcher()); + expect(invalidPayload.exception?.toString(), + 'JsonException{error: JsonError.unsupportedType, reason: ${JsonError.unsupportedType.reason}}'); + + final listPayload = Json.fromDynamic([1, "two", false]); + + expect(listPayload.toString(), '[1,"two",false]'); + + final invalidListPayload = Json.fromDynamic( + [1, "two", Exception('invalid'), false], + ); + + expect(invalidListPayload.toString(), '[1,"two",false]'); + expect(tryCast(invalidListPayload.exception)?.error, + JsonError.unsupportedType); + + expect( + tryCast(Json.fromDynamic(Exception('invalid')).exception) + ?.error, + JsonError.unsupportedType, + ); + expect( + tryCast(Json.fromDynamic(Json.fromDynamic(true)).exception) + ?.error, + JsonError.unsupportedType, + ); + + final invalidList = Json.fromList([1, "two", Exception('invalid'), false]); + + expect(invalidList.toString(), '[1,"two",false]'); + expect(invalidList.exception, TypeMatcher()); + + expect(Json.from(Json.fromDynamic(1)).toString(), '1'); + expect(Json.from(Json.fromDynamic(true)).toString(), 'true'); + expect(Json.from(Json.fromDynamic(12.42)).toString(), '12.42'); + expect(Json.from(Json.fromDynamic(null)).toString(), 'null'); + expect(Json.from(Json.fromDynamic({})).toString(), '{}'); + expect(Json.from(Json.fromDynamic([])).toString(), '[]'); + }); + + test('Json == Json', () { + expect(Json.fromDynamic([1, 2, 3]) == Json.fromDynamic([1, 2, 3]), true); + + expect( + { + Json.fromDynamic([1, 2, 3]): true, + }[Json.fromDynamic([1, 2, 3])], + true, + ); + }); + + test('list set', () { + final list = JsonPayload.fromList([1, 2, 3]); + + expect(() => list[100] = true, throwsException); + expect(() => list["yolo"] = true, throwsException); + + list[0] = "hello"; + + expect(list[0].rawValue, "hello"); + + expect(list[0].exists(), isTrue); + expect(list[100].exists(), isFalse); + expect(list.exists(0), isTrue); + expect(list.exists(100), isFalse); + }); + + test('ofType', () { + final payload = Json.fromDynamic({"name": "joe", "age": 30}); + + expect( + payload + .ofType( + (raw) => Person(raw['name'].stringValue, raw['age'].integerValue), + ) + ?.name, + 'joe', + ); + + expect(payload['name'].ofType(), 'joe'); + + expect( + payload + .ofTypeValue( + Person('default', 0), + (raw) => Person(raw['name'].stringValue, raw['age'].integerValue), + ) + .name, + 'joe', + ); + + expect(payload['name'].ofTypeValue('default'), 'joe'); + expect(payload['age'].ofTypeValue('default'), 'default'); + + final listPayload = Json.fromDynamic([ + { + "name": "joe", + "age": 30, + }, + ]); + + expect( + listPayload + .listOf( + (raw) => Person(raw['name'].stringValue, raw['age'].integerValue), + )?[0] + .name, + 'joe', + ); + + expect( + listPayload + .listOfValue( + (raw) => Person(raw['name'].stringValue, raw['age'].integerValue), + )[0] + .name, + 'joe', + ); + + final heterogenousList = Json.fromDynamic([ + { + "name": "joe", + "age": 30, + }, + "something else", + { + "name": "doe", + "age": 31, + }, + ]); + + expect( + heterogenousList + .listOfValue( + (raw) => Person(raw['name'].stringValue, raw['age'].integerValue), + )[0] + .name, + 'joe', + ); + + expect( + heterogenousList.listOfValue( + (raw) => raw.exists('name') && raw.exists('age') + ? Person(raw['name'].stringValue, raw['age'].integerValue) + : null, + ), + hasLength(2), + ); + + final heterogenousMap = Json.fromDynamic( + { + "one": { + "name": "joe", + "age": 30, + }, + "two": "something else", + "three": { + "name": "doe", + "age": 31, + }, + }, + ); + + expect( + heterogenousMap + .mapOfValue( + (raw) => Person(raw['name'].stringValue, raw['age'].integerValue), + )["one"] + ?.name, + 'joe', + ); + + expect( + heterogenousMap.mapOfValue( + (raw) => raw.exists('name') && raw.exists('age') + ? Person(raw['name'].stringValue, raw['age'].integerValue) + : null, + ), + hasLength(2), + ); + }); + + test('listOf', () { + expect(Json.fromList([1, 2, 3]).listObject?[0], 1); + expect(Json.fromList([1, 2, 3]).listObjectValue[0], 1); + expect(Json.fromDynamic(null).listObjectValue, isEmpty); + }); + + test('mapOf', () { + expect(Json.fromMap({'one': 1}).mapObject?['one'], 1); + expect(Json.fromMap({'one': 1}).mapObjectValue['one'], 1); + expect(Json.fromDynamic(null).mapObjectValue, isEmpty); }); } diff --git a/updown.png b/updown.png new file mode 100644 index 0000000..aa56a23 Binary files /dev/null and b/updown.png differ