generator topic

Code generation tools for JSON (de)serialization boilerplate. All generators use AST objects as input. The analyzer can read metadata or .dart files and return it as AST objects. Generators can then be used to use these objects to generate boilerplate code.

Tools:

Data Class

Using data classes instead of raw JSON data is beneficial because of the enforced type safety. Writing one is a repetitive and mostly boring task. Would it not be nice to generate a simple class based on a JSON String? Squint can do just that.

Example:

Given the following JSON in file foo/bar/example.json:

{
  "id": 1,
  "isJedi": true,
  "hasPadawan": false,
  "bff": "Leia",
  "jedi": [
      "Obi-Wan", 
      "Anakin", 
      "Luke Skywalker"
  ],
  "coordinates": [
      22, 
      4.4, 
      -15
  ],
  "objectives": {
      "in-mission": false,
      "mission-results": [
          false, 
          true, 
          true, 
          false
      ]
  },
  "annoyance-rate": [
      { "JarJarBinks" : 9000 }
  ],
  "foo": null
}

Using the cli command:

flutter pub run squint_json:generate --type dataclass --input foo/bar/example.json

Should generate the following code:

// Copyright (c) 2021 - 2023 Buijs Software
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import 'package:squint_json/squint_json.dart';

/// Autogenerated data class by Squint.
@squint
class example {
  const example({
    required this.id,
    required this.isJedi,
    required this.hasPadawan,
    required this.bff,
    required this.jedi,
    required this.coordinates,
    required this.objectives,
    required this.annoyanceRate,
    required this.foo,
  });

  @JsonValue("id")
  final int id;

  @JsonValue("isJedi")
  final bool isJedi;

  @JsonValue("hasPadawan")
  final bool hasPadawan;

  @JsonValue("bff")
  final String bff;

  @JsonValue("jedi")
  final List<String> jedi;

  @JsonValue("coordinates")
  final List<double> coordinates;

  @JsonEncode(using: encodeObjectives)
  @JsonDecode<Objectives, JsonObject>(using: decodeObjectives)
  @JsonValue("objectives")
  final Objectives objectives;

  @JsonValue("annoyance-rate")
  final List<Map<String, int>> annoyanceRate;

  @JsonValue("foo")
  final dynamic foo;
}

@squint
class Objectives {
  const Objectives({
    required this.inMission,
    required this.missionResults,
  });

  @JsonValue("in-mission")
  final bool inMission;

  @JsonValue("mission-results")
  final List<bool> missionResults;
}

JsonObject encodeObjectives(Objectives objectives) =>
        JsonObject.fromNodes(key: "objectives", nodes: [
          JsonBoolean(key: "in-mission", data: objectives.inMission),
          JsonArray<dynamic>(
                  key: "mission-results", data: objectives.missionResults),
        ]);

Objectives decodeObjectives(JsonObject object) => Objectives(
  inMission: object.boolean("in-mission"),
  missionResults: object.array<bool>("mission-results"),
);

The following boolean parameters can be used to customize the generated code:

blankLineBetweenFields

Generate a data class for a JSON File and save the output in folder foo/bar. Default value is true.

Example when true:

@squint
class example {
  const example({
    required this.id,
    required this.isJedi,
    required this.hasPadawan,
    required this.bff,
    required this.jedi,
    required this.coordinates,
    required this.objectives,
    required this.annoyanceRate,
    required this.foo,
  });

  @JsonValue("id")
  final int id;

  @JsonValue("isJedi")
  final bool isJedi;

  @JsonValue("hasPadawan")
  final bool hasPadawan;

  @JsonValue("bff")
  final String bff;

  @JsonValue("jedi")
  final List<String> jedi;

  @JsonValue("coordinates")
  final List<double> coordinates;

  @JsonEncode(using: encodeObjectives)
  @JsonDecode<Objectives, JsonObject>(using: decodeObjectives)
  @JsonValue("objectives")
  final Objectives objectives;

  @JsonValue("annoyance-rate")
  final List<Map<String, int>> annoyanceRate;

  @JsonValue("foo")
  final dynamic foo;
}

Example when false:

@squint
class example {
  const example({
    required this.id,
    required this.isJedi,
    required this.hasPadawan,
    required this.bff,
    required this.jedi,
    required this.coordinates,
    required this.objectives,
    required this.annoyanceRate,
    required this.foo,
  });

  @JsonValue("id")
  final int id;
  @JsonValue("isJedi")
  final bool isJedi;
  @JsonValue("hasPadawan")
  final bool hasPadawan;
  @JsonValue("bff")
  final String bff;
  @JsonValue("jedi")
  final List<String> jedi;
  @JsonValue("coordinates")
  final List<double> coordinates;
  @JsonEncode(using: encodeObjectives)
  @JsonDecode<Objectives, JsonObject>(using: decodeObjectives)
  @JsonValue("objectives")
  final Objectives objectives;
  @JsonValue("annoyance-rate")
  final List<Map<String, int>> annoyanceRate;
  @JsonValue("foo")
  final dynamic foo;
}

Example when false with --alwaysAddJsonValue also set to false:

@squint
class example {
  const example({
    required this.id,
    required this.isJedi,
    required this.hasPadawan,
    required this.bff,
    required this.jedi,
    required this.coordinates,
    required this.objectives,
    required this.annoyanceRate,
    required this.foo,
  });
  
  final int id;
  final bool isJedi;
  final bool hasPadawan;
  final String bff;
  final List<String> jedi;
  final List<double> coordinates;
  @JsonEncode(using: encodeObjectives)
  @JsonDecode<Objectives, JsonObject>(using: decodeObjectives)
  final Objectives objectives;
  @JsonValue("annoyance-rate")
  final List<Map<String, int>> annoyanceRate;
  final dynamic foo;
}

alwaysAddJsonValue

Generate a data class and add @JsonValue annotation to each field. By default, @JsonValue will only be added to fields which have a different name in the JSON String and in the data class. For example annoyance-rate in JSON will be generated as annoyanceRate in the data class with annotation @JsonValue("annoyance-rate").

flutter pub run squint_json:generate --type dataclass --input foo/example.json --alwaysAddJsonValue true
@squint
class example {
  const example({
    required this.id,
    required this.isJedi,
    required this.hasPadawan,
    required this.bff,
    required this.jedi,
    required this.coordinates,
    required this.objectives,
    required this.annoyanceRate,
    required this.foo,
  });

  @JsonValue("id")
  final int id;

  @JsonValue("isJedi")
  final bool isJedi;

  @JsonValue("hasPadawan")
  final bool hasPadawan;

  @JsonValue("bff")
  final String bff;

  @JsonValue("jedi")
  final List<String> jedi;

  @JsonValue("coordinates")
  final List<double> coordinates;

  @JsonEncode(using: encodeObjectives)
  @JsonDecode<Objectives, JsonObject>(using: decodeObjectives)
  @JsonValue("objectives")
  final Objectives objectives;

  @JsonValue("annoyance-rate")
  final List<Map<String, int>> annoyanceRate;

  @JsonValue("foo")
  final dynamic foo;
}

Example when false:

@squint
class example {
  const example({
    required this.id,
    required this.isJedi,
    required this.hasPadawan,
    required this.bff,
    required this.jedi,
    required this.coordinates,
    required this.objectives,
    required this.annoyanceRate,
    required this.foo,
  });
  
  final int id;

  final bool isJedi;
  
  final bool hasPadawan;
  
  final String bff;
  
  final List<String> jedi;
  
  final List<double> coordinates;

  @JsonEncode(using: encodeObjectives)
  @JsonDecode<Objectives, JsonObject>(using: decodeObjectives)
  final Objectives objectives;

  @JsonValue("annoyance-rate")
  final List<Map<String, int>> annoyanceRate;
  
  final dynamic foo;
}

Example when false with --blankLineBetweenFields also set to false:

@squint
class example {
  const example({
    required this.id,
    required this.isJedi,
    required this.hasPadawan,
    required this.bff,
    required this.jedi,
    required this.coordinates,
    required this.objectives,
    required this.annoyanceRate,
    required this.foo,
  });
  
  final int id;
  final bool isJedi;
  final bool hasPadawan;
  final String bff;
  final List<String> jedi;
  final List<double> coordinates;
  @JsonEncode(using: encodeObjectives)
  @JsonDecode<Objectives, JsonObject>(using: decodeObjectives)
  final Objectives objectives;
  @JsonValue("annoyance-rate")
  final List<Map<String, int>> annoyanceRate;
  final dynamic foo;
}

includeJsonAnnotations

Set includeJsonAnnotations as false, to generate a class without any annotations.

flutter pub run squint_json:generate --type dataclass --input foo/example.json --includeJsonAnnotations false
@squint
class example {
  const example({
    required this.id,
    required this.isJedi,
    required this.hasPadawan,
    required this.bff,
    required this.jedi,
    required this.coordinates,
    required this.objectives,
    required this.annoyanceRate,
    required this.foo,
  });
  
  final int id;
  
  final bool isJedi;
  
  final bool hasPadawan;
  
  final String bff;
  
  final List<String> jedi;
  
  final List<double> coordinates;
  
  final Objectives objectives;
  
  final List<Map<String, int>> annoyanceRate;
  
  final dynamic foo;
}

Squint will normalize JSON keys to be valid dart field names. If --includeJsonAnnotations is set to false then the generated code might not be compatible with the JSON file.

Serialization

Even though (de)serialization is simplified by the Squint decoder/encoder, it still requires some amount of boilerplate to be written. Thankfully this code can be generated.

Requirements:

  • Class is annotated with @squint.
  • There is one constructor.
  • All fields to be (de)serialized are present in said constructor.
  • All fields are either:
    • of type:
      • String
      • int
      • double
      • bool
      • List
      • Map
    • or annotated with both:
      • @JsonEncode
      • @JsonDecode

For configuring JSON property names, fields can be annotated with @JsonValue.

Example:

Given the following dart class in file foo/bar/testing_example.dart:

@squint
class TestingExample {
  const TestingExample({
    required this.foo,
    required this.isOk,
    required this.random,
    required this.multiples,
    required this.counters,
  });

  final String foo;
  final bool isOk;
  final List<double> random;
  final List<List<String>> multiples;
  final Map<String, double> counters;
}

Using the cli command:

flutter pub run squint_json:generate --type serializer --input foo/bar/testing_example.dart

Should generate the following code:

// Copyright (c) 2021 - 2023 Buijs Software
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import 'package:squint_json/squint_json.dart';
import 'testing_example.dart';

/// Autogenerated JSON (de)serialization methods by Squint.
extension TestingExampleJsonBuilder on TestingExample {
  JsonObject get toJsonObject => JsonObject.fromNodes(nodes: [
    JsonString(key: "foo", data: foo),
    JsonBoolean(key: "isOk", data: isOk),
    JsonArray<dynamic>(key: "random", data: random),
    JsonArray<dynamic>(key: "multiples", data: multiples),
    JsonObject.fromMap(key: "counters", data: counters),
  ]);

  String get toJson => toJsonObject.stringify;
}

extension TestingExampleJsonString2Class on String {
  TestingExample get toTestingExample => jsonDecode.toTestingExample;
}

extension TestingExampleJsonObject2Class on JsonObject {
  TestingExample get toTestingExample => TestingExample(
    foo: string("foo"),
    isOk: boolean("isOk"),
    random: array<double>("random"),
    multiples: array<List<String>>("multiples"),
    counters: object("counters"),
  );
}

Example with annotations:

Given the following dart class:

import 'package:squint_json/squint_json.dart';

@squint
class TestingExample {
  const TestingExample({
    required this.foo,
    required this.isOk,
    required this.random,
    required this.multiples,
    required this.counters,
    required this.noIdea,
  });

  final String foo;
  final bool isOk;
  final List<double> random;
  final List<List<String>> multiples;
  final Map<String, double> counters;

  @JsonDecode<int, JsonFloatingNumber>(using: decodeId)
  @JsonEncode(using: encodeId)
  @JsonValue("id")
  final int noIdea;
}

JsonFloatingNumber encodeId(int id) =>
        JsonFloatingNumber(key: "id", data: id.toDouble());

int decodeId(JsonFloatingNumber id) =>
        id.data.toInt();

Using the cli command:

flutter pub run squint_json:generate --type serializer --input foo/bar/testing_example.dart

Should generate the following code:

// Copyright (c) 2021 - 2023 Buijs Software
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import 'package:squint_json/squint_json.dart';
import 'testing_example.dart';

/// Autogenerated JSON (de)serialization methods by Squint.
extension TestingExampleJsonBuilder on TestingExample {
  JsonObject get toJsonObject => JsonObject.fromNodes(nodes: [
    JsonString(key: "foo", data: foo),
    JsonBoolean(key: "isOk", data: isOk),
    JsonArray<dynamic>(key: "random", data: random),
    JsonArray<dynamic>(key: "multiples", data: multiples),
    JsonObject.fromMap(key: "counters", data: counters),
    encodeId(noIdea),
  ]);

  String get toJson => toJsonObject.stringify;
}

extension TestingExampleJsonString2Class on String {
  TestingExample get toTestingExample => jsonDecode.toTestingExample;
}

extension TestingExampleJsonObject2Class on JsonObject {
  TestingExample get toTestingExample => TestingExample(
    foo: string("foo"),
    isOk: boolean("isOk"),
    random: array<double>("random"),
    multiples: array<List<String>>("multiples"),
    counters: object("counters"),
    noIdea: decodeId(floatNode("id")),
  );
}

Classes

SquintGeneratorOptions generator
Options to configure code generation.

Extensions

CustomType2DataClass on CustomType generator
Convert a CustomType to a data class.
EnumType2EnumClass on EnumType generator
Convert a EnumType to an enum class.
JsonConversionExtensionsGenerator on CustomType generator
Generate JSON (de)serialization methods for a dart class CustomType.
JsonNodeEnumGenerator on EnumType generator
Utilities to generate getters/setters for enum classes and serializers.
JsonNodeGenerator on List<TypeMember> generator
Utilities to generate getter/setters for dataclasses and serializers.
JsonValueExtensionsGenerator on EnumType generator
Generate JSON (de)serialization methods for an enum class EnumType.
SquintGeneratorOptionsBuilder on SquintGeneratorOptions generator
Builder to customize SquintGeneratorOptions without building an instance from scratch.

Constants

noAnnotationsGeneratorOptions → const SquintGeneratorOptions generator
Generate data class without annotations.
standardSquintGeneratorOptions → const SquintGeneratorOptions generator
Default code generation options.