codec 0.3.0
codec: ^0.3.0 copied to clipboard
Type-safe, composable JSON codecs for Dart: structured CodecException with $.path on failure. Nested objects, discriminated unions, recursion, multi-version compatibility.
Changelog #
0.3.0 #
Codable.fieldRenameis now nullable (FieldRename?, defaultnull).nullmeans "inherit the project-level default" (thefield_renamebuild option incodec_gen, falling back toFieldRename.none); an explicit value — includingFieldRename.none— overrides it. Behaviourally compatible: with no global default configured,nullresolves tononeexactly as before.
0.2.1 #
- Docs & metadata: English
descriptionand CHANGELOG;repositorynow points to the package subdirectory (pub.dev scoring). No code changes.
0.2.0 #
Breaking changes #
CodecExceptionno longer extendsFormatException:decode/encodenow throw their own typesDecodeException/EncodeException(both sealed subclasses ofCodecException) instead of the Dart built-inFormatException. Migration:- codegen users: set
exception_style: formaton thecodec_genbuilder inbuild.yaml; the generated top-level codec will automatically append.withFormatExceptions(), preserving the old behavior. - hand-written codecs: call
.withFormatExceptions()on the outermost codec to wrap allCodecExceptionthrows asFormatException. - structured error handling: switch to
on DecodeException/on EncodeExceptionand useerrors/isAllMissing/hasWrongType(decode) orcause/causeStackTrace(encode) to inspect details.
- codegen users: set
Added #
Codec.withFormatExceptions()combinator: attach to the outermost codec to makedecode/encodewrap allCodecExceptionthrows as Dart's built-inFormatException— a zero-migration compatibility shim for existingon FormatExceptionhandlers. Must be the last call in the chain (applies to the entire codec chain).UnexpectedErrorerror kind: a top-level catch-all that captures any non-CodecExceptionthrown unexpectedly insidedecode/encode, converging it into aDecodeExceptionto keep the exception model airtight.
Fixes (robustness) #
- DateTime codecs no longer leak underlying exceptions: NaN / ±Infinity / out-of-range
epoch numbers (
UnsupportedError/RangeError) are now caught and converted toBadFormat, thrown as aDecodeException. refinepredicate throws + top-level unexpected exceptions are now unified: arbitrary exceptions thrown inside arefinepredicate, and unexpected exceptions that escapedecode/encode, are all converged intoDecodeException— decode / encode are now airtight and only throwCodecException(orFormatExceptionin compat mode).
Tests #
- Runtime test count: 87 → 114 (new exception hierarchy, DateTime edge cases, refine robustness, and related coverage).
0.1.6 #
Added (unknown-value forward compatibility) #
Codec.enumOf/Codec.enumByNamenow accept an optionalunknownFallback: when decoding encounters an unknown tag (no mapping hit), fall back to the specified enum value instead of throwingUnknownTag. Defaultnull= retain the original strict behavior, fully backwards-compatible. Typical use case: the backend may add or rename enum codes; the client needs forward compatibility so a single unknown value does not cause the entire record to fail decoding.CodecField.unknownEnumValue(annotation layer): used alongsideenumValueFieldto declare the fallback enum value for unknown codes; codec_gen passes it through to the generatedCodec.enumOf(..., unknownFallback:). See codec_gen 0.1.6 for the codegen implementation.
Design trade-off (preserving "no silent schema-drift swallowing") #
unknownFallbackonly catches "value decoded successfully but tag not in mapping"; type / format errors in the innervalueCodecstill bubble normally (e.g., an int field receiving a non-numeric string) and are not swallowed by the fallback. This mirrorswithDefault(null-only fallback): the deliberate distinction between "unknown enum item" (business-level forward compat) and "wrong protocol shape" (must be exposed) is intentional.
Tests #
- 3 new cases:
enumOf/enumByNameunknown-value fallback;unknownFallbackdoes not swallow inner type errors. Runtime test count: 84 → 87.
Version #
- Runtime package code changed (added
unknownFallback/unknownEnumValue);versionbumped 0.1.5 → 0.1.6, aligned with CHANGELOG.
0.1.5 #
Added (annotation layer) #
CodecField.enumValueField: map an enum field by the value of one of the enum instance's own fields (e.g.code) instead of the enum name, without requiring@CodecEnumon the enum itself. Suitable for enums in a core / domain layer that should not depend on the serialization framework (no import or part file needed). Only valid on enum fields; the target field type must beint/String/double/num; mutually exclusive withdateTime; when set alongsidecodec,codectakes precedence. See codec_gen 0.1.5 for the codegen implementation.
Historical debt (cleanup) #
pubspec.yamlversionhad been stuck at0.1.0while the CHANGELOG had progressed to0.1.4— a drift first noted in the codec_gen 0.1.4 "historical debt" entry. Since this release touches runtime code (addsenumValueField), the version has been bumped to0.1.5to realign with the CHANGELOG.
0.1.4 #
Build / workspace #
- Joined pub workspace: the root
pubspec.yamllists this package underworkspace:; this package'spubspec.yamladdsresolution: workspace. Result: this package no longer generates its own.dart_tool/package_config.json; dependency resolution is handled by the root. The IDE's analyzer view from the project root can now resolvepackage:test/test.dart(previouslytest/expectwere red in sub-package test files because the root usedflutter_testinstead oftest). - SDK constraint raised:
^3.4.0→^3.6.0(minimum required for pub workspace). lintsdowngraded to^4.0.0: workspace shares a single dependency resolution, so this package must be compatible with the root'sflutter_lints ^4.0.0(which transitively requireslints ^4.0.0). All lint rules used by this package are fully covered by lints 4.x; no functionality lost..gitignoreextended withbuild/: runningflutter testin the sub-package produces hundreds of unit_test assets and native_assets; these are excluded from the repository.
0.1.3 #
Added #
Codec.dateTimeMillisUtc: a symmetric codec between epoch-millisecond numbers and UTCDateTime. Decode accepts only numbers and returns aDateTimewithisUtc=true, preventing device-timezone contamination when accessing.year/.dayetc.; encode returns a millisecond integer (not an ISO string), matching the common pattern where both sides of a backend protocol use millisecond timestamps.Codec.dateTimeSecondsUtc: same as above but at second granularity. Encode uses integer truncation (consistent with the Unixtime(0)convention); sub-second precision is discarded by the second-granularity protocol.DateTimeMode.millisUtc/DateTimeMode.secondsUtc: new enum values on the codec_gen side; select them with@CodecField(dateTime: DateTimeMode.millisUtc).
Tests #
- 10 new cases: UTC
isUtcflag, number round-trip, ISO string rejection, NaN/Infinity rejection, sub-second truncation, both local and UTC DateTime produce absolute milliseconds on encode.
0.1.2 #
Added (annotation layer) #
FieldRename.screamingSnake:userName→USER_NAME.DateTimeModeenum +CodecField.dateTimefield: lets codec_gen switch toCodec.dateTimeUtc/Codec.dateTimeSecondswithout string references.CodecIgnoreannotation:@CodecIgnore()is equivalent to@CodecField(ignore: true).
Docs #
CodecField.includeIfNulldartdoc updated from "field-level override not yet implemented" to the formal semantic description (implemented in codec_gen 0.1.2).CodecField.defaultValuedartdoc explicitly documents support for const List / const Map.
0.1.1 #
Behavior changes (fix potential silent data corruption) #
Codec.integernow rejects true decimals (1.5) and NaN/±Infinity; it only accepts whole-number floats (1.0) and integer strings with no decimal point. The previous silenttoInt()truncation could silently discard the fractional part; it now throwsBadFormatexplicitly.Codec.number/Codec.numericthrowEncodeExceptionwhen encoding NaN/±Infinity, pulling the error into the codec error model and preventingJsonUnsupportedObjectErrorfrom surfacing only at the outerjsonEncodecall.Codec.discriminatedencode: if theencodeclosure's returned body accidentally contains a key with the same name as the discriminator field, the codec-injected tag always wins (map literal spread order changed from{tag, ...body}to{...body, tag}).
Added #
Codec.dateTimeUtc: encodes by callingtoUtc().toIso8601String(), producing semantically consistent output regardless of the local timezone.Codec.dateTimeSeconds: interprets epoch numbers as Unix seconds, avoiding the existingdateTimecodec accidentally treating them as milliseconds (which would parse to a date near 1970).Codec.dateTimeitself: epoch numbers now acceptnum(previously onlyint), allowing JS-serialized values like1700000000000.0(integer milliseconds with a decimal point).
Doc enhancements #
Codec.withDefault/FieldsReader.optionalOrnow explicitly document "null-only fallback" to avoid confusion with "fallback on any failure"; useCodec.firstOfto express the latter explicitly.
Tests #
- 15 new edge-case tests covering the above changes:
integerstrict integer-float vs true-decimal distinction;number/numericencode non-finite;discriminatedtag override prevention;dateTimemulti-form epoch input. Total tests: 58 → 73.
0.1.0 #
- First independent release: extracted from internal codebase as a standalone pub package.
- Public API:
Codec<T>abstract class + static factories (string/object/discriminated/lazy/firstOf/enumByName/mapOf/custom, etc.) + chainable combinators (nullable/withDefault/refine/bimap/list/orElse) +MapOmitNullsextension. - On failure, throws Dart SDK built-in
FormatException; message includes$.path[idx].fieldlocation and supportsErrorMode.failFast/ErrorMode.accumulatefor multi-error aggregation. - 38 unit tests covering primitives, combinators, nested paths, discriminated union, lazy recursion, firstOf multi-version compatibility, encode failure wrapping, and omitNulls.