trphoenix
2018-11-19 524fe036f63de1056aab457781e0b7141a5b7adc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// 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:json_annotation/json_annotation.dart';
import 'package:source_gen/source_gen.dart';
 
import '../shared_checkers.dart';
import '../type_helper.dart';
import '../utils.dart';
 
class JsonHelper extends TypeHelper<TypeHelperContextWithConfig> {
  const JsonHelper();
 
  /// Simply returns the [expression] provided.
  ///
  /// By default, JSON encoding in from `dart:convert` calls `toJson()` on
  /// provided objects.
  @override
  String serialize(DartType targetType, String expression,
      TypeHelperContextWithConfig context) {
    if (!_canSerialize(context.config, targetType)) {
      return null;
    }
 
    if (context.config.explicitToJson) {
      return '$expression${context.nullable ? '?' : ''}.toJson()';
    }
    return expression;
  }
 
  @override
  String deserialize(DartType targetType, String expression,
      TypeHelperContextWithConfig context) {
    if (targetType is! InterfaceType) {
      return null;
    }
 
    final type = targetType as InterfaceType;
    final classElement = type.element;
 
    final fromJsonCtor = classElement.constructors
        .singleWhere((ce) => ce.name == 'fromJson', orElse: () => null);
 
    String asCast;
    if (fromJsonCtor != null) {
      // TODO: should verify that this type is a valid JSON type
      final asCastType = fromJsonCtor.parameters.first.type;
      asCast = asStatement(asCastType);
    } else if (_annotation(context.config, type)?.createFactory == true) {
      if (context.config.anyMap) {
        asCast = ' as Map';
      } else {
        asCast = ' as Map<String, dynamic>';
      }
    } else {
      return null;
    }
 
    // TODO: the type could be imported from a library with a prefix!
    // github.com/dart-lang/json_serializable/issues/19
    final result = '${targetType.name}.fromJson($expression$asCast)';
 
    return commonNullPrefix(context.nullable, expression, result).toString();
  }
}
 
bool _canSerialize(JsonSerializable config, DartType type) {
  if (type is InterfaceType) {
    final toJsonMethod = _toJsonMethod(type);
 
    if (toJsonMethod != null) {
      // TODO: validate there are no required parameters
      return true;
    }
 
    if (_annotation(config, type)?.createToJson == true) {
      // TODO: consider logging that we're assuming a user will wire up the
      // generated mixin at some point...
      return true;
    }
  }
  return false;
}
 
JsonSerializable _annotation(JsonSerializable config, InterfaceType source) {
  final annotations = const TypeChecker.fromRuntime(JsonSerializable)
      .annotationsOfExact(source.element, throwOnUnresolved: false)
      .toList();
 
  if (annotations.isEmpty) {
    return null;
  }
 
  return mergeConfig(config, ConstantReader(annotations.single));
}
 
MethodElement _toJsonMethod(DartType type) => typeImplementations(type)
    .map((dt) => dt is InterfaceType ? dt.getMethod('toJson') : null)
    .firstWhere((me) => me != null, orElse: () => null);