// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/dart/constant/value.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:source_gen/source_gen.dart'; import 'json_literal_generator.dart'; import 'utils.dart'; final _jsonKeyExpando = Expando(); JsonKey jsonKeyForField(FieldElement field, JsonSerializable classAnnotation) => _jsonKeyExpando[field] ??= _from(field, classAnnotation); JsonKey _from(FieldElement element, JsonSerializable classAnnotation) { // If an annotation exists on `element` the source is a 'real' field. // If the result is `null`, check the getter – it is a property. // TODO(kevmoo) setters: github.com/dart-lang/json_serializable/issues/24 final obj = jsonKeyAnnotation(element); if (obj == null) { return _populateJsonKey(classAnnotation, element); } Object _getLiteral(DartObject dartObject, Iterable things) { if (dartObject.isNull) { return null; } final reader = ConstantReader(dartObject); String badType; if (reader.isSymbol) { badType = 'Symbol'; } else if (reader.isType) { badType = 'Type'; } else if (dartObject.type is FunctionType) { // TODO(kevmoo): Support calling function for the default value? badType = 'Function'; } else if (!reader.isLiteral) { badType = dartObject.type.name; } if (badType != null) { badType = things.followedBy([badType]).join(' > '); throwUnsupported( element, '`defaultValue` is `$badType`, it must be a literal.'); } final literal = reader.literalValue; if (literal is num || literal is String || literal is bool) { return literal; } else if (literal is List) { return literal .map((e) => _getLiteral(e, things.followedBy(['List']))) .toList(); } else if (literal is Map) { final mapThings = things.followedBy(['Map']); return literal.map((k, v) => MapEntry(_getLiteral(k, mapThings), _getLiteral(v, mapThings))); } badType = things.followedBy(['$dartObject']).join(' > '); throwUnsupported( element, 'The provided value is not supported: $badType. ' 'This may be an error in package:json_serializable. ' 'Please rerun your build with `--verbose` and file an issue.'); } final defaultValueObject = obj.getField('defaultValue'); Object defaultValueLiteral; final enumFields = iterateEnumFields(defaultValueObject.type); if (enumFields != null) { final allowedValues = enumFields.map((p) => p.name).toList(); final enumValueIndex = defaultValueObject.getField('index').toIntValue(); defaultValueLiteral = '${defaultValueObject.type.name}.${allowedValues[enumValueIndex]}'; } else { defaultValueLiteral = _getLiteral(defaultValueObject, []); if (defaultValueLiteral != null) { defaultValueLiteral = jsonLiteralAsDart(defaultValueLiteral); } } final disallowNullValue = obj.getField('disallowNullValue').toBoolValue(); final includeIfNull = obj.getField('includeIfNull').toBoolValue(); if (disallowNullValue == true) { if (includeIfNull == true) { throwUnsupported( element, 'Cannot set both `disallowNullvalue` and `includeIfNull` to `true`. ' 'This leads to incompatible `toJson` and `fromJson` behavior.'); } } return _populateJsonKey( classAnnotation, element, name: obj.getField('name').toStringValue(), nullable: obj.getField('nullable').toBoolValue(), includeIfNull: includeIfNull, ignore: obj.getField('ignore').toBoolValue(), defaultValue: defaultValueLiteral, required: obj.getField('required').toBoolValue(), disallowNullValue: disallowNullValue, ); } JsonKey _populateJsonKey( JsonSerializable classAnnotation, FieldElement fieldElement, { String name, bool nullable, bool includeIfNull, bool ignore, Object defaultValue, bool required, bool disallowNullValue, }) { final jsonKey = JsonKey( name: _encodedFieldName(classAnnotation, name, fieldElement), nullable: nullable ?? classAnnotation.nullable, includeIfNull: _includeIfNull( includeIfNull, disallowNullValue, classAnnotation.includeIfNull), ignore: ignore ?? false, defaultValue: defaultValue, required: required ?? false, disallowNullValue: disallowNullValue ?? false); return jsonKey; } String _encodedFieldName(JsonSerializable classAnnotation, String jsonKeyNameValue, FieldElement fieldElement) { if (jsonKeyNameValue != null) { return jsonKeyNameValue; } switch (classAnnotation.fieldRename) { case FieldRename.none: // noop break; case FieldRename.snake: return snakeCase(fieldElement.name); case FieldRename.kebab: return kebabCase(fieldElement.name); } return fieldElement.name; } bool _includeIfNull( bool keyIncludeIfNull, bool keyDisallowNullValue, bool classIncludeIfNull) { if (keyDisallowNullValue == true) { assert(keyIncludeIfNull != true); return false; } return keyIncludeIfNull ?? classIncludeIfNull; }