Skip to content

Added createJsonKeys #1401

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 3, 2024
16 changes: 16 additions & 0 deletions json_annotation/lib/src/json_serializable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@ class JsonSerializable {
/// such as [fieldRename].
final bool? createFieldMap;

/// If `true` (defaults to false), a private class `_$ExampleJsonKeys`
/// constant is created in the generated part file.
///
/// This class will contain every property, with the json key as value,
/// exposing a secured way to access the json key from the property.
///
/// ```dart
/// @JsonSerializable(createJsonKeys: true)
/// class Example {
/// // ...
/// static const jsonKeys = _$PublicationImplJsonKeys();
/// }
/// ```
final bool? createJsonKeys;

/// If `true` (defaults to false), a private, static `_$ExamplePerFieldToJson`
/// abstract class will be generated in the part file.
///
Expand Down Expand Up @@ -247,6 +262,7 @@ class JsonSerializable {
this.checked,
this.constructor,
this.createFieldMap,
this.createJsonKeys,
this.createFactory,
this.createToJson,
this.disallowUnrecognizedKeys,
Expand Down
8 changes: 6 additions & 2 deletions json_annotation/lib/src/json_serializable.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

160 changes: 81 additions & 79 deletions json_serializable/README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
<!-- This content is generated. See tool/readme/readme_template.md -->
[![Pub Package](https://img.shields.io/pub/v/json_serializable.svg)](https://pub.dev/packages/json_serializable)

Provides [Dart Build System] builders for handling JSON.
Provides [Dart Build System][Dart Build System] builders for handling JSON.

The builders generate code when they find members annotated with classes defined
in [package:json_annotation].
in [package:json_annotation][package:json_annotation].

- To generate to/from JSON code for a class, annotate it with
[`JsonSerializable`]. You can provide arguments to [`JsonSerializable`] to
configure the generated code. You can also customize individual fields by
annotating them with [`JsonKey`] and providing custom arguments. See the
table below for details on the [annotation values](#annotation-values).

- To generate a Dart field with the contents of a file containing JSON, use the
[`JsonLiteral`] annotation.

## Setup

To configure your project for the latest released version of
`json_serializable`, see the [example].
`json_serializable`, see the [example][example].

## Example

Expand Down Expand Up @@ -85,8 +84,8 @@ code will be generated when you build. There are three ways to control how code
is generated:

1. Setting properties on [`JsonKey`] annotating the target field.
1. Set properties on [`JsonSerializable`] annotating the target type.
1. Add configuration to `build.yaml` – [see below](#build-configuration).
2. Set properties on [`JsonSerializable`] annotating the target type.
3. Add configuration to `build.yaml` – [see below](#build-configuration).

Every [`JsonSerializable`] field is configurable via `build.yaml`. If you find
you want all or most of your classes with the same configuration, it may be
Expand All @@ -106,7 +105,7 @@ Annotate `enum` types with [`JsonEnum`] (new in `json_annotation` 4.2.0) to:
1. Specify the default rename logic for each enum value using `fieldRename`. For
instance, use `fieldRename: FieldRename.kebab` to encode `enum` value
`noGood` as `"no-good"`.
1. Force the generation of the `enum` helpers, even if the `enum` is not
2. Force the generation of the `enum` helpers, even if the `enum` is not
referenced in code. This is an edge scenario, but useful for some.

Annotate `enum` values with [`JsonValue`] to specify the encoded value to map
Expand Down Expand Up @@ -147,16 +146,20 @@ enum StatusCodeEnhanced {

Out of the box, `json_serializable` supports many common types in the
[dart:core](https://api.dart.dev/stable/dart-core/dart-core-library.html)
library:
library:

[`BigInt`], [`bool`], [`DateTime`], [`double`], [`Duration`], [`Enum`], [`int`],
[`Iterable`], [`List`], [`Map`], [`num`], [`Object`], [`Record`], [`Set`],
[`String`], [`Uri`]

The collection types –

[`Iterable`], [`List`], [`Map`], [`Record`], [`Set`]

– can contain values of all the above types.

For [`Map`], the key value must be one of

[`BigInt`], [`DateTime`], [`Enum`], [`int`], [`Object`], [`String`], [`Uri`]

# Custom types and custom encoding
Expand All @@ -169,92 +172,90 @@ customize the encoding/decoding of any type, you have a few options.
for these types, you don't have to! The generator code only looks for these
methods. It doesn't care how they were created.

```dart
@JsonSerializable()
class Sample1 {
Sample1(this.value);

factory Sample1.fromJson(Map<String, dynamic> json) =>
_$Sample1FromJson(json);
```dart
@JsonSerializable()
class Sample1 {
Sample1(this.value);

// Sample2 is NOT annotated with @JsonSerializable(), but that's okay
// The class has a `fromJson` constructor and a `toJson` method, which is
// all that is required.
final Sample2 value;
factory Sample1.fromJson(Map<String, dynamic> json) =>
_$Sample1FromJson(json);

Map<String, dynamic> toJson() => _$Sample1ToJson(this);
}
// Sample2 is NOT annotated with @JsonSerializable(), but that's okay
// The class has a `fromJson` constructor and a `toJson` method, which is
// all that is required.
final Sample2 value;

class Sample2 {
Sample2(this.value);
Map<String, dynamic> toJson() => _$Sample1ToJson(this);
}

// The convention is for `fromJson` to take a single parameter of type
// `Map<String, dynamic>` but any JSON-compatible type is allowed.
factory Sample2.fromJson(int value) => Sample2(value);
final int value;
class Sample2 {
Sample2(this.value);

// The convention is for `toJson` to take return a type of
// `Map<String, dynamic>` but any JSON-compatible type is allowed.
int toJson() => value;
}
```
// The convention is for `fromJson` to take a single parameter of type
// `Map<String, dynamic>` but any JSON-compatible type is allowed.
factory Sample2.fromJson(int value) => Sample2(value);
final int value;

1. Use the [`JsonKey.toJson`] and [`JsonKey.fromJson`] properties to specify
// The convention is for `toJson` to take return a type of
// `Map<String, dynamic>` but any JSON-compatible type is allowed.
int toJson() => value;
}
```
2. Use the [`JsonKey.toJson`] and [`JsonKey.fromJson`] properties to specify
custom conversions on the annotated field. The functions specified must be
top-level or static. See the documentation of these properties for details.

```dart
@JsonSerializable()
class Sample3 {
Sample3(this.value);

factory Sample3.fromJson(Map<String, dynamic> json) =>
_$Sample3FromJson(json);
```dart
@JsonSerializable()
class Sample3 {
Sample3(this.value);

@JsonKey(
toJson: _toJson,
fromJson: _fromJson,
)
final DateTime value;
factory Sample3.fromJson(Map<String, dynamic> json) =>
_$Sample3FromJson(json);

Map<String, dynamic> toJson() => _$Sample3ToJson(this);
@JsonKey(
toJson: _toJson,
fromJson: _fromJson,
)
final DateTime value;

static int _toJson(DateTime value) => value.millisecondsSinceEpoch;
static DateTime _fromJson(int value) =>
DateTime.fromMillisecondsSinceEpoch(value);
}
```
Map<String, dynamic> toJson() => _$Sample3ToJson(this);

1. Create an implementation of [`JsonConverter`] and annotate either the
static int _toJson(DateTime value) => value.millisecondsSinceEpoch;
static DateTime _fromJson(int value) =>
DateTime.fromMillisecondsSinceEpoch(value);
}
```
3. Create an implementation of [`JsonConverter`] and annotate either the
corresponding field or the containing class. [`JsonConverter`] is convenient
if you want to use the same conversion logic on many fields. It also allows
you to support a type within collections. Check out
[these examples](https://github.com/google/json_serializable.dart/blob/master/example/lib/json_converter_example.dart).

```dart
@JsonSerializable()
class Sample4 {
Sample4(this.value);
```dart
@JsonSerializable()
class Sample4 {
Sample4(this.value);

factory Sample4.fromJson(Map<String, dynamic> json) =>
_$Sample4FromJson(json);
factory Sample4.fromJson(Map<String, dynamic> json) =>
_$Sample4FromJson(json);

@EpochDateTimeConverter()
final DateTime value;
@EpochDateTimeConverter()
final DateTime value;

Map<String, dynamic> toJson() => _$Sample4ToJson(this);
}
Map<String, dynamic> toJson() => _$Sample4ToJson(this);
}

class EpochDateTimeConverter implements JsonConverter<DateTime, int> {
const EpochDateTimeConverter();
class EpochDateTimeConverter implements JsonConverter<DateTime, int> {
const EpochDateTimeConverter();

@override
DateTime fromJson(int json) => DateTime.fromMillisecondsSinceEpoch(json);
@override
DateTime fromJson(int json) => DateTime.fromMillisecondsSinceEpoch(json);

@override
int toJson(DateTime object) => object.millisecondsSinceEpoch;
}
```
@override
int toJson(DateTime object) => object.millisecondsSinceEpoch;
}
```

# Build configuration

Expand All @@ -276,6 +277,7 @@ targets:
constructor: ""
create_factory: true
create_field_map: false
create_json_keys: false
create_per_field_to_json: false
create_to_json: true
disallow_unrecognized_keys: false
Expand All @@ -297,15 +299,15 @@ targets:
[`Enum`]: https://api.dart.dev/stable/dart-core/Enum-class.html
[`int`]: https://api.dart.dev/stable/dart-core/int-class.html
[`Iterable`]: https://api.dart.dev/stable/dart-core/Iterable-class.html
[`JsonConverter`]: https://pub.dev/documentation/json_annotation/4.8.1/json_annotation/JsonConverter-class.html
[`JsonEnum.valueField`]: https://pub.dev/documentation/json_annotation/4.8.1/json_annotation/JsonEnum/valueField.html
[`JsonEnum`]: https://pub.dev/documentation/json_annotation/4.8.1/json_annotation/JsonEnum-class.html
[`JsonKey.fromJson`]: https://pub.dev/documentation/json_annotation/4.8.1/json_annotation/JsonKey/fromJson.html
[`JsonKey.toJson`]: https://pub.dev/documentation/json_annotation/4.8.1/json_annotation/JsonKey/toJson.html
[`JsonKey`]: https://pub.dev/documentation/json_annotation/4.8.1/json_annotation/JsonKey-class.html
[`JsonLiteral`]: https://pub.dev/documentation/json_annotation/4.8.1/json_annotation/JsonLiteral-class.html
[`JsonSerializable`]: https://pub.dev/documentation/json_annotation/4.8.1/json_annotation/JsonSerializable-class.html
[`JsonValue`]: https://pub.dev/documentation/json_annotation/4.8.1/json_annotation/JsonValue-class.html
[`JsonConverter`]: https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonConverter-class.html
[`JsonEnum.valueField`]: https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonEnum/valueField.html
[`JsonEnum`]: https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonEnum-class.html
[`JsonKey.fromJson`]: https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonKey/fromJson.html
[`JsonKey.toJson`]: https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonKey/toJson.html
[`JsonKey`]: https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonKey-class.html
[`JsonLiteral`]: https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonLiteral-class.html
[`JsonSerializable`]: https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonSerializable-class.html
[`JsonValue`]: https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonValue-class.html
[`List`]: https://api.dart.dev/stable/dart-core/List-class.html
[`Map`]: https://api.dart.dev/stable/dart-core/Map-class.html
[`num`]: https://api.dart.dev/stable/dart-core/num-class.html
Expand Down
20 changes: 20 additions & 0 deletions json_serializable/lib/src/encoder_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,26 @@ mixin EncodeHelper implements HelperCore {
return buffer.toString();
}

/// Generates an object containing metadatas related to the encoding,
/// destined to be used by other code-generators.
String createJsonKeys(Set<FieldElement> accessibleFieldSet) {
assert(config.createJsonKeys);

final buffer = StringBuffer(
'class _\$${element.name.nonPrivate}JsonKeys {',
)..write('const _\$${element.name.nonPrivate}JsonKeys();');

for (final field in accessibleFieldSet) {
buffer.writeln(
'final String ${field.name} = ${escapeDartString(nameAccess(field))};',
);
}

buffer.write('}');

return buffer.toString();
}

Iterable<String> createToJson(Set<FieldElement> accessibleFields) sync* {
assert(config.createToJson);

Expand Down
4 changes: 4 additions & 0 deletions json_serializable/lib/src/generator_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ class GeneratorHelper extends HelperCore with EncodeHelper, DecodeHelper {
yield createFieldMap(accessibleFieldSet);
}

if (config.createJsonKeys) {
yield createJsonKeys(accessibleFieldSet);
}

if (config.createPerFieldToJson) {
yield createPerFieldToJson(accessibleFieldSet);
}
Expand Down
6 changes: 6 additions & 0 deletions json_serializable/lib/src/type_helpers/config_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class ClassConfig {
final bool createFactory;
final bool createToJson;
final bool createFieldMap;
final bool createJsonKeys;
final bool createPerFieldToJson;
final bool disallowUnrecognizedKeys;
final bool explicitToJson;
Expand All @@ -66,6 +67,7 @@ class ClassConfig {
required this.createFactory,
required this.createToJson,
required this.createFieldMap,
required this.createJsonKeys,
required this.createPerFieldToJson,
required this.disallowUnrecognizedKeys,
required this.explicitToJson,
Expand All @@ -85,6 +87,8 @@ class ClassConfig {
constructor: config.constructor ?? ClassConfig.defaults.constructor,
createFieldMap:
config.createFieldMap ?? ClassConfig.defaults.createFieldMap,
createJsonKeys:
config.createJsonKeys ?? ClassConfig.defaults.createJsonKeys,
createPerFieldToJson: config.createPerFieldToJson ??
ClassConfig.defaults.createPerFieldToJson,
createFactory:
Expand Down Expand Up @@ -113,6 +117,7 @@ class ClassConfig {
createFactory: true,
createToJson: true,
createFieldMap: false,
createJsonKeys: false,
createPerFieldToJson: false,
disallowUnrecognizedKeys: false,
explicitToJson: false,
Expand All @@ -129,6 +134,7 @@ class ClassConfig {
createFactory: createFactory,
createToJson: createToJson,
createFieldMap: createFieldMap,
createJsonKeys: createJsonKeys,
createPerFieldToJson: createPerFieldToJson,
ignoreUnannotated: ignoreUnannotated,
explicitToJson: explicitToJson,
Expand Down
Loading