Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/dogs_core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## Unreleased

- Add an opt-in `DogModelCodec` for strict JSON model reads, typed nested models, input aliases, explicit conversions, and filtered raw maps.
- Add unified `JsonKey` field metadata (`aliases`, `name`, `convert`, `each`, `parse`, `read`, `value`) in place of separate alias/convert/parser annotations.
- Add `JsonModel` class metadata and `JsonConversions` static converter tear-offs (`stringify`, `flagInt`, `integer`) for use with `JsonKey`.
- Allow `JsonModel(emptyListsOnNull: true)` to default all list fields on an opted-in model without per-field annotations.
- Allow `DefaultValue(onNull: true)` to supply a default for explicit null input; the existing missing-only behavior remains the default.

## 10.2.0

- **FEAT**: Harder version bump.
Expand Down
47 changes: 46 additions & 1 deletion packages/dogs_core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,51 @@ class Person with Dataclass<Person> {
[Package on pub.dev](https://pub.dev/packages/dogs_core)
[Documentation](https://dogs.helight.dev)

## Declarative JSON model mapping

`DogModelCodec` is an opt-in reader for APIs that need strict Dart casts and
stable request keys. It uses the existing generated structures and does not
change native `DogEngine.fromNative` behavior. Import `dogs_models.dart` to use
its annotations:

```dart
import 'package:dogs_core/dogs_models.dart';
import 'dogs.g.dart';

@JsonModel(outputKeys: ['id', 'name'])
class City {
@JsonKey(aliases: ['legacy_id', 'id'])
final int? id;

final String? name;

City({this.id, this.name});
}

final models = DogModelCodec(
engine: configureDogs(plugins: [GeneratedModelsPlugin()]),
);
final city = models.fromJson<City>({'legacy_id': 1, 'name': 'Erbil'});
final request = models.toJson(city); // {'id': 1, 'name': 'Erbil'}
```

`@JsonModel` also marks the class for DOGs generation. The codec reads
registered nested models and lists recursively.
`@JsonModel(emptyListsOnNull: true)` supplies fresh typed empty lists for
missing or null list fields on that model. Use one `@JsonKey(...)` per field:
`aliases` for alternate input names, `name` for an explicit input key,
`convert` for whole-value conversion (for example `JsonConversions.stringify`
or `JsonConversions.flagInt`), `each` for list-element conversion,
`parse` for nested object maps (or each list element map), `read` when the
rule needs the full parent map, and `value` to force a decode value
(including explicit `null`). Callback arguments must be constant top-level or
static function tear-offs for generated metadata (factory constructor
tear-offs are not retained by codegen).

`toJson` returns raw constructor values, including nulls and nested model
instances. Use `engine.toNative` for a recursively serialized object graph.
Applications can subclass `DogModelGuard` to customize nested parsing errors.

## Flutter <a href="https://pub.dev/packages/dogs_flutter"><img src="https://img.shields.io/pub/v/dogs_flutter?label=dogs_flutter" alt="pub"></a>

Use our flutter integration to easily create forms for any serializable object, without having
Expand All @@ -121,4 +166,4 @@ you’ll interact with generated code directly is when using builders, which are
the central dogs.g.dart.

This design keeps Dogs non-intrusive and future-proof. While part files may be added later for flexibility,
you will never be required to reference generated code from within your model definition files.
you will never be required to reference generated code from within your model definition files.
1 change: 1 addition & 0 deletions packages/dogs_core/lib/dogs_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export "src/graph_value.dart";
export "src/hooks.dart";
export "src/json.dart";
export "src/metadata.dart";
export "src/model_codec.dart";
export "src/opmodes/factory.dart";
export "src/opmodes/operation.dart";
export "src/projections.dart";
Expand Down
4 changes: 4 additions & 0 deletions packages/dogs_core/lib/dogs_models.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/// Declarative, strict JSON model mapping using generated DOGs structures.
library;

export "dogs_core.dart" hide DogLinked, compareTypeHashcodes, isPolymorphicField;
7 changes: 5 additions & 2 deletions packages/dogs_core/lib/src/hooks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,16 @@ class DefaultValue extends StructureMetadata with FieldSerializationHook {
/// If the default value should be kept even if the field is present in the serialized map.
final bool keep;

/// Whether an explicitly null input also receives the default value.
final bool onNull;

/// Creates a new [DefaultValue] with the given [value].
/// The [value] may be any constant dart value or a `dynamic Function()` which will
/// be evaluated as the supplier of the default value.
///
/// If [keep] is set to `true`, the default value will be kept in the serialized map,
/// otherwise it will be removed if the field is equal to the default value.
const DefaultValue(this.value, {this.keep = false});
const DefaultValue(this.value, {this.keep = false, this.onNull = false});

dynamic _getNativeDefault(NativeStructureFieldContext fieldContext, DogEngine engine) {
final provided = value is DefaultValueSupplier ? value() : value;
Expand All @@ -153,7 +156,7 @@ class DefaultValue extends StructureMetadata with FieldSerializationHook {
@override
void beforeFieldDeserialization(NativeStructureContext context,
NativeStructureFieldContext fieldContext, Map<String, dynamic> map, DogEngine engine) {
if (!map.containsKey(fieldContext.key)) {
if (!map.containsKey(fieldContext.key) || (onNull && map[fieldContext.key] == null)) {
map[fieldContext.key] = _getNativeDefault(fieldContext, engine);
}
}
Expand Down
Loading