如何在Dart中使用泛型和泛型列表进行JSON序列化?

30
我正在开发一个使用Flutter的移动项目。这个项目需要连接一些服务器来获取REST消费服务(GET、POST、PUT、DELETE,...)的数据,并且从它们那里检索数据以及发送数据。数据需要用JSON格式进行格式化,因此我决定利用Dart的Json序列化库2.0.3和Json注释2.0.0以及build_runner 1.2.8;它可以很好地处理基本数据类型,例如int、String和bool,以及自定义对象。但是对于泛型,例如<T>item;字段或List<T>list;字段似乎根本不起作用。
我的意图是添加一些通用字段,以便它们可以用于返回各种json类型和结构。我设法找到了解决方案,为第一种情况使用"@JsonKey"覆盖fromJson和toJson,并在方法中将<T>与我想要强制转换到的目标类型进行比较。然而,我无法找到解决List<T>类型字段的解决方案。如果我尝试为它们使用注释,我得到的只是一个无用的List<dynamic>类型,无法为转换类进行比较。我该如何解决困境?我应该坚持使用json_serialization还是改用build_value?非常感谢您对此事提供的任何帮助。
我的代码:
import 'package:json_annotation/json_annotation.dart';

part 'json_generic.g.dart';

@JsonSerializable()
class JsonGeneric<T> {
  final int id;
  final String uri;
  final bool active;
  @JsonKey(fromJson: _fromGenericJson, toJson: _toGenericJson)
  final T item;
  @JsonKey(fromJson: _fromGenericJsonList, toJson: _toGenericJsonList)
  final List<T> list;

  static const String _exceptionMessage = "Incompatible type used in JsonEnvelop";

  JsonGeneric({this.id, this.uri, this.active, this.item, this.list});

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

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

  static T _fromGenericJson<T>(Map<String, dynamic> json) {
    if (T == User) {
      return json == null ? null : User.fromJson(json) as T;
    } else if (T == Company) {
      return json == null ? null : Company.fromJson(json) as T;
    } else if (T == Data) {
      return json == null ? null : Data.fromJson(json) as T;
    } else {
      throw Exception(_exceptionMessage);
    }
  }

  static Map<String, dynamic> _toGenericJson<T>(T value) {
    if (T == User) {
      return (T as User).toJson();
    } else if(T == Company) {
      return (T as Company).toJson();
    } else if(T == Data) {
      return (T as Data).toJson();
    } else {
      throw Exception(_exceptionMessage);
    }
  }

  static dynamic _fromGenericJsonList<T>(List<dynamic> json) {
    if (T == User) {

    } else if(T == Company) {

    } else if(T == Data) {

    } else {
      throw Exception(_exceptionMessage);
    }
  }

  static List<Map<String, dynamic>> _toGenericJsonList<T>(dynamic value) {
    if (T == User) {

    } else if(T == Company) {

    } else if(T == Data) {

    } else {
      throw Exception(_exceptionMessage);
    }
  }
}

我期望能够使用"@JsonKey"或不使用它来序列化/反序列化"final List list;",但到目前为止,我未能找到将其转换为适当的JSON格式的方法。
当我尝试为这个类生成代码(使用命令"flutter packages pub run build_runner build")时,我最终收到以下错误:
运行JsonSerializableGenerator时出错。由于类型T,无法生成list的fromJson代码。提供的TypeHelper实例都不支持定义的类型。package:json_generic.dart:11:17
   ╷
11 │   final List<T> list;
   │                 ^^^^
   ╵

这个回答解决了你的问题吗?Flutter - 如何将嵌套的json解析为带有泛型的类? - Ovidiu
8个回答

22

json_serializable

json_serializable提供了几种策略1来处理泛型类型,如单个对象TList<T>(从v. 5.0.2+开始):

  1. 辅助类:JsonConverter
  2. 辅助方法:@JsonKey(fromJson:, toJson:)
  3. 泛型参数工厂@JsonSerializable(genericArgumentFactories: true)

1 我所知道的。可能还有其他方法。

辅助类:JsonConverter

基本思路:编写一个自定义的JsonConverter类,具有fromJsontoJson方法,以识别和处理我们的类型T字段的反/序列化。

JsonConverter策略的好处是它将所有模型的反/序列化逻辑封装到一个可重用的单个类中,该类可在需要序列化相同模型类型的任何类之间重用。而且,与泛型参数工厂策略不同,您的toJsonfromJson调用不会改变,其中每个toJsonfromJson调用都需要我们提供一个处理函数。

我们可以通过注释来使用JsonConverter对我们的对象进行反/序列化:

  • 需要自定义处理的单个T / List<T>字段,或
  • 整个类(在这种情况下,将在所有类型为T的字段上使用)。

以下是一个包含泛型类型字段T的json_serializable类OperationResult<T>的示例。

OperationResult类的注意事项:

  • 具有单个泛型类型字段T t
  • t可以是类型为T的单个对象对象的List<T>
  • 无论T是什么类型,它都必须具有toJson()/fromJson()方法(即可反/序列化)。
  • 具有名为ModelConverterJsonConverter类,注释了T t字段。
  • 生成的存根_$OperationResultFromJson<T>(json)_$OperationResultToJson<T>()现在需要一个T变量。
/// This method of json_serializable handles generic type arguments / fields by
/// specifying a converter helper class on the generic type field or on the entire class.
/// If the converter is specified on the class itself vs. just a field, any field with
/// type T will be de/serialized using the converter.
/// This strategy also requires us determine the JSON type during deserialization manually,
/// by peeking at the JSON and making assumptions about its class.
@JsonSerializable(explicitToJson: true)
class OperationResult<T> {
  final bool ok;
  final Operation op;
  @ModelConverter()
  final T t;
  final String title;
  final String msg;
  final String error;

  OperationResult({
    this.ok = false,
    this.op = Operation.update,
    required this.t,
    this.title = 'Operation Error',
    this.msg = 'Operation failed to complete',
    this.error= 'Operation could not be decoded for processing'});

  factory OperationResult.fromJson(Map<String,dynamic> json) =>
      _$OperationResultFromJson<T>(json);
  Map<String,dynamic> toJson() => _$OperationResultToJson<T>(this);
}

以下是用于上述内容的 JsonConverterModelConverter

/// This JsonConverter class holds the toJson/fromJson logic for generic type
/// fields in our Object that will be de/serialized.
/// This keeps our Object class clean, separating out the converter logic.
///
/// JsonConverter takes two type variables: <T,S>.
///
/// Inside our JsonConverter, T and S are used like so:
///
/// T fromJson(S)
/// S toJson(T)
///
/// T is the concrete class type we're expecting out of fromJson() calls.
/// It's also the concrete type we're inputting for serialization in toJson() calls.
///
/// Most commonly, T will just be T: a variable type passed to JsonConverter in our
/// Object being serialized, e.g. the "T" from OperationResult<T> above.
///
/// S is the JSON type.  Most commonly this would Map<String,dynamic>
/// if we're only de/serializing single objects.  But, if we want to de/serialize
/// Lists, we need to use "Object" instead to handle both a single object OR a List of objects.
class ModelConverter<T> implements JsonConverter<T, Object> {
  const ModelConverter();

  /// fromJson takes Object instead of Map<String,dynamic> so as to handle both
  /// a JSON map or a List of JSON maps.  If List is not used, you could specify
  /// Map<String,dynamic> as the S type variable and use it as
  /// the json argument type for fromJson() & return type of toJson(). 
  /// S can be any Dart supported JSON type
  /// https://pub.dev/packages/json_serializable/versions/6.0.0#supported-types
  /// In this example we only care about Object and List<Object> serialization
  @override
  T fromJson(Object json) {
    /// start by checking if json is just a single JSON map, not a List
    if (json is Map<String,dynamic>) {
      /// now do our custom "inspection" of the JSON map, looking at key names
      /// to figure out the type of T t. The keys in our JSON will
      /// correspond to fields of the object that was serialized.
      if (json.containsKey('items') && json.containsKey('customer')) {
        /// In this case, our JSON contains both an 'items' key/value pair
        /// and a 'customer' key/value pair, which I know only our Order model class
        /// has as fields.  So, this JSON map is an Order object that was serialized
        /// via toJson().  Now I'll deserialize it using Order's fromJson():
        return Order.fromJson(json) as T;
        /// We must cast this "as T" because the return type of the enclosing
        /// fromJson(Object? json) call is "T" and at compile time, we don't know
        /// this is an Order.  Without this seemingly useless cast, a compile time
        /// error will be thrown: we can't return an Order for a method that
        /// returns "T".
      }
      /// Handle all the potential T types with as many if/then checks as needed.
      if (json.containsKey('status') && json.containsKey('menuItem')) {
        return OrderItem.fromJson(json) as T;
      }
      if (json.containsKey('name') && json.containsKey('restaurantId')) {
        return Menu.fromJson(json) as T;
      }
      if (json.containsKey('menuId') && json.containsKey('restaurantId')) {
        return MenuItem.fromJson(json) as T;
      }
    } else if (json is List) { /// here we handle Lists of JSON maps
      if (json.isEmpty) return [] as T;

      /// Inspect the first element of the List of JSON to determine its Type
      Map<String,dynamic> _first = json.first as Map<String,dynamic>;
      bool _isOrderItem = _first.containsKey('status') && _first.containsKey('menuItem');

      if (_isOrderItem) {
        return json.map((_json) => OrderItem.fromJson(_json)).toList() as T;
      }

      bool _isMenuItem = _first.containsKey('menuId') && _first.containsKey('restaurantId');

      if (_isMenuItem) {
        return json.map((_json) => MenuItem.fromJson(_json)).toList() as T;
      }

    }
    /// We didn't recognize this JSON map as one of our model classes, throw an error
    /// so we can add the missing case
    throw ArgumentError.value(json, 'json', 'OperationResult._fromJson cannot handle'
        ' this JSON payload. Please add a handler to _fromJson.');
  }

  /// Since we want to handle both JSON and List of JSON in our toJson(),
  /// our output Type will be Object.
  /// Otherwise, Map<String,dynamic> would be OK as our S type / return type.
  ///
  /// Below, "Serializable" is an abstract class / interface we created to allow
  /// us to check if a concrete class of type T has a "toJson()" method. See
  /// next section further below for the definition of Serializable.
  /// Maybe there's a better way to do this?
  ///
  /// Our JsonConverter uses a type variable of T, rather than "T extends Serializable",
  /// since if T is a List, it won't have a toJson() method and it's not a class
  /// under our control.
  /// Thus, we impose no narrower scope so as to handle both cases: an object that
  /// has a toJson() method, or a List of such objects.
  @override
  Object toJson(T object) {
    /// First we'll check if object is Serializable.
    /// Testing for Serializable type (our custom interface of a class signature
    /// that has a toJson() method) allows us to call toJson() directly on it.
    if (object is Serializable){
      return object.toJson();
    } /// otherwise, check if it's a List & not empty & elements are Serializable
    else if (object is List) {
      if (object.isEmpty) return [];

      if (object.first is Serializable) {
        return object.map((t) => t.toJson()).toList();
      }
    }
    /// It's not a List & it's not Serializable, this is a design issue
    throw ArgumentError.value(object, 'Cannot serialize to JSON',
        'OperationResult._toJson this object or List either is not '
            'Serializable or is unrecognized.');
  }

}

以下是用于我们的模型类(如Order和MenuItem)实现的Serializable接口(请参见上面的ModelConverter中的toJson()代码,了解如何以及为什么使用它):
/// Interface for classes to implement and be "is" test-able and "as" cast-able
abstract class Serializable {
  Map<String,dynamic> toJson();
}

辅助方法: @JsonKey(fromJson:, toJson:)

该注释用于为使用json_serializable的类中的任何类型的字段指定自定义de/serialization处理程序,而不仅仅是通用类型。

因此,我们可以使用与上面JsonConverter示例中使用的"查看键"逻辑相同的方式,为我们的通用类型字段T t指定自定义处理程序。

下面,我们在我们的类OperationResultJsonKey<T>中添加了两个静态方法(仅在此Stackoverflow示例中命名为此以明显):

  • _fromJson
  • _toJson

(这些也可以作为顶级函数存在。)

然后我们将这两个方法提供给JsonKey:

@JsonKey(fromJson: _fromJson, toJson: _toJson)

然后,在重新运行我们的flutter或dart build_runner之后(flutter pub run build_runner builddart run build_runner build),这两个静态方法将由json_serializable提供的生成的de/serialize方法使用。

/// This method of json_serializable handles generic type arguments / fields by
/// specifying a static or top-level helper method on the field itself.
/// json_serializable will call these hand-typed helpers when de/serializing that particular
/// field.
/// During de/serialization we'll again determine the type manually, by peeking at the
/// JSON keys and making assumptions about its class.
@JsonSerializable(explicitToJson: true)
class OperationResultJsonKey<T> {
  final bool ok;
  final Operation op;
  @JsonKey(fromJson: _fromJson, toJson: _toJson)
  final T t;
  final String title;
  final String msg;
  final String error;


  OperationResultJsonKey({
    this.ok = false,
    this.op = Operation.update,
    required this.t,
    this.title = 'Operation Error',
    this.msg = 'Operation failed to complete',
    this.error = 'Operation could not be decoded for processing'});

  static T _fromJson<T>(Object json) {
    // same logic as JsonConverter example
  }

  static Object _toJson<T>(T object) {
    // same logic as JsonConverter example
  }

  /// These two _$ methods will be created by json_serializable and will call the above
  /// static methods `_fromJson` and `_toJson`.
  factory OperationResultJsonKey.fromJson(Map<String, dynamic> json) =>
      _$OperationResultJsonKeyFromJson(json);

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

}

通用的参数工厂 @JsonSerializable(genericArgumentFactories: true)

在这种专门处理 de/serialization 的方式中,我们需要直接为在 OperationResult 上调用 toJson()fromJson() 提供自定义的 de/serialization 方法。

这种策略可能是最灵活的(允许您为每种泛型类型指定序列化处理方式),但它也非常冗长,需要您在每个 toJson / fromJson 调用上提供一个序列化处理函数。这很快就会变得烦人。

toJson

例如,在序列化 OperationResult<Order> 时,.toJson() 调用需要一个函数,告诉 json_serializable 如何在序列化 OperationResult<Order> 时序列化 Order 字段。

该助手函数的签名为:Object Function(T) toJsonT

所以在 OperationResult 中,我们的 toJson() 存根方法(由 json_serializable 为我们完成)从:

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

变成:

Map<String,dynamic> toJson(Object Function(T) toJsonT) => _$OperationResultToJson<T>(this, toJsonT);

  • toJson() 从接受 零个 参数变为接受一个 函数 作为参数
  • 当序列化 Order 时,json_serializable 将调用该 函数
  • 函数 返回的是 Object 而不是 Map<String,dynamic>,以便它还可以处理 List 中的多个 T 对象,例如 List<OrderItem>

fromJson

对于我们的 OperationResult<Order> 类上使用的 genericArgumentFactoriesfromJson() 部分,我们需要提供一个签名为:T Function(Object?) fromJsonT 的函数。

因此,如果我们要对具有泛型类型的对象进行 de/serialization,例如 OperationResult<Order>,则我们的 fromJson() 助手函数将是:

static Order fromJsonModel(Object? json) => Order.fromJson(json as Map<String,dynamic>);

这里有一个使用 genericArgumentFactories 的示例类 OperationResultGAF

@JsonSerializable(explicitToJson: true, genericArgumentFactories: true)
class OperationResultGAF<T> {
  final bool ok;
  final Operation op;
  final String title;
  final String msg;
  final T t;
  final String error;


  OperationResultGAF({
    this.ok = false,
    this.op = Operation.update,
    this.title = 'Operation Error',
    this.msg = 'Operation failed to complete',
    required this.t,
    this.error= 'Operation could not be decoded for processing'});

  // Interesting bits here → ----------------------------------- ↓ ↓
  factory OperationResultGAF.fromJson(Map<String,dynamic> json, T Function(Object? json) fromJsonT) =>
      _$OperationResultGAFFromJson<T>(json, fromJsonT);

  // And here → ------------- ↓ ↓
  Map<String,dynamic> toJson(Object Function(T) toJsonT) =>
      _$OperationResultGAFToJson<T>(this, toJsonT);
}

如果T是一个名为Order的类,那么这个Order类可以保存用于与genericArgumentFactories一起使用的静态辅助方法。
@JsonSerializable(explicitToJson: true, includeIfNull: false)
class Order implements Serializable {

  //<snip>

  /// Helper methods for genericArgumentFactories
  static Order fromJsonModel(Object? json) => Order.fromJson(json as Map<String,dynamic>);
  static Map<String, dynamic> toJsonModel(Order order) => order.toJson();

  /// Usual json_serializable stub methods
  factory Order.fromJson(Map<String,dynamic> json) => _$OrderFromJson(json);
  Map<String,dynamic> toJson() => _$OrderToJson(this);

}

请注意,上述辅助方法仅调用由json_serializable生成的常规toJson()fromJson()存根方法。
将这些静态方法添加到模型类中的目的是使提供这些辅助方法给OperationResultGAF.toJson()OperationResultGAF.fromJson()更加简洁:我们只提供它们的函数名称而不是实际函数。
例如,我们可以使用以下方式代替原有的写法:
OperationResultGAF<Order>.fromJson(_json, (Object? json) => Order.fromJson(json as Map<String,dynamic>));

我们可以使用:

OperationResultGAF<Order>.fromJson(_json, Order.fromJsonModel);

如果 T 是包括 List<MenuItem> 这样的对象,则我们需要处理列表的帮助方法。
以下是一些静态的帮助方法的示例,可添加到 MenuItem 类中以处理列表:
  static List<MenuItem> fromJsonModelList(Object? jsonList) {
    if (jsonList == null) return [];
    
    if (jsonList is List) {
      return jsonList.map((json) => MenuItem.fromJson(json)).toList();
    }
    
    // We shouldn't be here
    if (jsonList is Map<String,dynamic>) {
      return [MenuItem.fromJson(jsonList)];
    }

    // We really shouldn't be here
    throw ArgumentError.value(jsonList, 'jsonList', 'fromJsonModelList cannot handle'
        ' this JSON payload. Please add a handler for this input or use the correct '
        'helper method.');
  }

  /// Not at all comprehensive, but you get the idea
  static List<Map<String,dynamic>> toJsonModelList(Object list) {
    if (list is List<MenuItem>) {
      return list.map((item) => item.toJson()).toList();
    }
    return [];
  }

以下是关于如何在单元测试中调用这些静态帮助方法的示例:
  List<MenuItem> _mListA = [MockData.menuItem1, MockData.menuItem2];

  OperationResultGAF<List<MenuItem>> _orC = OperationResultGAF<List<MenuItem>>(
      op: Operation.delete, t: _mListA);

  /// Use toJsonModelList to produce a List<Map<String,dynamic>>
  var _json = _orC.toJson(MenuItem.toJsonModelList);

  /// Use fromJsonModelList to convert List<Map<String,dynamic>> to List<MenuItem>
  OperationResultGAF<List<MenuItem>> _orD = OperationResultGAF<List<MenuItem>>.fromJson(
      _json, MenuItem.fromJsonModelList);

  expect(_orC.op, _orD.op);
  expect(_orC.t.first.id, _orD.t.first.id);

感谢您非常详细的回答。值得注意的是,您的toJson方法不支持编码基本类型(它会抛出Cannot serialize to JSON异常),如果它没有实现Serializable,则最可能是通用类型属性。最好的方法可能是以return jsonEncode(object)结束toJson方法(这对于基本类型有效),并在try / catch中包装它,并在失败时抛出不支持的异常。fromJson方法也有同样的问题。 - Justin
在第一种方法(JsonConverter)中,如果T对象为空,我该如何处理?我找不到这种情况的解决方案。 - Anas-Qasem

15

这是我的适当解决方案,对我非常有效。

class Paginate<T> {
  int from;
  int index;
  int size;
  int count;
  int pages;
  List<T> items;
  bool hasPrevious;
  bool hasNext;

  Paginate(
      {this.index,
      this.size,
      this.count,
      this.from,
      this.hasNext,
      this.hasPrevious,
      this.items,
      this.pages});


  factory  Paginate.fromJson(Map<String,dynamic> json,Function fromJsonModel){
    final items = json['items'].cast<Map<String, dynamic>>();
    return Paginate<T>(
      from: json['from'],
      index: json['index'],
      size: json['size'],
      count: json['count'],
      pages: json['pages'],
      hasPrevious: json['hasPrevious'],
      hasNext: json['hasNext'],
      items: new List<T>.from(items.map((itemsJson) => fromJsonModel(itemsJson)))
    );
  }
}

假设我们将使用“flight model paginate model”。在这里,您必须配置航班列表。

class Flight {
  String flightScheduleId;
  String flightId;
  String flightNo;
  String flightDate;
  String flightTime;

  Flight(
      {this.flightScheduleId,
      this.flightId,
      this.flightNo,
      this.flightDate,
      this.flightTime});

  factory Flight.fromJson(Map<String, dynamic> parsedJson) {
    var dateFormatter = new DateFormat(Constants.COMMON_DATE_FORMAT);
    var timeFormatter = new DateFormat(Constants.COMMON_TIME_FORMAT);
    var parsedDate = DateTime.parse(parsedJson['flightDepartureTime']);
    String formattedDate = dateFormatter.format(parsedDate);
    String formattedTime = timeFormatter.format(parsedDate);
    return Flight(
        flightScheduleId: parsedJson['id'],
        flightId: parsedJson['flightLayoutId'],
        flightNo: parsedJson['outboundFlightName'],
        flightDate: formattedDate,
        flightTime: formattedTime,
  }
  // Magic goes here. you can use this function to from json method.
  static Flight fromJsonModel(Map<String, dynamic> json) => Flight.fromJson(json);
}

-> 在这里,您可以使用:

 Paginate<Flight>.fromJson(responses, Flight.fromJsonModel);

请问您能否查看一下这个问题? https://stackoverflow.com/questions/63017293/converting-json-result-to-dart-model - S. Aziz Kazdal

14

以下是关于此事的一个例子

https://github.com/dart-lang/json_serializable/blob/master/example/lib/json_converter_example.dart

// json_converter_example.dart


// 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:json_annotation/json_annotation.dart';

part 'json_converter_example.g.dart';

@JsonSerializable()
class GenericCollection<T> {
  @JsonKey(name: 'page')
  final int page;

  @JsonKey(name: 'total_results')
  final int totalResults;

  @JsonKey(name: 'total_pages')
  final int totalPages;

  @JsonKey(name: 'results')
  @_Converter()
  final List<T> results;

  GenericCollection(
      {this.page, this.totalResults, this.totalPages, this.results});

  factory GenericCollection.fromJson(Map<String, dynamic> json) =>
      _$GenericCollectionFromJson<T>(json);

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

class _Converter<T> implements JsonConverter<T, Object> {
  const _Converter();

  @override
  T fromJson(Object json) {
    if (json is Map<String, dynamic> &&
        json.containsKey('name') &&
        json.containsKey('size')) {
      return CustomResult.fromJson(json) as T;
    }
    if (json is Map<String, dynamic> &&
        json.containsKey('name') &&
        json.containsKey('lastname')) {
      return Person.fromJson(json) as T;
    }
    // This will only work if `json` is a native JSON type:
    //   num, String, bool, null, etc
    // *and* is assignable to `T`.
    return json as T;
  }

  @override
  Object toJson(T object) {
    // This will only work if `object` is a native JSON type:
    //   num, String, bool, null, etc
    // Or if it has a `toJson()` function`.
    return object;
  }
}

@JsonSerializable()
class CustomResult {
  final String name;
  final int size;

  CustomResult(this.name, this.size);

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

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

  @override
  bool operator ==(Object other) =>
      other is CustomResult && other.name == name && other.size == size;

  @override
  int get hashCode => name.hashCode * 31 ^ size.hashCode;
}

@JsonSerializable()
class Person {
  final String name;
  final String lastname;

  Person(this.name, this.lastname);

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

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

  @override
  bool operator ==(Object other) =>
      other is Person && other.name == name && other.lastname == lastname;
}


// 主要.dart

import './json_converter_example.dart';
import 'dart:convert';

final jsonStringCustom =
    '''{"page":1,"total_results":10,"total_pages":200,"results":[{"name":"Something","size":80},{"name":"Something 2","size":200}]}''';
final jsonStringPerson =
    '''{"page":2,"total_results":2,"total_pages":300,"results":[{"name":"Arya","lastname":"Stark"},{"name":"Night","lastname":"King"}]}''';
void main() {
  // Encode CustomResult
  List<CustomResult> results;
  results = [CustomResult("Mark", 223), CustomResult("Albert", 200)];
  // var customResult = List<CustomResult> data;
  var jsonData = GenericCollection<CustomResult>(
      page: 1, totalPages: 200, totalResults: 10, results: results);
  print({'JsonString', json.encode(jsonData)});

  // Decode CustomResult
  final genericCollectionCustom =
      GenericCollection<CustomResult>.fromJson(json.decode(jsonStringCustom));
  print({'name', genericCollectionCustom.results[0].name});

  // Encode Person

  List<Person> person;
  person = [Person("Arya", "Stark"), Person("Night", "King")];

  var jsonDataPerson = GenericCollection<Person>(
      page: 2, totalPages: 300, totalResults: 2, results: person);
  print({'JsonStringPerson', json.encode(jsonDataPerson)});

  // Decode Person

  final genericCollectionPerson =
      GenericCollection<Person>.fromJson(json.decode(jsonStringPerson));

  print({'name', genericCollectionPerson.results[0].name});
}

结果是

{JsonStringCustom, {"page":1,"total_results":10,"total_pages":200,"results":[{"name":"Mark","size":223},{"name":"Albert","size":200}]}}
{name, Something}
{JsonStringPerson, {"page":2,"total_results":2,"total_pages":300,"results":[{"name":"Arya","lastname":"Stark"},{"name":"Night","lastname":"King"}]}}
{name, Arya}

12
不太干净的方法……这违反了泛型<T>的概念。任何带有序列化的数据类都应该被接受。 - softmarshmallow

4
我这样做,不需要使用hackey和奇怪的“查看键”方法。我有点惊讶在文档中看到了那种方法。
典型JsonSerializable类的附加内容包括:
  • 第4行上的@_Converter()
  • _Converter<T>类位于Response<T>类下面
这里DataModels也是JsonSerializable
@JsonSerializable()
class Response<T> {
  final int count;
  @_Converter()
  final List<T> results;

  Response(this.count, this.results);

  factory Response.fromJson(Map<String, dynamic> json) => _$ResponseFromJson<T>(json);

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

class _Converter<T> implements JsonConverter<T, Object?> {
  const _Converter();

  @override
  T fromJson(Object? json) {
    switch (T) {
      case DataModel1:
        return DataModel1.fromJson(json as Map<String, dynamic>) as T;
      case DataModel2:
        return DataModel2.fromJson(json as Map<String, dynamic>) as T;
      case DataModel3:
        return DataModel3.fromJson(json as Map<String, dynamic>) as T;
      default:
        throw UnsupportedError('Unsupported type: $T');
    }
  }

  @override
  Object? toJson(T object) => object;
}

4
如果您正在使用JsonSerializable和build_runner,可以让您的模型继承自一个抽象类,并在该抽象类中定义一个方法,调用JsonSerializable生成的代码中的_$xxxFromJson(Map json)方法,示例如下:
abstract class FromJsonModel<T> {
  T fromJson(Map<String, dynamic> json);
  static Type typeOf<T>() => T;
}

@JsonSerializable()
class Shop extends FromJsonModel<Shop>{
  // Must be nullable for default ctor
  String? name;

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

  @override
  Shop fromJson(Map<String, dynamic> json) => _$ShopFromJson(json);
}

@JsonSerializable()
class Product extends FromJsonModel<Product>{
  // Must be nullable for default ctor
  String? name;

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

  @override
  Product fromJson(Map<String, dynamic> json) => _$ProductFromJson(json);
}

当您连接到REST端点时,请使用以下工厂方法,并调用您的模型的fromJson方法,如下所示。

class MyClient {
  Future<T> get<T extends FromJsonModel<T>>(Uri url,
      {Map<String, String>? headers}) async {
    final response =
        await http.get(url, headers: headers).timeout(Duration(seconds: 5));
    dynamic jsonResult = jsonDecode(response.body);
    FromJsonModel model =
        FromJsonModelFactory.get(FromJsonModel.typeOf<T>().toString());
    return model.fromJson(jsonResult);
  }

  Future<List<T>> getList<T extends FromJsonModel<T>>(Uri url,
      {Map<String, String>? headers}) async {
    final response =
        await http.get(url, headers: headers).timeout(Duration(seconds: 5));
    dynamic jsonResult = jsonDecode(response.body);

    if (jsonResult is Iterable) {
      FromJsonModel model = FromJsonModelFactory.get(FromJsonModel.typeOf<T>().toString());
      return jsonResult.map<T>((e) => model.fromJson(e) as T).toList();
    }

    return [];
  }
}

class FromJsonModelFactory {

  static Map<String, FromJsonModel> _processorMap = {
    '$Product': Product(),
    '$Shop': Shop(),
  };

  static FromJsonModel get(String type) {
    if (_processorMap[type] == null) {
      throw new Exception("FromJsonModelFactory: $type is not a valid FromJsonModel type!");
    }

    return _processorMap[type]!;
  }
}

最后调用客户端的 get / getList 方法。
class ProductService {
  late MyClient myClient;

  ProductService() {
    myClient = new MyClient();
  }

  Future<List<Product>> findRecommendedByLocation(Location location, int pageNo) async {
    return myClient.getList(Uri.parse("${URLs.productRecommendedByLocation}/${location}/$pageNo"), headers: HttpSettings.headers);
  }

  Future<Product> findById(String productId) async {
    return myClient.get(Uri.parse("${URLs.product}/$productId"), headers: HttpSettings.headers);
  }
}

当您创建新的模型时,您必须修改FromJsonModelFactory。但如果不能使用dart:mirrors选项,则实际上这很有效。

希望有人会发现这很有用。


不确定为什么这个解决方案得到的关注很少。它以一种非常通用的方式处理问题,没有大量的if语句(这不是惯用法)。 - Denis The Menace
解决方案很好,但是有没有办法避免模型中的可空字段? - Łukasz Kupiński

2

具有泛型的类应该像这样:

import 'package:json_annotation/json_annotation.dart';

part 'result.g.dart';

@JsonSerializable(explicitToJson: true, genericArgumentFactories: true) //<-- here
class Result<T> {
  final T data;
  final bool isSuccess;
  final String? message;

  Result(this.data,this.message,this.isSuccess);

//and here -->
  factory Result.fromJson(Map<String, dynamic> json, T Function(Object? json) fromJsonT,) => _$ResultFromJson(json, fromJsonT);

  Map<String, dynamic> toJson(Object Function(T value) toJsonT) => _$ResultToJson(this, toJsonT);
}

其他没有泛型的模型类:

import 'package:json_annotation/json_annotation.dart';

part 'login_response.g.dart';

@JsonSerializable()//<-- here
class LoginResponse{
  @JsonKey(name: "access_token")
  final String? accessToken;

  LoginResponse(this.accessToken);

// and here -->
  factory LoginResponse.fromJson(Map<String,dynamic> json) => _$LoginResponseFromJson(json);
  Map<String,dynamic> toJson() => _$LoginResponseToJson(this);
}

使用 dio

    final response = await dio.get(path);
///response.data is json
    var res =  Result<LoginResponse>.fromJson(response.data, (data) => LoginResponse.fromJson(data as Map<String, dynamic>));
//log it ( use explicitToJson: true in Result class to print all properties):
    logger.i("res : ",res.toString());

1
这个对我有效。 - Nejat Njono
1
这个对我有效。 - undefined
上面提到的 fromJsonT 方法,它是否应该自动生成?在我的代码中似乎没有生成。 - undefined
@atreeon 的 fromJsonT 和 toJsonT 是输入函数的名称。 - undefined
哦,是的,抱歉,我明白了! - undefined

1
假设我们有这两个相似的JSON,它们包含了一个通用类型的项目列表。
{
   "items":[
      {
         "animalName" : "cat",
         "eyeColor" : "green"
      },
      {
         "personName" : "dog",
         "eyeColor" : "black"
      }  ]
}

{
   "items":[
      {
         "productId" : 123,
         "productName" : "Car"
      },
      {
         "productId" : 567,
         "productName" : "Pencile"
      }
   ]
}

这里就是魔法所在

class ItemsNetwork<T> {
  late List<T> _items;

  List<T> get items => _items;

  T Function(Map<String, dynamic>) itemFromJson;

  ItemsNetwork({
    required this.itemFromJson,
  });

  ItemsNetwork<T> fromJson(Map<String, dynamic> json) {
    _items = (json['items'] as List<dynamic>)
        .map((e) => itemFromJson.call(e as Map<String, dynamic>))
        .toList();
    return this;
  }
}

然后你可以像下面这样使用它:

List<Animal> animals = ItemsNetwork(itemFromJson: Animal.fromJson).fromJson(jsonMap).items;


List<Animal> products = ItemsNetwork(itemFromJson: Product.fromJson).fromJson(jsonMap).items;

0

这是我的方法:

class Wrapper<T, K> {
  bool? isSuccess;
  T? data;

  Wrapper({
    this.isSuccess,
    this.data,
  });

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

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

Wrapper<T, K> _$WrapperFromJson<T, K>(Map<String, dynamic> json) {
  return Wrapper<T, K>(
    isSuccess: json['isSuccess'] as bool?,
    data: json['data'] == null ? null : Generic.fromJson<T, K>(json['data']),
  );
}

class Generic {
  /// If T is a List, K is the subtype of the list.
  static T fromJson<T, K>(dynamic json) {
    if (json is Iterable) {
      return _fromJsonList<K>(json) as T;
    } else if (T == LoginDetails) {
      return LoginDetails.fromJson(json) as T;
    } else if (T == UserDetails) {
      return UserDetails.fromJson(json) as T;
    } else if (T == Message) {
      return Message.fromJson(json) as T;
    } else if (T == bool || T == String || T == int || T == double) { // primitives
      return json;
  } else {
      throw Exception("Unknown class");
    }
  }

  static List<K> _fromJsonList<K>(List<dynamic> jsonList) {
    return jsonList?.map<K>((dynamic json) => fromJson<K, void>(json))?.toList();
  }
}

为了添加对新数据模型的支持,只需将其添加到Generic.fromJson中:

else if (T == NewDataModel) {
  return NewDataModel.fromJson(json) as T;
}

这适用于任何通用对象:

Wrapper<Message, void>.fromJson(someJson)

或者泛型对象列表:

Wrapper<List<Message>, Message>.fromJson(someJson)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接