all my JSON data contains status(int), msg(String), and data(any Type). Because I'm come from java ,I want use generics。I'm writing a deserialize for a top generics with built_value, but failed.
I have try this
https://github.com/google/built_value.dart/blob/master/end_to_end_test/test/generics_serializer_test.dart.
But do not really understand.
There follows my code:
abstract class GenericValue<T>
implements Built<GenericValue<T>, GenericValueBuilder<T>> {
T get data;
int get status;
String get msg;
GenericValue._();
static Serializer<GenericValue> get serializer => _$genericValueSerializer;
factory GenericValue([updates(GenericValueBuilder<T> b)]) =
_$GenericValue<T>;
}
abstract class UserInfo implements Built<UserInfo, UserInfoBuilder> {
static Serializer<UserInfo> get serializer => _$userInfoSerializer;
String get id;
String get email;
UserInfo._();
factory UserInfo([updates(UserInfoBuilder b)]) = _$UserInfo;
}
GenericValue<UserInfo> parseUserInfo(String jsonStr) {
final parsed = json.jsonDecode(jsonStr);
final specifiedType = const FullType(GenericValue, [FullType(UserInfo)]);
final serializersWithBuilder = (standardSerializers.toBuilder()
..addBuilderFac`enter code here`tory(specifiedType, () => GenericValueBuilder<UserInfo>
()))
.build();
Response<UserInfo> response = serializersWithBuilder.deserialize(parsed,
specifiedType: specifiedType);
return response;
}
but result is: Invalid argument(s): Unknown type on deserialization. Need either specifiedType or discriminator field.
how can it do it in right way, to deserialize JSON data like this.
String toJsonUserInfo() {
final specifiedType = const FullType(GenericValue, [FullType(UserInfo)]);
final serializersWithBuilder = (standardSerializers.toBuilder()
..addBuilderFactory(
specifiedType, () => GenericValueBuilder<UserInfo>()))
.build();
return json.encode(
serializersWithBuilder.serialize(this, specifiedType: specifiedType));
}
static GenericValue<UserInfo> fromJsonUserInfo(String jsonString) {
final specifiedType = const FullType(GenericValue, [FullType(UserInfo)]);
final serializersWithBuilder = (standardSerializers.toBuilder()
..addBuilderFactory(
specifiedType, () => GenericValueBuilder<UserInfo>()))
.build();
return serializersWithBuilder.deserialize(json.decode(jsonString),
specifiedType: specifiedType);
}
it works.
Related
I am getting the following error message when using argument matcher, any, when mocking a method in dart tests using mockito in a null safe dart code base.
What steps need to be taken to fix this issue
error:
The argument type 'Null' can't be assigned to the parameter type 'int'.
Test code can be found here:
class MockNumberTriviaRepository extends Mock implements NumberTriviaRespository {}
void main() {
late GetConcreteNumberTrivia usecase;
late MockNumberTriviaRepository mockNumberTriviaRepository;
setUp(() {
mockNumberTriviaRepository = MockNumberTriviaRepository();
usecase = GetConcreteNumberTrivia(mockNumberTriviaRepository);
});
const tNumber = 1;
const tNumberTrivia = NumberTrivia(number: tNumber, text: "test");
test('should get trivia for the number from repository', () async {
//arrange
when(mockNumberTriviaRepository.getConcreteNumberTrivia(any)).thenAnswer((_) async => const Right(tNumberTrivia));
//act
final result = await usecase.execute(tNumber);
//assert
// UseCase should simply return whatever was returned from the Repository
expect(result, const Right(tNumberTrivia));
// Verify that the method has been called on the Repository
verify(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
verifyNoMoreInteractions(mockNumberTriviaRepository);
});
}
Implementation code can be found here:
abstract class NumberTriviaRespository {
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int number);
Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia();
}
abstract class Failure extends Equatable {
const Failure([List properties = const <dynamic>[]]);
}
class GetConcreteNumberTrivia {
final NumberTriviaRespository respository;
const GetConcreteNumberTrivia(this.respository);
Future<Either<Failure, NumberTrivia>> execute(int number) async {
return await respository.getConcreteNumberTrivia(number);
}
}
class NumberTrivia extends Equatable {
final String text;
final int number;
const NumberTrivia({required this.text, required this.number});
#override
List<Object?> get props => [text, number];
}
Mockito has issues with Dart Null-safety. Please see https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md.
You can override the implementation of your mock class to support a null argument by following the recipes on the link above:
class MockNumberTriviaRepository extends Mock
implements NumberTriviaRespository {
#override
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int? number) =>
super.noSuchMethod(Invocation.method(#getConcreteNumberTrivia, [number]),
returnValue: Future.value(
Right<Failure, NumberTrivia>(NumberTrivia(text: "", number: 1))));
}
I have several APIs defined in a TestFeignClient:
#FeignClient(name = "testFeign", url = "xxx")
public interface TestFeignClient {
#CustomAnnotation(value = 1)
#GetMapping("/test2")
String test1();
#CustomAnnotation(value = 2)
#GetMapping("/test2")
String test2();
}
also I define a custom decoder like this:
public class MyDecoder implements Decoder {
#Override
public Object decode(Response response, Type type) {
// how to get the #CustomAnnotation value in this method ?
return null;
}
}
my question is how can I get the #CustomAnnotation value in the decoder method ??
I'm brand new to Dart so I've just been getting my feet wet implementing an SDK for an existing API,
but I'm running into some issues figuring out how to structure things to cut down on boilerplate when
I'm parsing the responses from the server.
Basically, all responses from the API look like the following:
{
"data": {
...
},
"error": {
...
}
}
where either data or error is present, but never both. Fairly standard stuff. The shape of the
error isn't all that interesting since it's the same for every response, but the data object is
different for every response. So far I've been able to get something "working" by declaring a base class
that handles the error and passing that up through super (I should note I'm using the
json_serializer package):
class Response {
Response({this.error});
Error? error;
}
#JsonSerializable(createToJson: false)
class ExampleResponse extends Response {
ExampleResponse({
this.data,
Error? error,
}) : super(error: error);
Map<String, dynamic>? data;
factory ExampleResponse.fromJson(Map<String, dynamic> json) =>
_$ExampleResponseFromJson(json);
}
This works but I lose all of the type information for the data field. I'm not sure how to approach this
from here that doesn't involve yet another class for the data shape:
#JsonSerializable()
class Thing {
Thing(this.name);
String name;
factory Thing.fromJson(Map<String, dynamic> json) => _$ThingFromJson(json);
String toString() => 'Thing{name: $name}';
}
#JsonSerializable()
class ExampleResponseData {
ExampleResponseData(this.thing);
Thing thing;
factory ExampleResponseData.fromJson(Map<String, dynamic> json) =>
_$ExampleResponseDataFromJson(json);
String toString() => 'ExampleResponseData{thing: $thing}';
}
#JsonSerializable(createToJson: false)
class ExampleResponse extends Response {
ExampleResponse({
this.data,
Error? error,
}) : super(error: error);
ExampleResponseData? data;
factory ExampleResponse.fromJson(Map<String, dynamic> json) =>
_$ExampleResponseFromJson(json);
String toString() => 'ExampleResponse{error: $error, data: $data}';
}
In Go I would just define an inner struct for occassions where the type has no value outside of JSON
parsing like:
type ExampleResponse struct {
Error *Error `json:"error"`
Data *struct {
Thing Thing `json:"thing"`
} `json:"thing"`
}
but I'm a lot rustier with OOP in general and on even more unfamiliar ground with Dart. Is there a
better way to handle this that doesn't end up with twice as many types as there are responses?
You can find solution based on built_value. Idea is to use generic for data type. Built value can handle such cases, though you will have a little bit of boilerplate. Below you can find example:
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
part 'built_value_example.g.dart';
void main(List<String> arguments) {
final resultAJson = endpointA();
final resultA = serializers.deserialize(resultAJson, specifiedType: FullType(Response, [FullType(DataA)]));
print(resultA);
final resultBJson = endpointB();
final resultB = serializers.deserialize(resultBJson, specifiedType: FullType(Response, [FullType(DataB)]));
print(resultB);
}
Map<String, dynamic> endpointA() {
final data = DataA((b) => b..dataAField = 7);
final response = Response<DataA>((b) => b..data = data);
return serializers.serialize(response, specifiedType: FullType(Response, [FullType(DataA)])) as Map<String, dynamic>;
}
Map<String, dynamic> endpointB() {
final data = DataB((b) => b..dataBField = 'data b value');
final response = Response<DataB>((b) => b..data = data..error = 'data b error');
return serializers.serialize(response, specifiedType: FullType(Response, [FullType(DataB)])) as Map<String, dynamic>;
}
abstract class Response<DATA_TYPE> implements Built<Response<DATA_TYPE>, ResponseBuilder<DATA_TYPE>> {
Response._();
factory Response([Function(ResponseBuilder<DATA_TYPE> b) updates]) = _$Response<DATA_TYPE>;
static Serializer<Response> get serializer => _$responseSerializer;
DATA_TYPE get data;
String? get error;
}
abstract class DataA implements Built<DataA, DataABuilder> {
DataA._();
factory DataA([Function(DataABuilder b) updates]) = _$DataA;
static Serializer<DataA> get serializer => _$dataASerializer;
int get dataAField;
}
abstract class DataB implements Built<DataB, DataBBuilder> {
DataB._();
factory DataB([Function(DataBBuilder b) updates]) = _$DataB;
static Serializer<DataB> get serializer => _$dataBSerializer;
String get dataBField;
}
#SerializersFor([
DataA,
DataB,
Response,
])
final Serializers serializers = (_$serializers.toBuilder()
..addPlugin(StandardJsonPlugin())
..addBuilderFactory(FullType(Response, [FullType(DataA)]), () => ResponseBuilder<DataA>())
..addBuilderFactory(FullType(Response, [FullType(DataB)]), () => ResponseBuilder<DataB>()))
.build();
I want to parse http response to Dart object,so I defined a abstract class
BaseBean:
abstract class BaseBean{
BaseBean.fromJson(Map<String, dynamic> json);
Map<String, dynamic> toJson();
}
And I used it in a function:
Future<ResultData<T>> netFetch<T extends BaseBean>(){
......
return new ResultData(T.fromJson(), result, code);
}
but T.fromJson() has an error:
The method 'fromJson' isn't defined for the class 'Type'
So,can I use generics in Dart like this?Is there a better way to solve this problem?
Yes, of course, it is possible, but only with a workaround:
T unmarshal<T>(Map map, {Type type}) {
if (type == null) {
type = T;
}
switch (type) {
case Order:
return Order.fromJson(map) as T;
case OrderItem:
return OrderItem.fromJson(map) as T;
case Product:
return Product.fromJson(map) as T;
default:
throw StateError('Unable to unmarshal value of type \'$type\'');
}
}
var order = unmarshal<Order>(data);
//
var product = unmarshal(data, type: Product) as Product;
//
var type = <String, Type>{};
types['OrderItem'] = OrderItem;
// ...
var type = types['OrderItem'];
var orderItem = unmarshal(data, type: type);
I used the same approach and I came to this solution.
I created a Map<Type, BaseBean Function(dynamic)> decoders.
It looks like
final decoders = {
MyClass: (data) => MyClass.fromJson(data),
};
and I get the function in this way
final MyClass myData = decoders[T]!(data);
You can also create a typedef for the BaseBean Function(dynamic) like this
typedef Decoder = BaseBean Function(dynamic);
So the Map<Type, BaseBean Function(dynamic)> will became Map<Type, Decoder>.
I am new to flutter and getting type error. I am trying to use json automated serializations.
AFTER DOING SOME TWEAKS HERE IS HOW IT LOOKS LIKE
Here is how I am trying to get the data from api
Future getMyProduct() async {
final res = await http.get('url');
final data = json.decode(res.body);
BaseResponse req = new BaseResponse.fromJson(data);
return req;
}
My BaseResponse class looks like this
import 'package:dynamicapp/model/model.dart';
import 'package:json_annotation/json_annotation.dart';
part 'response.g.dart';
#JsonSerializable()
class BaseResponse extends Object {
final int id;
final int sellingPrice;
final int totalStock;
final String productName;
final String productDesc;
final List<Image> images;
BaseResponse(this.id, this.sellingPrice, this.totalStock, this.productName,
this.productDesc, this.images);
factory BaseResponse.fromJson(Map<String, dynamic> json) => _$BaseResponseFromJson(json);
Map<String, dynamic> toJson() => _$BaseResponseToJson(this);
}
#JsonSerializable()
class Image extends Object {
final int id;
final String image;
// final int product_id;
Image(this.id, this.image);
factory Image.fromJson(Map<String, dynamic> json) => _$ImageFromJson(json);
Map<String, dynamic> toJson() => _$ImageToJson(this);
}
Could anyone please help me with this. I am stuck here. Have been trying different methods but none working. Thank you.
It looks like data is a List<dynamic>, then data.map(someFunc).toList() will take each element of data pass it to someFunc and form it back into a list of the return type of someFunc (which you will presumably want to be BaseResponse). Which tells you that someFunc needs to be a function that takes dynamic and returns BaseResponse.
You'd want to write something like this:
final data = json.decode(res.body);
List<BaseResponse> responses =
data.map((j) => BaseResponse.fromJson(j)).toList();