如何在Dart中使用泛型和泛型列表与json序列化?

kse8i1jr  于 2022-12-15  发布在  其他
关注(0)|答案(7)|浏览(377)

我正在用Flutter开发一个移动的项目,这个项目需要连接一些服务器进行REST消费服务(GET、POST、PUT、DELETE......),并且需要检索数据以及向它们发送数据,这些数据需要用JSON格式化,所以我决定使用Json序列化库2.0.3 for Dart,带有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>类型,它对于比较类的类型转换毫无用处。2我该如何解决我的困境呢?3我应该坚持json_serialization还是使用build_价值代替?在这件事上的任何帮助都将非常感激。
我的代码:

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);
    }
  }
}

我期望能够序列化/反序列化“最终List列表;“不管有没有“@JsonKey”,但到目前为止,我还没能找到一种方法将它强制转换成适当的json格式。
当我尝试为这个类生成代码时(使用命令“flutter packages pub run build_runner build”),我收到了以下错误:
运行JsonSerializableGenerator时出错由于T类型,无法为list生成fromJson代码。提供的TypeHelper示例均不支持定义的类型。包:json_generic.dart:11:17

╷
11 │   final List<T> list;
   │                 ^^^^
   ╵
cx6n0qe3

cx6n0qe31#

这里有个例子
https://github.com/dart-lang/json_serializable/blob/master/example/lib/json_converter_example.dart
// json转换器示例.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;
}

//主省道

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}
kuarbcqp

kuarbcqp2#

这是我的正确解决方案完美地为我工作。

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)))
    );
  }
}

假设我们要使用飞行模型分页模型。在这里你必须配置飞行列表。

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);
avwztpqn

avwztpqn3#

json_可序列化

json_serializable有几个策略1来处理泛型类型作为单个对象TList<T>(从v. 5.0.2+开始):
1.帮助程序类:JsonConverter
1.助手方法:@JsonKey(fromJson:, toJson:)
1.泛型参数工厂@JsonSerializable(genericArgumentFactories: true)
1我知道。可能还有其他方法。

帮助程序类:JsonConverter

基本思想:使用fromJsontoJson方法编写一个自定义JsonConverter类,以识别和处理我们的Type T字段反序列化/序列化。
JsonCoverter策略的好处是它将模型的所有反序列化/序列化逻辑封装到一个类中,该类可在需要序列化的相同模型类型的任何类中重用,并且您的toJsonfromJson调用不会改变,这与泛型参数工厂策略相反,在泛型参数工厂策略中,每个toJsonfromJson调用要求我们提供一个处理函数。
我们可以使用JsonConverter和我们的对象进行反序列化,注解如下:

  • 需要自定义处理的单个T/List<T>字段,或
  • 整个类(其中它将用于T类型的任何/所有字段)。

下面是一个包含泛型类型字段T的json_serializable类OperationResult<T>的示例。
关于OperationResult类的说明:

  • 具有单个通用类型字段T t
  • t可以是T类型的单个对象***或***对象的List<T>
  • 无论T是什么类型,它都必须具有toJson()/fromJson()方法(即,可反串行化/可串行化)。
  • 具有名为**ModelConverter**的JsonConverter类,用于注解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.');
  }

}

下面是Serializable接口,用于实现我们的模型类,如OrderMenuItem(请参见上面ModelConvertertoJson()代码,以了解如何/为什么使用它):

/// 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为类中的 * 任何 * 类型的字段指定自定义反序列化/序列化处理程序,而不仅仅是泛型类型。
因此,我们可以为泛型类型字段T t指定自定义处理程序,使用与上面JsonConverter示例中相同的“查看键”逻辑。
下面,我们向类OperationResultJsonKey<T>添加了两个静态方法(在这个Stackoverflow示例中,这样命名只是为了清楚起见):

  • _fromJson
  • _toJson

(这些函数也可以作为顶级函数存在于类之外。)
然后我们向JsonKey提供这两个方法:
@JsonKey(fromJson: _fromJson, toJson: _toJson)
然后,重新运行flutter或dart(flutter pub run build_runner builddart run build_runner build)的build_runner之后,json_serializable提供的生成的反序列化方法将使用这两个静态方法。

/// 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)

在最后一种反序列化/序列化的专门处理方式中,我们希望直接为OperationResult上对toJson()fromJson()的调用提供自定义反序列化/序列化方法。
这种策略可能是最灵活的(允许你指定你想要如何对每个泛型类型进行序列化处理),但是它也非常冗长,需要你在每个toJson/fromJson调用上提供一个序列化处理函数。

给杰森

例如,在序列化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>

来自杰森

对于OperationResult<Order>类中使用的genericArgumentFactoriesfromJson()端,我们需要提供一个签名函数:T Function(Object?) fromJsonT
因此,如果要反序列化/序列化的泛型类型的对象是OperationResult<Order>,则fromJson()的helper函数为: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);

如果TList对象,比如List<MenuItem>,那么我们需要处理列表的helper方法。
下面是添加到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);
46scxncf

46scxncf4#

如果您使用JsonSerializable和build_runner,您可以让您的模型从一个抽象类扩展,方法是在JsonSerializable生成的代码中调用_$xxxFromJson(Map〈String,dynamic〉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不是一个选项,那么实际上它工作得很好。
希望有人会觉得这个有用。

baubqpgj

baubqpgj5#

假设我们有两个类似的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;
doinxwow

doinxwow6#

以下是我的方法:

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)
yqyhoc1h

yqyhoc1h7#

我是这样做的,不需要hackey和奇怪的'peek at keys'方法,我有点惊讶地看到这个方法在documentation包中。
对典型JsonSerializable类的补充包括:

  • 第4行上的@_Converter()
  • Response<T>类之下的_Converter<T>

这里的数据模型也是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;
}

相关问题