18

Using the source_gen stack to make a code generator, how can I make a generator that generates code that would be the input of another generator (more specifically json_serializable)?

For example, consider:

class Example extends Generator {
  @override
  String generate(LibraryReader library, BuildStep buildStep) {
    return '''
@JsonSerializable(nullable: false)
class Person {
  final String firstName;
  final String lastName;
  final DateTime dateOfBirth;
  Person({this.firstName, this.lastName, this.dateOfBirth});
  factory Person.fromJson(Map<String, dynamic> json) => _PersonFromJson(json);
  Map<String, dynamic> toJson() => _PersonToJson(this);
}
''';
  }
}

This is an example of a code-generator that output code which then needs to be sent to json_serializable

What can I do so that json_serializable correctly generates here?

Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • I don't have an answer but [this link](https://pub.dev/packages/build_config) might point you in a good direction? I'm also interested. I had bookmarked that link to do further research – Frank Treacy Dec 27 '19 at 23:15
  • I am halfway through to understand the case for your question. can you elaborate more? there is two way to solve this either by calling the actual method which outputs the part file of that JSON or by using a manual step to configure and invoke part builder our self. There is third way to run the command using dart ```await Process.start('bash',arguments,runInShell: true);``` but it's kind of a last resort to play. actually I just tried all code generation logics one day ago on this repo [link](https://github.com/parthdave93/FlutterRouteGen) so.. I think I can be helpful. – Parth Dave Jan 02 '20 at 10:12
  • It's about composing code generator, such that I can write one that depends on another one. This way I won't have to fork its sources for maintainability purpose. – Rémi Rousselet Jan 02 '20 at 10:48
  • The generation step must work in a single `flutter generate`/`pub run build_runner build`. Otherwise it'd be very unusual to use. – Rémi Rousselet Jan 02 '20 at 10:49

3 Answers3

5

Check the build.yaml config file documentation for more info, but I think you should use the applies_builders param that allows to execute another build after the defined one.

The example shows a builder that generates .tar.gz files and then executes another build that takes the .tar.gz files as input

builders:
  # The regular builder config, creates .tar.gz files.
  regular_builder:
    import: "package:my_package/builder.dart"
    builder_factories: ["myBuilder"]
    build_extensions: {".dart": [".tar.gz"]}
    auto_apply: dependents
    apply_builders: [":archive_extract_builder"]
post_process_builders:
  # The post process builder config, extracts .tar.gz files.
  extract_archive_builder:
    import: "package:my_package/extract_archive_builder.dart"
    builder_factory: "myExtractArchiveBuilder"
    input_extensions: [".tar.gz"]

so with source_gen you should implement for your build

applies_builders: ["source_gen|combining_builder", "json_serializable"]

and configure the other builder

json_serializable:
    import: "package:json_serializable/builder.dart"
    builder_factories: ["jsonSerializable"]
    build_extensions: {".dart": ["json_serializable.g.part"]}
    auto_apply: dependents
    build_to: cache
    applies_builders: ["source_gen|combining_builder"]
jamesblasco
  • 1,744
  • 1
  • 9
  • 25
  • What I should pass in the [apply_builders] property? – Pedro Massango Mar 19 '20 at 10:20
  • 1
    can you show a complete example of a `build.yaml` file that runs that configures `json_serializable` to run after a builder? – Eyal Kutz Jul 15 '21 at 09:44
  • This doesn't work for me – zigzag Aug 25 '23 at 10:09
  • It didn't work because I was using `SharedPartBuilder` and no `SharedPartBuilder` is able to resolve the Dart code emitted by another `SharedPartBuilder`. https://stackoverflow.com/questions/76976480/how-to-run-json-serializable-over-output-of-another-builder – zigzag Aug 25 '23 at 16:50
2

It's not possible just with annotation because there maybe two packages that both have the @JsonSerializable annotation

There are two situtations :

  • You know what other generators should run after your generator.


class Example extends Generator {
    @override
    String generate(LibraryReader library, BuildStep buildStep) {
      return JsonSerializable().generate('''
          @JsonSerializable(nullable: false)
          class Person {
            final String firstName;
            final String lastName;
            final DateTime dateOfBirth;
            Person({this.firstName, this.lastName, this.dateOfBirth});
            factory Person.fromJson(Map<String, dynamic> json) => _PersonFromJson(json);
            Map<String, dynamic> toJson() => _PersonToJson(this);
          }
        ''');
     }

}

  • You don't know what other generators should run after your generator.

Unfortunately currently there is no way to tell the source_gen that your generator may produce a code that needs code generation.

I created an issue here https://github.com/dart-lang/source_gen/issues/442 if you want to subscribe

Sahandevs
  • 1,120
  • 9
  • 25
  • I'm trying to replicate your example code, but 1) `JsonSerializable` class is not a generator and 2) `JsonSerializableGenerator.generate()` doesn't take `String` as an input, but `(LibraryReader library, BuildStep buildStep)` – zigzag Aug 25 '23 at 14:50
-2

You can decode the JSON by calling the jsonDecode() function, with the JSON string as the method argument.

Map<String, dynamic> user = jsonDecode(jsonString);

print('Howdy, ${user['name']}!');
print('We sent the verification link to ${user['email']}.');

Now, Use the User.fromJson() constructor, for constructing a new User instance from a map structure and a toJson() method, which converts a User instance into a map.

employee.dart

class Employee {
  final String name;
  final String id;

  Employee(this.name, this.id);

  Employee.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        id = json['id'];

  Map<String, dynamic> toJson() =>
    {
      'name': name,
      'id': id,
    };
}

json_serializable is an automated source code generator that generates the JSON serialization boilerplate for you.

You need one regular dependency, and two dev dependencies to include json_serializable in your project.

dependencies:
  json_annotation: ^0.2.3

dev_dependencies:
  build_runner: ^0.8.0
  json_serializable: ^0.5.0

For more details on JSON serialization you can refer here

you can also use the Smoke library.

It's a subset of the Mirrors functionality but has both a Mirrors-based and a Codegen-based implementation. It's written by the PolymerDart team, so it's as close to "Official" as we're going to get.

While developing, it'll use the Mirrors-based encoding/decoding; but for publishing you can create a small transformer that will generate code.

Seth Ladd created a code sample here, which I extended slightly to support child-objects:

Sreeram Nair
  • 2,369
  • 12
  • 27