Skip to content

Adding case insensitive to enum based on loading JsonEnum config #1155

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

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
22 changes: 20 additions & 2 deletions json_annotation/lib/src/enum_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@

import 'json_key.dart';

/// Compare an enum value against a source using case-insensitive.
///
/// Exposed only for code generated by `package:json_serializable`.
/// Not meant to be used directly by user code.
bool $enumCompareCaseInsensitive(String arg1, Object arg2) => arg2 is String &&
(arg1.toLowerCase() == arg2.toLowerCase());

/// Compare an enum value against a source.
///
/// Exposed only for code generated by `package:json_serializable`.
/// Not meant to be used directly by user code.
bool $enumCompareStandard<V>(V arg1, Object arg2) => arg1 == arg2;

/// Returns the key associated with value [source] from [enumValues], if one
/// exists.
///
Expand All @@ -18,13 +31,16 @@ K? $enumDecodeNullable<K extends Enum, V>(
Map<K, V> enumValues,
Object? source, {
Enum? unknownValue,
bool Function(V arg1, Object arg2)? comparator,
}) {
if (source == null) {
return null;
}

comparator ??= $enumCompareStandard;

for (var entry in enumValues.entries) {
if (entry.value == source) {
if (comparator(entry.value, source)) {
return entry.key;
}
}
Expand Down Expand Up @@ -65,6 +81,7 @@ K $enumDecode<K extends Enum, V>(
Map<K, V> enumValues,
Object? source, {
K? unknownValue,
bool Function(V arg1, Object arg2)? comparator,
}) {
if (source == null) {
throw ArgumentError(
Expand All @@ -73,8 +90,9 @@ K $enumDecode<K extends Enum, V>(
);
}

comparator ??= $enumCompareStandard;
for (var entry in enumValues.entries) {
if (entry.value == source) {
if (comparator(entry.value, source)) {
return entry.key;
}
}
Expand Down
13 changes: 13 additions & 0 deletions json_annotation/lib/src/json_enum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class JsonEnum {
const JsonEnum({
this.alwaysCreate = false,
this.fieldRename = FieldRename.none,
this.caseInsensitive = false,
});

/// If `true`, `_$[enum name]EnumMap` is generated for in library containing
Expand All @@ -34,4 +35,16 @@ class JsonEnum {
/// Note: the value for [JsonValue.value] takes precedence over this option
/// for entries annotated with [JsonValue].
final FieldRename fieldRename;

/// if `true`, enum comparison will be done using case-insensitive.
///
/// The default, `false`, means enum comparison will be done using
/// case-sensitive.
final bool caseInsensitive;

factory JsonEnum.fromJson(Map<String, dynamic> json) {
final caseInsensitive = json['case_insensitive'] as bool?;
return // caseInsensitive == null ? null :
JsonEnum(caseInsensitive: caseInsensitive ?? false);
}
}
1 change: 1 addition & 0 deletions json_serializable/lib/src/enum_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ JsonEnum _fromAnnotation(DartObject? dartObject) {
final reader = ConstantReader(dartObject);
return JsonEnum(
alwaysCreate: reader.read('alwaysCreate').literalValue as bool,
caseInsensitive: reader.read('caseInsensitive').literalValue as bool,
fieldRename: enumValueForDartObject(
reader.read('fieldRename').objectValue,
FieldRename.values,
Expand Down
6 changes: 6 additions & 0 deletions json_serializable/lib/src/json_key_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@ KeyConfig _from(FieldElement element, ClassConfig classAnnotation) {
final ctorParamDefault = classAnnotation.ctorParamDefaults[element.name];

if (obj.isNull) {
final enumObj = jsonEnumAnnotation(element);

return _populateJsonKey(
classAnnotation,
element,
defaultValue: ctorParamDefault,
ignore: classAnnotation.ignoreUnannotated,
caseInsensitive: enumObj.isNull ? null :
enumObj.read('caseInsensitive').literalValue as bool?,
);
}

Expand Down Expand Up @@ -241,6 +245,7 @@ KeyConfig _populateJsonKey(
String? readValueFunctionName,
bool? required,
String? unknownEnumValue,
bool? caseInsensitive,
}) {
if (disallowNullValue == true) {
if (includeIfNull == true) {
Expand All @@ -261,6 +266,7 @@ KeyConfig _populateJsonKey(
readValueFunctionName: readValueFunctionName,
required: required ?? false,
unknownEnumValue: unknownEnumValue,
caseInsensitive: caseInsensitive,
);
}

Expand Down
3 changes: 3 additions & 0 deletions json_serializable/lib/src/type_helpers/config_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class KeyConfig {

final String? unknownEnumValue;

final bool? caseInsensitive;

final String? readValueFunctionName;

KeyConfig({
Expand All @@ -32,6 +34,7 @@ class KeyConfig {
required this.readValueFunctionName,
required this.required,
required this.unknownEnumValue,
required this.caseInsensitive,
});
}

Expand Down
2 changes: 2 additions & 0 deletions json_serializable/lib/src/type_helpers/enum_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ class EnumHelper extends TypeHelper<TypeHelperContextWithConfig> {
expression,
if (jsonKey.unknownEnumValue != null)
'unknownValue: ${jsonKey.unknownEnumValue}',
if ((jsonKey.caseInsensitive ?? false) == true)
r'comparator: $enumCompareCaseInsensitive',
];

return '$functionName(${args.join(', ')})';
Expand Down
10 changes: 10 additions & 0 deletions json_serializable/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import 'type_helpers/config_types.dart';

const _jsonKeyChecker = TypeChecker.fromRuntime(JsonKey);

const _jsonEnumChecker = TypeChecker.fromRuntime(JsonEnum);

DartObject? _jsonKeyAnnotation(FieldElement element) =>
_jsonKeyChecker.firstAnnotationOf(element) ??
(element.getter == null
Expand All @@ -22,6 +24,14 @@ DartObject? _jsonKeyAnnotation(FieldElement element) =>
ConstantReader jsonKeyAnnotation(FieldElement element) =>
ConstantReader(_jsonKeyAnnotation(element));

DartObject? _jsonEnumAnnotation(Element? element) =>
(element != null && element is ClassElement) ?
_jsonEnumChecker.firstAnnotationOf(element)
: null;

ConstantReader jsonEnumAnnotation(FieldElement element) =>
ConstantReader(_jsonEnumAnnotation(element.type.element));

/// Returns `true` if [element] is annotated with [JsonKey].
bool hasJsonKeyAnnotation(FieldElement element) =>
_jsonKeyAnnotation(element) != null;
Expand Down
6 changes: 6 additions & 0 deletions json_serializable/test/integration/integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ void main() {
roundTripOrder(order);
});

test('case insensitive map', () {
final jsonOrder = {'category': 'CHaRmED'};
final order = Order.fromJson(jsonOrder);
expect(order.category, Category.charmed);
});

test('required, but missing enum value fails', () {
expect(
() => Person.fromJson({
Expand Down
2 changes: 1 addition & 1 deletion json_serializable/test/integration/json_test_common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'dart:collection';

import 'package:json_annotation/json_annotation.dart';

@JsonEnum(fieldRename: FieldRename.kebab)
@JsonEnum(fieldRename: FieldRename.kebab, caseInsensitive: true)
enum Category {
top,
bottom,
Expand Down