Git Product home page Git Product logo

genq's Introduction

genq: Instant Data Class Generation for Dart

Go pub package

logo

Motivation

With dart, creating data classes can be tedious. You have the choice between:

  • Writing them by hand, which is error-prone and time-consuming.
  • Using code generation tools like build_runner with freezed, which become slow for large projects.

genq cuts through the wait and generates data classes for your Dart projects in milliseconds, not minutes. ⚡️

Inspired by freezed, genq offers a familiar syntax for defining data classes, but with a focus on lightning-fast performance.

logo

The above image shows the difference in performance between build_runner and genq for generating 2500 data classes. See more details in the benchmarks section.

Index

Benchmarks

build_runner + freezed 🐌 genq 🚀
build_runner genq

In this benchmark (located in ./benchmarks), count is the number of files in the benchmark, containing 250 annotated classes each. So for example, count=1 means 250 classes, count=2 means 500 classes, and so on. For count 10, build_runner and freezed took around 14.9 seconds, while genq took 0.11 seconds. This is a >100x speedup!

Notes

  1. Never trust a benchmark you didn't falsify yourself.
  2. genq is optimized to perform one task and one task only - data class generation, whereas build_runner is built to do a lot more. Take this into account when choosing between the two.

Getting Started

1. Install

Install genq via brew using the following command:

brew install jankuss/genq/genq

Or download the latest release from the releases page.

2. Add genq to your project

dependencies:
  # ... Other dependencies ...
  genq: ^0.3.0

3. Define your data classes

import 'package:genq/genq.dart';

part 'user.genq.dart';

@genq
class User with _$User {
  factory User({
    required String name,
    required int age,
  }) = _User;
}

Read more about defining your data classes here.

4. Generate the code

Run the genq command in your project directory, and you will have your desired data classes generated in no time:

genq

Defining Data Classes

Overview

To define a data class, you need to annotate the class with @genq and provide a factory constructor with named parameters.

import 'package:genq/genq.dart'; // <- Import genq

part 'user.genq.dart'; // <- Add a part directive to the generated file

@genq // <- Annotate the class with @genq
class User with _$User { // <- Add the mixin _$<ClassName>
  factory User({ // <- Define a factory constructor
    required String name, // <- Define fields as named parameters
    required int age,
  }) = _User; // <- Redirecting constructor, _<ClassName>
}

The generated class will have the following methods:

  • copyWith: Create a copy of the data class with modified fields.
  • toString: Generate a human-readable string representation of the data class.
  • ==: Compare two data classes for equality.

JSON Serialization/Deserialization

To generate JSON serialization/deserialization code, you need to use the @Genq(json: true) annotation instead of @genq.

import 'package:genq/genq.dart';

part 'user.genq.dart';

@Genq(json: true)
class User with _$User {
  factory User({
    @JsonKey(name: 'full_name')
    required String name,
    required int age,
  }) = _User;
}

This will generate two public functions, which you can use to serialize/deserialize the data class to/from JSON:

$UserFromJson(Map<String, dynamic> json) => /* ... */;
$UserToJson(User value) => /* ... */;

Customize JSON Serialization

You can customize the generated JSON serialization/deserialization code for fields using the @JsonKey annotation.

import 'package:genq/genq.dart';

part 'user.genq.dart';

@Genq(json: true)
class User with _$User {
  factory User({
    // Customizing the JSON key for the field 'name'. When deserializing, the value of 'full_name' will be assigned to the 'name' field.
    @JsonKey(name: 'full_name')
    required String name,
    // Providing a default value for the field 'age'. If the field is not present in the JSON, the default value will be used.
    @JsonKey(defaultValue: 99)
    required int age,
  }) = _User;
}

Custom fromJson and toJson functions

You can also provide custom fromJson and toJson functions for a field using the fromJson and toJson parameters of the @JsonKey annotation.

import 'package:genq/genq.dart';

part 'user.genq.dart';

class UserName {
  final String value;

  UserName(this.value);

  static UserName fromJson(String value) {
    return UserName(value);
  }

  static String toJson(UserName value) {
    return value.value;
  }
}

@Genq(json: true)
class User with _$User {
  factory User({
    @JsonKey(
      fromJson: UserName.fromJson,
      toJson: UserName.toJson,
    )
    required UserName name,
    required int age,
  }) = _User;
}

Unknowns enum values

You can provide a value for unknown enum values using the unknownEnumValue parameter of the @JsonKey annotation. When deserializing and encountering an unknown value, the unknownEnumValue will be used instead of throwing an exception.

import 'package:genq/genq.dart';

part 'user.genq.dart';

@GenqJsonEnum()
enum Role {
  admin,
  user,
  unknown,
}

@Genq(json: true)
class User with _$User {
  factory User({
    @JsonKey(unknownEnumValue: Role.unknown)
    required Role role,
  }) = _User;
}

Enums

Enums are also supported for JSON serialization/deserialization. They need to be annotated with @GenqJsonEnum.

import 'package:genq/genq.dart';

part 'user.genq.dart';

@GenqJsonEnum()
enum Role {
  // You can annotate the enum values with @JsonValue to customize the JSON serialization/deserialization.
  // For example, the string 'ADMIN' will get deserialized to the Role.admin value and vice versa.
  // If you don't provide a value for @JsonValue, the enum key is used.
  @JsonValue('ADMIN')
  admin,
  @JsonValue('USER')
  user,
}

Similary to the @Genq(json: true) annotation, this will generate two public functions, which you can use to serialize/deserialize the enum to/from JSON:

$RoleFromJson(Object json) => /* ... */;
$RoleToJson(Role value) => /* ... */;

Notes

The fundamental idea behind the JSON codegen for it to be fast and efficient is to publicly expose the generated functions. Based on this, genq can assume for a type T that the functions $TFromJson and $TToJson are available, thus avoiding unnecessary traversal of other files.

How?

genq uses its own subset parser of the dart language and generates code directly from the parsed AST. This allows genq to generate code much faster than build_runner, which uses the analyzer package. Code generation is also done in parallel for each file, which further speeds up the process.

Notes on the subset parser

The subset parser is written for the specific structures of data classes as defined here. Thus, there may be parsing errors if the code does not follow the expected structure. While the parser is generally robust when encountering unparsable code, there may be cases where it fails to parse the code correctly. If you encounter such a case, please open an issue with the code that caused the error.

When should I use genq over build_runner?

One great thing: you don't have to choose! You can use both in your project. A good guidline would be: Use genq for data class generation in your day-to-day development, and build_runner for more complex code generation tasks.

If your project is sufficiently small, you might not even need genq. However, if you find yourself or your team spending a lot of time waiting for build_runner to generate code, genq might be a good alternative.

Future Plans

  • Editor support (VSCode, IntelliJ)
  • Extensibility

genq's People

Contributors

jankuss avatar

Stargazers

 avatar Lucas.Xu avatar Bishal Rumba avatar Biplab Dutta avatar Ryo Takeuchi avatar Hugo avatar Abhishek Bahadur Niraula avatar aloptrbl avatar Sebbagh Djameleddine avatar Kosei Akaboshi avatar Sacha Arbonel avatar Julio Rios avatar Lai avatar Jungo Takagi avatar Liber Da Silva avatar Artur Cheymos avatar Vitaliy Podushkin avatar Ulrich Diedrichsen avatar 송진우 avatar Qazeem Abiodun Qudus avatar 8thgencore avatar Ivan Smetanin avatar Plague Fox avatar Ryotaro Onoue avatar K9i - Kota Hayashi avatar Jeffrey avatar Zemlaynikin Max avatar tsuyoshi wada avatar Muhammad Vaid avatar shogo134 avatar  avatar Amin Memariani avatar Jimmy Nelle avatar Miller Adulu avatar Andres Araujo avatar Daichi Furiya avatar Florian avatar  avatar fedix avatar Zlati Pehlivanov avatar  avatar razrab.ytka avatar Handika avatar Jesse avatar Victor Ferraz avatar Felipe de León avatar Talha Kerpiççi avatar onFire(Abhi) avatar Agalaba Ifeanyi Precious avatar Pedro Marinho avatar Prabin Lamsal avatar Jeilson Araujo avatar Chima Precious avatar Pascal Welsch avatar Victor Mariano avatar Moyinoluwa Shabi avatar Subroto Kumar avatar Ren Strydom avatar Naimul Kabir avatar Lorenzo Gangemi avatar  avatar Varun avatar Davide Bolzoni avatar hab avatar Abel Koudaya avatar  avatar Tomiwa Idowu avatar Jorge Coca avatar Petyo Tsonev avatar Felipe Amorim avatar Utpal Barman avatar Nikita Sirovskiy avatar Alejandro Giubel Hernández Arbelo avatar  avatar Yousuf Omer avatar Karlo Verde avatar Pedro Massango avatar Maksim Lin avatar Tony Blanco avatar Moshe Dicker avatar Dung Nguyen Minh avatar Diego Ponce de León avatar  avatar Abdulrasheed Fawole avatar Renan avatar Jeremiah Ogbomo avatar Leynier Gutiérrez González avatar Yeikel Uriarte Arteaga avatar Martin Jensen avatar Tobias Zuber avatar  avatar Pedro Nogueira Prado dos Santos avatar Benevides avatar Lucas de Oliveira Melo avatar Pedro Santos avatar Fernando da Costa Felício avatar Dannylo Dangel avatar Matheus Felipe avatar Robson Silva avatar Felix Krüger avatar

Watchers

 avatar  avatar  avatar  avatar

genq's Issues

Hash code gen bug: Too many positional arguments

The hashcode function that gets generated cannot handle more than 20 arguments. Therefore once a class reaches over 20 properties this error occurs:

Error: Too many positional arguments: 20 allowed, but 21 found.
Try removing the extra positional arguments.
    return Object.hash(
                      ^
org-dartlang-sdk:///third_party/dart/sdk/lib/core/object.dart:183:14: Context: Found this candidate, but the arguments don't match.
  static int hash(Object? object1, Object? object2,

The way to fix this is to use

@override
int get hashCode => Object.hashAll([ <List of properties> ]);

instead of

@override
int get hashCode => Object.hash(<List of properties>);

Is it possible to generate class methods?

Today we use freezed to generate some class methods like this:

import 'package:freezed_annotation/freezed_annotation.dart';

part 'something_state.freezed.dart';

@freezed
class SomethingState with _$SomethingState {
  const factory SomethingState.initial() = _Initial;
  const factory SomethingState.loading() = _Loading;
  const factory SomethingState.invalid(String errorMessage) = _Invalid;
}
and freezed outputs:
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark

part of 'something_state.dart';

// **************************************************************************
// FreezedGenerator
// **************************************************************************

T _$identity<T>(T value) => value;

final _privateConstructorUsedError = UnsupportedError(
    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');

/// @nodoc
mixin _$SomethingState {
  @optionalTypeArgs
  TResult when<TResult extends Object?>({
    required TResult Function() initial,
    required TResult Function() loading,
    required TResult Function(String errorMessage) invalid,
  }) =>
      throw _privateConstructorUsedError;
  @optionalTypeArgs
  TResult? whenOrNull<TResult extends Object?>({
    TResult? Function()? initial,
    TResult? Function()? loading,
    TResult? Function(String errorMessage)? invalid,
  }) =>
      throw _privateConstructorUsedError;
  @optionalTypeArgs
  TResult maybeWhen<TResult extends Object?>({
    TResult Function()? initial,
    TResult Function()? loading,
    TResult Function(String errorMessage)? invalid,
    required TResult orElse(),
  }) =>
      throw _privateConstructorUsedError;
  @optionalTypeArgs
  TResult map<TResult extends Object?>({
    required TResult Function(_Initial value) initial,
    required TResult Function(_Loading value) loading,
    required TResult Function(_Invalid value) invalid,
  }) =>
      throw _privateConstructorUsedError;
  @optionalTypeArgs
  TResult? mapOrNull<TResult extends Object?>({
    TResult? Function(_Initial value)? initial,
    TResult? Function(_Loading value)? loading,
    TResult? Function(_Invalid value)? invalid,
  }) =>
      throw _privateConstructorUsedError;
  @optionalTypeArgs
  TResult maybeMap<TResult extends Object?>({
    TResult Function(_Initial value)? initial,
    TResult Function(_Loading value)? loading,
    TResult Function(_Invalid value)? invalid,
    required TResult orElse(),
  }) =>
      throw _privateConstructorUsedError;
}

/// @nodoc
abstract class $SomethingStateCopyWith<$Res> {
  factory $SomethingStateCopyWith(
          SomethingState value, $Res Function(SomethingState) then) =
      _$SomethingStateCopyWithImpl<$Res, SomethingState>;
}

/// @nodoc
class _$SomethingStateCopyWithImpl<$Res, $Val extends SomethingState>
    implements $SomethingStateCopyWith<$Res> {
  _$SomethingStateCopyWithImpl(this._value, this._then);

  // ignore: unused_field
  final $Val _value;
  // ignore: unused_field
  final $Res Function($Val) _then;
}

/// @nodoc
abstract class _$$InitialImplCopyWith<$Res> {
  factory _$$InitialImplCopyWith(
          _$InitialImpl value, $Res Function(_$InitialImpl) then) =
      __$$InitialImplCopyWithImpl<$Res>;
}

/// @nodoc
class __$$InitialImplCopyWithImpl<$Res>
    extends _$SomethingStateCopyWithImpl<$Res, _$InitialImpl>
    implements _$$InitialImplCopyWith<$Res> {
  __$$InitialImplCopyWithImpl(
      _$InitialImpl _value, $Res Function(_$InitialImpl) _then)
      : super(_value, _then);
}

/// @nodoc

class _$InitialImpl implements _Initial {
  const _$InitialImpl();

  @override
  String toString() {
    return 'SomethingState.initial()';
  }

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        (other.runtimeType == runtimeType && other is _$InitialImpl);
  }

  @override
  int get hashCode => runtimeType.hashCode;

  @override
  @optionalTypeArgs
  TResult when<TResult extends Object?>({
    required TResult Function() initial,
    required TResult Function() loading,
    required TResult Function(String errorMessage) invalid,
  }) {
    return initial();
  }

  @override
  @optionalTypeArgs
  TResult? whenOrNull<TResult extends Object?>({
    TResult? Function()? initial,
    TResult? Function()? loading,
    TResult? Function(String errorMessage)? invalid,
  }) {
    return initial?.call();
  }

  @override
  @optionalTypeArgs
  TResult maybeWhen<TResult extends Object?>({
    TResult Function()? initial,
    TResult Function()? loading,
    TResult Function(String errorMessage)? invalid,
    required TResult orElse(),
  }) {
    if (initial != null) {
      return initial();
    }
    return orElse();
  }

  @override
  @optionalTypeArgs
  TResult map<TResult extends Object?>({
    required TResult Function(_Initial value) initial,
    required TResult Function(_Loading value) loading,
    required TResult Function(_Invalid value) invalid,
  }) {
    return initial(this);
  }

  @override
  @optionalTypeArgs
  TResult? mapOrNull<TResult extends Object?>({
    TResult? Function(_Initial value)? initial,
    TResult? Function(_Loading value)? loading,
    TResult? Function(_Invalid value)? invalid,
  }) {
    return initial?.call(this);
  }

  @override
  @optionalTypeArgs
  TResult maybeMap<TResult extends Object?>({
    TResult Function(_Initial value)? initial,
    TResult Function(_Loading value)? loading,
    TResult Function(_Invalid value)? invalid,
    required TResult orElse(),
  }) {
    if (initial != null) {
      return initial(this);
    }
    return orElse();
  }
}

abstract class _Initial implements SomethingState {
  const factory _Initial() = _$InitialImpl;
}

/// @nodoc
abstract class _$$LoadingImplCopyWith<$Res> {
  factory _$$LoadingImplCopyWith(
          _$LoadingImpl value, $Res Function(_$LoadingImpl) then) =
      __$$LoadingImplCopyWithImpl<$Res>;
}

/// @nodoc
class __$$LoadingImplCopyWithImpl<$Res>
    extends _$SomethingStateCopyWithImpl<$Res, _$LoadingImpl>
    implements _$$LoadingImplCopyWith<$Res> {
  __$$LoadingImplCopyWithImpl(
      _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then)
      : super(_value, _then);
}

/// @nodoc

class _$LoadingImpl implements _Loading {
  const _$LoadingImpl();

  @override
  String toString() {
    return 'SomethingState.loading()';
  }

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        (other.runtimeType == runtimeType && other is _$LoadingImpl);
  }

  @override
  int get hashCode => runtimeType.hashCode;

  @override
  @optionalTypeArgs
  TResult when<TResult extends Object?>({
    required TResult Function() initial,
    required TResult Function() loading,
    required TResult Function(String errorMessage) invalid,
  }) {
    return loading();
  }

  @override
  @optionalTypeArgs
  TResult? whenOrNull<TResult extends Object?>({
    TResult? Function()? initial,
    TResult? Function()? loading,
    TResult? Function(String errorMessage)? invalid,
  }) {
    return loading?.call();
  }

  @override
  @optionalTypeArgs
  TResult maybeWhen<TResult extends Object?>({
    TResult Function()? initial,
    TResult Function()? loading,
    TResult Function(String errorMessage)? invalid,
    required TResult orElse(),
  }) {
    if (loading != null) {
      return loading();
    }
    return orElse();
  }

  @override
  @optionalTypeArgs
  TResult map<TResult extends Object?>({
    required TResult Function(_Initial value) initial,
    required TResult Function(_Loading value) loading,
    required TResult Function(_Invalid value) invalid,
  }) {
    return loading(this);
  }

  @override
  @optionalTypeArgs
  TResult? mapOrNull<TResult extends Object?>({
    TResult? Function(_Initial value)? initial,
    TResult? Function(_Loading value)? loading,
    TResult? Function(_Invalid value)? invalid,
  }) {
    return loading?.call(this);
  }

  @override
  @optionalTypeArgs
  TResult maybeMap<TResult extends Object?>({
    TResult Function(_Initial value)? initial,
    TResult Function(_Loading value)? loading,
    TResult Function(_Invalid value)? invalid,
    required TResult orElse(),
  }) {
    if (loading != null) {
      return loading(this);
    }
    return orElse();
  }
}

abstract class _Loading implements SomethingState {
  const factory _Loading() = _$LoadingImpl;
}

/// @nodoc
abstract class _$$InvalidImplCopyWith<$Res> {
  factory _$$InvalidImplCopyWith(
          _$InvalidImpl value, $Res Function(_$InvalidImpl) then) =
      __$$InvalidImplCopyWithImpl<$Res>;
  @useResult
  $Res call({String errorMessage});
}

/// @nodoc
class __$$InvalidImplCopyWithImpl<$Res>
    extends _$SomethingStateCopyWithImpl<$Res, _$InvalidImpl>
    implements _$$InvalidImplCopyWith<$Res> {
  __$$InvalidImplCopyWithImpl(
      _$InvalidImpl _value, $Res Function(_$InvalidImpl) _then)
      : super(_value, _then);

  @pragma('vm:prefer-inline')
  @override
  $Res call({
    Object? errorMessage = null,
  }) {
    return _then(_$InvalidImpl(
      null == errorMessage
          ? _value.errorMessage
          : errorMessage // ignore: cast_nullable_to_non_nullable
              as String,
    ));
  }
}

/// @nodoc

class _$InvalidImpl implements _Invalid {
  const _$InvalidImpl(this.errorMessage);

  @override
  final String errorMessage;

  @override
  String toString() {
    return 'SomethingState.invalid(errorMessage: $errorMessage)';
  }

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        (other.runtimeType == runtimeType &&
            other is _$InvalidImpl &&
            (identical(other.errorMessage, errorMessage) ||
                other.errorMessage == errorMessage));
  }

  @override
  int get hashCode => Object.hash(runtimeType, errorMessage);

  @JsonKey(ignore: true)
  @override
  @pragma('vm:prefer-inline')
  _$$InvalidImplCopyWith<_$InvalidImpl> get copyWith =>
      __$$InvalidImplCopyWithImpl<_$InvalidImpl>(this, _$identity);

  @override
  @optionalTypeArgs
  TResult when<TResult extends Object?>({
    required TResult Function() initial,
    required TResult Function() loading,
    required TResult Function(String errorMessage) invalid,
  }) {
    return invalid(errorMessage);
  }

  @override
  @optionalTypeArgs
  TResult? whenOrNull<TResult extends Object?>({
    TResult? Function()? initial,
    TResult? Function()? loading,
    TResult? Function(String errorMessage)? invalid,
  }) {
    return invalid?.call(errorMessage);
  }

  @override
  @optionalTypeArgs
  TResult maybeWhen<TResult extends Object?>({
    TResult Function()? initial,
    TResult Function()? loading,
    TResult Function(String errorMessage)? invalid,
    required TResult orElse(),
  }) {
    if (invalid != null) {
      return invalid(errorMessage);
    }
    return orElse();
  }

  @override
  @optionalTypeArgs
  TResult map<TResult extends Object?>({
    required TResult Function(_Initial value) initial,
    required TResult Function(_Loading value) loading,
    required TResult Function(_Invalid value) invalid,
  }) {
    return invalid(this);
  }

  @override
  @optionalTypeArgs
  TResult? mapOrNull<TResult extends Object?>({
    TResult? Function(_Initial value)? initial,
    TResult? Function(_Loading value)? loading,
    TResult? Function(_Invalid value)? invalid,
  }) {
    return invalid?.call(this);
  }

  @override
  @optionalTypeArgs
  TResult maybeMap<TResult extends Object?>({
    TResult Function(_Initial value)? initial,
    TResult Function(_Loading value)? loading,
    TResult Function(_Invalid value)? invalid,
    required TResult orElse(),
  }) {
    if (invalid != null) {
      return invalid(this);
    }
    return orElse();
  }
}

abstract class _Invalid implements SomethingState {
  const factory _Invalid(final String errorMessage) = _$InvalidImpl;

  String get errorMessage;
  @JsonKey(ignore: true)
  _$$InvalidImplCopyWith<_$InvalidImpl> get copyWith =>
      throw _privateConstructorUsedError;
}

Is it possible to use genq to do the same?

Not Dart compatible

Error when parsing: lib\features\auth\entities\app_theme.dart:13

final class AppTheme with _$AppTheme implements ISerializable, ICopyWith {
^
Unexpected token: FINAL (final). Expected CLASS.
✅ Generated 0 data classes

Dart is not JavaScript, we have final, abstract interface, abstract base and sealed classes.

Support for const factory

Awesome package! Could you adjust it to support const factory constructors?

Ex:

@Genq(json: true)
class User with _$User {
  const factory User({
    @JsonKey(
      fromJson: UserName.fromJson,
      toJson: UserName.toJson,
    )
    required UserName name,
    required int age,
  }) = _User;
}

Thanks!

Regenerate the part classes when the files are saved

It's basically a file watcher detecting when a file contains the @genq annotations is saved.

I think it would improve the UX developer avoiding us to constantly switch between the Flutter bash and the genq bash

JSON deserialization bug: double and int should be deserialized as num

Some languages will output json for doubles in a format that will break the current way genq is handling doubles and ints.

Ex:

{
     "someDoubleValue": 30.4,
     "anotherDoubleValue": 30,
     "someIntValue": 30,
}

This will break the deserialization when trying to use a double with "anotherDoubleValue".

Instead of generating the following
bodyFontSize: json['anotherDoubleValue'] == null ? null : json['anotherDoubleValue'] as double?

Follow JsonSerializableGenerator's lead on this and do
(json['anotherDoubleValue'] as num?)?.toDouble()

Support sealed classes

One change that would make genq an excellent freezed replacement would be to support unions with sealed classes.
Example

@genq
sealed class HomeState with _$HomeState {
  const factory HomeState.loading() = LoadingState;
  const factory HomeState.loaded(String data) = LoadedState;
  const factory HomeState.error(String message) = ErrorState;
}

It would be great if this was able to generate a sealed classes that could be used with dart's pattern matching. Here is more details on how this would be useful:
https://medium.com/@aliammariraq/sealed-classes-in-dart-unlocking-powerful-features-d8dba185925f

Is there anyway to support a partial migration from freezed to genq

I work in a very big project and would like to eventually switch most of our data models with a few exceptions from freezed to genq.

However I am running into an issue with json serialization where all properties of genq data models must also be genq models. In a very big project this makes migrating difficult because it will be a big lift to migrate every single data model and even then there are models that need to stay in freezed.

Example:

@Genq(json: true)
class SomeGenqClass with _$SomeGenqClass {
  factory SomeGenqClass({
    required String value,
    required SomeFreezedClass someClass,
  }) = _SomeGenqClass;
}

@freezed
class SomeFreezedClass with _$SomeFreezedClass {
  const factory SomeFreezedClass({
    required String value,
  }) = _SomeFreezedClass;

  factory SomeFreezedClass.fromJson(Map<String, dynamic> json) =>
      _$SomeFreezedClassFromJson(json);
}

Gives an error that The function '$SomeFreezedClassFromJson' isn't defined.

Is there a way around this to make the migration easier?

Thanks

Add `JsonSerializable`-like annotation

A lot of existing dart code bases use vanilla json_serializable & json_annotation (https://pub.dev/packages/json_serializable), without any freezed. For classes marked with the annotation, the fields within the class will be considered for generating the serialization/deserialization code.

We could offer such an annotation, which is almost identical to the annotation from json_serializable (maybe @GenqJsonSerializable()). This should make migrations relatively trivial.

A limitation might be that genq has no ability to resolve inherited fields.

[Question] Any best practices for parsing types like DateTime?

I read through the readme and I'm guessing using the @JsonKey() annotation with fromJson and toJson is the way to parse it.

I did something like this:

//Sample model
@Genq(json: true)
class MyModel with _$MyModel {
  factory MyModel({
    @JsonKey(
      fromJson: GenqDateTime.fromJson,
      toJson: GenqDateTime.toJson,
    )
    required GenqDateTime someDate,
  }) = _MyModel ;
}

//toJson fromJson
class GenqDateTime {
  final DateTime value;

  GenqDateTime(this.value);

  static GenqDateTime fromJson(String value) {
    final dateTime = DateTime.parse(value);
    return GenqDateTime(dateTime);
  }

  static String toJson(GenqDateTime enclosedDt) {
    return enclosedDt.value.toIso8601String();
  }
}

I'm just wondering if this is the right way to go about it? I use freezed normally so I got used to how it auto parses Strings into DateTime objects and just wondering if there's a way to do it without having to have a class enclosing a DateTime

I wanted to make the question more broad for other types but the only type I can think of is DateTime.

Edit: updated sample code

Add a model generator

Hi!

I just tried your tool and it's very great! I was wondering if it was possible to add a generator command line like in Ruby on Rails.

It would be something like genq generate User to generate the user.dart file with the basic template like in your README.

It would be useful to have this feature because there's no VSCode extension or snippets for that.

Possible to add a watcher?

Similar to how build_runner has a watch to rebuild when files change, is there something similar for genq?

Editor integrations

It would be nice if genq could run in editors when saving a file.

Integrations would be good for:

  • Visual Studio Code
  • Android Studio

Since genq is sufficiently fast, it should be possible to run genq before a file is saved, thus making the experience with Hot Reload/Hot Restart seamless.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.