json_serializable

repository·master·Indexed 23 days ago

https://github.com/google/json_serializable.dart

A collection of Dart Build System builders designed to automate JSON serialization and deserialization through code generation. It works with the json_annotation package to generate toJson and fromJson methods, supports JSON Schema generation, and provides tools for custom type encoding via JsonConverter. The repository also includes checked_yaml for improved error reporting when decoding YAML into classes annotated for json_serializable.

Tokens
9.2K
Snippets
21
Records
51
Agent score
78%

What's inside json_serializable

  1. Use json_annotation to define serialization metadata

    master
    The json_annotation package provides the necessary annotations that json_serializable uses to generate JSON serialization and deserialization code. While json_serializable is the code generator, you must include json_annotation in your project to use the annotations (like @JsonSerializable) in your source code.
  2. Use checked_yaml for better YAML parsing error messages

    master

    The package:checked_yaml provides the checkedYamlDecode function, which is designed to work with classes annotated for package:json_serializable.

    When decoding YAML into a class, standard exceptions can be difficult to map back to the source file. checkedYamlDecode catches CheckedFromJsonException (thrown when checked: true is used in json_serializable) and wraps them in a ParsedYamlException. This exception includes the specific line and column number from the input YAML where the error occurred, making debugging much easier.

    final config = checkedYamlDecode(
      yamlContent,
      (m) => Configuration.fromJson(m!),
      sourceUrl: sourceUri,
    );
  3. Configure JSON serialization precedence

    master

    You can control how code is generated using three methods, which follow a specific precedence order:

    1. @JsonKey: Properties set on a specific field. This has the highest precedence.
    2. @JsonSerializable: Properties set on the class level. This takes precedence over build.yaml.
    3. build.yaml: Global configuration for the package. This has the lowest precedence.

    If a property is defined in both @JsonKey and @JsonSerializable, the @JsonKey value is used.

  4. Configure code generation precedence

    master

    You can control how code is generated using three methods, which follow a specific order of precedence:

    1. ja:JsonKey: Properties set on the annotation of a specific target field have the highest precedence.
    2. ja:JsonSerializable: Properties set on the annotation of the target class take precedence over global settings.
    3. build.yaml: Global configuration settings in your build.yaml file have the lowest precedence.

    If a property is set in both ja:JsonKey and ja:JsonSerializable, the ja:JsonKey value wins.

  5. Install json_serializable and json_annotation

    master

    To use json_serializable in your Dart or Flutter project, add json_annotation to your dependencies and add both build_runner and json_serializable to your dev_dependencies in pubspec.yaml.

    dependencies:
      json_annotation: ^4.9.0
    
    dev_dependencies:
      build_runner: ^2.10.5
      json_serializable: ^6.10.0
  6. Configure json_serializable for checked YAML decoding

    master

    To use checked_yaml effectively, your target class must be configured with specific json_serializable annotations:

    1. Set anyMap: true in the @JsonSerializable annotation to allow the generated code to parse YamlMap types from package:yaml.
    2. Set checked: true in the @JsonSerializable annotation so that decoding errors are wrapped in CheckedFromJsonException.
    3. (Optional) Use disallowUnrecognizedKeys: true to ensure strictness.
    4. Use @JsonKey(required: true) for mandatory fields to trigger validation errors during decoding.
    @JsonSerializable(anyMap: true, checked: true, disallowUnrecognizedKeys: true)
    class Configuration {
      @JsonKey(required: true)
      final String name;
      final int count;
    
      Configuration({required this.name, required this.count}) {
        if (name.isEmpty) {
          throw ArgumentError.value(name, 'name', 'Cannot be empty.');
        }
      }
    
      factory Configuration.fromJson(Map json) => _$ConfigurationFromJson(json);
    
      Map<String, dynamic> toJson() => _$ConfigurationToJson(this);
    }
  7. How to use json_serializable for JSON serialization

    master

    To generate toJson and fromJson code for a class, annotate it with @JsonSerializable. You must also include a part directive pointing to the generated file (e.g., part 'filename.g.dart';) and manually connect the generated functions to your class's factory constructor and toJson method.

    You can customize individual fields using the @JsonKey annotation.

    import 'package:json_annotation/json_annotation.dart';
    
    part 'example.g.dart';
    
    @JsonSerializable(createJsonSchema: true)
    class Person {
      final String firstName, lastName;
      final DateTime? dateOfBirth;
    
      Person({required this.firstName, required this.lastName, this.dateOfBirth});
    
      factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
    
      Map<String, dynamic> toJson() => _$PersonToJson(this);
    
      static const jsonSchema = _$PersonJsonSchema;
    }
  8. Handle custom types and custom encoding

    master

    If a type is not supported out-of-the-box, use one of these three strategies:

    1. Add fromJson/toJson to the type: If you control the type, add a fromJson constructor (taking Map<String, dynamic> or other JSON-compatible types) and a toJson method. The generator will automatically use them.
    2. Use @JsonKey(fromJson: ..., toJson: ...): Specify top-level or static functions for custom conversion on a specific field.
    3. Implement JsonConverter: Create a class implementing JsonConverter<T, S> (where T is the Dart type and S is the JSON type). This is ideal for reusable logic or supporting types within collections.

    Example using JsonConverter:

    @JsonSerializable()
    class Sample4 {
      Sample4(this.value);
    
      factory Sample4.fromJson(Map<String, dynamic> json) => _$Sample4FromJson(json);
    
      @EpochDateTimeConverter()
      final DateTime value;
    
      Map<String, dynamic> toJson() => _$Sample4ToJson(this);
    }
    
    class EpochDateTimeConverter implements JsonConverter<DateTime, int> {
      const EpochDateTimeConverter();
    
      @override
      DateTime fromJson(int json) => DateTime.fromMillisecondsSinceEpoch(json);
    
      @override
      int toJson(DateTime object) => object.millisecondsSinceEpoch;
    }
    class EpochDateTimeConverter implements JsonConverter<DateTime, int> {
      const EpochDateTimeConverter();
    
      @override
      DateTime fromJson(int json) => DateTime.fromMillisecondsSinceEpoch(json);
    
      @override
      int toJson(DateTime object) => object.millisecondsSinceEpoch;
    }