dart/Flutter: Problems with Decodeing json with utf8 decode - json

I try to load a json-file to put it in a filterable/searchable Listview (search for a diagnosis with a symptom). I'm new to in programming so probably there is a better / simpler way to do this but i would like to do it this way, so it doesnt get more complicated.
I get this error if i try to use utf8.decode:
"The argument type 'String' can't be assigned to the parameter type 'List'."
This is what i tried:
class Services {
static Future<List<User>> getUsers() async {
final response = await rootBundle.loadString('assets/diff.json');
List<User> list = parseUsers(response);
return list;
}
static List<User> parseUsers(String responseBody) {
final parsed = json.decode(utf8.decode(responseBody)).cast<Map<String, dynamic>>();
return parsed.map<User>((json) => User.fromJson(json)).toList();
}
}
the User Class:
class User {
String symptom;
String diagnosis;
User(this.symptom, this.diagnosis);
User.fromJson(Map<String, dynamic> json){
symptom = json['symptom'];
diagnosis = json['diagnosis'];
}
}
extract of the json file:
[
{"symptom":"Kopfschmerz","diagnosis":"Migräne, Spannungskopfschmerz"}
,
{"symptom":"Bauchschmerz","diagnosis":"Apendizitis, Infektion"}
]
Is there a simple way to make this work? Thanks!

With dynamic json.decode(String) the returned object can have a real type of:
List<dynamic>
Map<String, dynamic>
But also when the type is List<dynamic>, decode has parsed also the items in the List, so in your case (since your json has structure [{"" : ""}]) you just need to cast the (reified) List's type parameter with the cast() method.
static List<User> parseUsers(String responseBody) {
//final parsed = json.decode(utf8.decode(responseBody)).cast<Map<String, dynamic>>();
final parsed = (json.decode(responseBody) as List<dynamic>).cast<Map<String, dynamic>>();
return parsed.map<User>((json) => User.fromJson(json)).toList();
}

Related

Flutter store List of custom Classes in Shared Preferences

I have List of custom objects which also have fields with non-primitive datatypes. I would like to store a list of these objects in SharedPreferences. This is my List and also the simple Data:
#JsonSerializable()
class MedicamentsBase {
final List<Medicament> data;
const MedicamentsBase({
required this.data,
});
factory MedicamentsBase.fromJson(Map<String, dynamic> json) =>
_$MedicamentsBaseFromJson(json);
Map<String, dynamic> toJson() => _$MedicamentsBaseToJson(this);
}
#JsonSerializable()
class Medicament {
#JsonKey(name: '_id')
String id;
String name;
int dosageAmount;
String dosageType;
DateTime startDate;
DateTime endDate;
List<DateTime> timesOfDay;
String intakeContext;
bool notify;
int notificationId;
Medicament({
required this.name,
required this.dosageAmount,
required this.dosageType,
required this.startDate,
required this.endDate,
required this.timesOfDay,
required this.intakeContext,
required this.notify,
required this.notificationId,
required this.id,
});
factory Medicament.fromJson(Map<String, dynamic> json) =>
_$MedicamentFromJson(json);
Map<String, dynamic> toJson() => _$MedicamentToJson(this);
}
I already serialized them. But if I try to store the MedicamentsBase like this:
static Future<void> setMedicaments(List<Medicament> medicaments) async {
final SharedPreferences _sharedPreferences =
await SharedPreferences.getInstance();
await _sharedPreferences.setStringList(
'key',
medicaments.map((medicament) => json.encode(medicament)).toList(),
);
}
Things are not working as expected if trying to get the List back like this:
static Future<List<Medicament>> getMedicaments() async {
final SharedPreferences _sharedPreferences =
await SharedPreferences.getInstance();
return MedicamentsBase.fromJson(
_sharedPreferences.getStringList('key') as Map<String, dynamic>,
).data;
}
I think it is because Medicament also has Strings and DateTimes in it. What is the correct way to store and get this kind of data in Shared Preferences?
Shared preferences might not be the way to go if you are intending on saving more complex data types, I think your options are:
Checking out on how to use the hive package
Trying to save primitive type which you can later use to reconstruct your complex data types with using shared preferences
Create a ToJson / FromJson functions in your complex data types, and use the file.dart function which are available as part of flutter to save this data as a json file, take alook at those functions which should be useful to do so:
write the file:
Directory applicationDocumentDirectory =
await getApplicationDocumentsDirectory();
File file = Directory applicationDocumentDirectory =
await getApplicationDocumentsDirectory();
Map<String, dynamic> fileJson = yourDataType.toJson();
String dataAsJsonString = jsonEncode(fileJson);
songDataFile.writeAsStringSync(dataAsJsonString);
String fileData = file.readAsStringSync();
return jsonDecode(fileData);
read the file:
Directory applicationDocumentDirectory =
await getApplicationDocumentsDirectory();
File file = Directory applicationDocumentDirectory =
await getApplicationDocumentsDirectory();
String fileData = file.readAsStringSync();
return jsonDecode(fileData);
Hopefully those ideas help :)

How to get value from Json List - flutter

I have this code where i get json data into a list in flutter but i don't really know how to get the particular data i want like the value
main.dart
Future<String> loadDataFromJson() async {
return await rootBundle.loadString("assets/categories.json");
}
Future loadData() async {
String jString = await loadDataFromJson();
final jRes = json.decode(jString) as List;
List<Category> datas = jRes.map((e) => Category.fromJson(e)).toList();
print(datas);
}
#override
void initState() {
super.initState();
loadData();
}
Here I printed the data and it gave me this I/flutter ( 6111): [Instance of 'Category', Instance of 'Category', Instance of 'Category', Instance of 'Category']
Models
class Category {
final String catId;
final String catName;
Category({this.catId, this.catName});
factory Category.fromJson(Map<String, dynamic> json) {
return Category(catId: json['cat_id'], catName: json['category']);
}
}
my json is something like this but there are multiple
{
"category": "Design & Creativity",
"cat_id": "1",
"cat_suncategory": [
{
"sub_name": "Ads",
"sub_image": "https://images.unsplash.com/photo-1589838017489-9198a27bd040?ixid=MXwxMjA3fDB8MHxzZWFyY2h8Mnx8YWR2ZXJ0aXNlbWVudHxlbnwwfHwwfA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60"
}
]
}
So please how do i get the value i want
I cannot understand your problem but this may help you;
If your json values in 'jRes', you can do
String myCategory = jRes["category"];
String subName = jRes["cat_suncategory"][0]["sub_name"];
String subImage = jRes["cat_suncategory"][0]["sub_image"];
Because of using '[0]' is; the 'cat_suncategory' is an array and you should take first element of it, it means [0].

Dynamic List in Flutter for Json

I'm working with some complex json in dart, and I have an issue creating objects before I know what type they'll be.
I appreciate the suggestions, but I don't think I completely understand. In the given answer:
var entity = Model();
castToEntity(entity, {'test': 10});
Don't I need to know that it will be a Model class?
What if I have the below two classes:
#JsonSerializable(explicitToJson: true, includeIfNull: false)
class Location {
String id;
String resourceType;
Location({#required this.id, this.resourceType})
factory Location.fromJson(Map<String, dynamic> json) => _$LocationFromJson(json);
Map<String, dynamic> toJson() => _$LocationToJson(this);
}
class Reference {
String reference;
String resourceType;
Location({#required this.reference, this.resourceType}
factory Reference.fromJson(Map<String, dynamic> json) => _$ReferenceFromJson(json);
Map<String, dynamic> toJson() => _$ReferenceToJson(this);
}
And then I query the server, and I don't know what kind of class it will be. It could be a Location, or a Reference, or if it's a list, it could be multiple of both, and I don't know until I've requested it.
var myBundle = Bundle.fromJson(json.decode(response.body));
Each "myBundle.entry" is another resource. I'd like to be able to use information from that resource to define itself. So I could do something like:
myBundle.entry.resourceType newResource = new myBundle.entry.resourceType();
What I'm doing right now is sending it to a function that has all of the possible options predefined:
var newResource = ResourceTypes(myBundle.entry[i].resource.resourceType,
myBundle.entry[i].resource.toJson());
dynamic ResourceTypes(String resourceType, Map<String, dynamic> json) {
if (resourceType == 'Location') return (new Location.fromJson(json));
if (resourceType == 'Reference') return (new Reference.fromJson(json));
}
It was said that there's not reflection in dart, so I didn't know any other way to do it.
As far as I know, it's not possible since Dart doesn't have Reflection like c#, the most close that I can imagine, is using an abstract class that enforces your entity to implement fromJson, and, in that method, you read the Map and put values into fields, like the code below:
abstract class Serializable {
void fromJson(Map<String,dynamic> data);
}
class Model implements Serializable {
int test;
#override
void fromJson(data) {
test = data['test'];
}
}
Serializable castToEntity(Serializable entity, Map<String, dynamic> data) {
return entity..fromJson(data);
}
Now, when you read you database and have the Map, you can call a method generic like:
var entity = Model();
castToEntity(entity, {'test': 10});
print(entity.test);
Where entity is an empty model.
Note: Your fields on entity, cannot be final, since fromJson is an instance method and not a factory method.

Flutter - How to parsed nested json to a class with generics?

I'm wondering how can I parse a nested json to a class with generic types. My intention is to wrap responses from the backend (like loginRespose that contains a token) with a code and a message
I have
class BaseResponse<T>{
int code;
String message;
T responseObject;
BaseResponse.fromJson(Map<String, dynamic> parsedJson)
: code = parsedJson['Code'],
message = parsedJson['Message'],
responseObject = T.fromJson(parsedJson['ResponseObject']); //This is what I'd like to do
}
Obviously the last line throws an error because T doesn't has a named constructor "fromJson".
I tried adding some restrictions to the Type but I didn't find any solutions. Do you have any idea on how to pull this off?
You can't do such thing, at least not in flutter. As dart:mirror is disabled and there's no interface for classes constructors.
You'll have to take a different route.
I'll recommend using POO instead. You would here give up on deserializing responseObject from your BaseResponse. And then have subclass of BaseResponse handles this deserialization
Typically you'd have one subclass per type:
class IntResponse extends BaseResponse<int> {
IntResponse.fromJson(Map<String, dynamic> json) : super._fromJson(json) {
this.responseObject = int.parse(json["Hello"]);
}
}
You can then hide this mess away by adding a custom factory constructor on BaseResponse to make it more convenient to use.
class BaseResponse<T> {
int code;
String message;
T responseObject;
BaseResponse._fromJson(Map<String, dynamic> parsedJson)
: code = parsedJson['Code'],
message = parsedJson['Message'];
factory BaseResponse.fromJson(Map<String, dynamic> json) {
if (T == int) {
return IntResponse.fromJson(json) as BaseResponse<T>;
}
throw UnimplementedError();
}
}
Then either instantiate the wanted type directly, or use the factory constructor :
final BaseResponse foo = BaseResponse.fromJson<int>({"Hello": "42", "Code": 42, "Message": "World"});
You can achieve this with the built_value package (you'll also need built_value_generator and build_runner). Your class will look something like this:
part 'base_response.g.dart';
abstract class BaseResponse<T> implements Built<BaseResponse<T>, BaseResponseBuilder<T>> {
int code;
String message;
T responseObject;
factory BaseResponse([updates(BaseResponseBuilder<T> b)]) = _$BaseResponse<T>;
static Serializer<BaseResponse> get serializer => _$baseResponseSerializer;
}
You will have to run flutter packages pub run build_runner build to make the generated file. Then you use it like this:
BaseResponse baseResponse = serializers.deserialize(
json.decode(response.body),
specifiedType: const FullType(BaseResponse, const [FullType(ConcreteTypeGoesHere)])
);
There's just one more bit of boilerplate you have to take care of. You need another file called serializers.dart. You need to manually add all the classes you want to deserialize here, and also an addBuilderFactory function for each class that takes a type parameter - and for each concrete type you want to use.
part 'serializers.g.dart';
#SerializersFor(const [
BaseResponse,
ConcreteTypeGoesHere,
])
final Serializers serializers = (_$serializers.toBuilder()
..addBuilderFactory(
FullType(BaseResponse, const [const FullType(ConcreteTypeGoesHere)]),
() => new BaseResponseBuilder<ConcreteTypeGoesHere>()
)
..addPlugin(StandardJsonPlugin()))
.build();
Then re-run flutter packages pub run build_runner build
Makes me wish for Gson... :S
Here is my approach:
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();
}
}
In order to add support for a new data model, simply add it to Generic.fromJson:
else if (T == NewDataModel) {
return NewDataModel.fromJson(json) as T;
}
This works with either generic objects:
Wrapper<Message, void>.fromJson(someJson)
Or lists of generic objects:
Wrapper<List<Message>, Message>.fromJson(someJson)

How does built_value deserialize json array to object array?

In built_value's official example show how to setup an array member of an object:
abstract class Collections implements Built<Collections, CollectionsBuilder> {
static Serializer<Collections> get serializer => _$collectionsSerializer;
BuiltList<int> get list;
BuiltMap<String, int> get map;
BuiltListMultimap<int, bool> get listMultimap;
factory Collections([updates(CollectionsBuilder b)]) = _$Collections;
Collections._();
}
it just demonstrate how to deserialize a map but not array, the array just a key/member not the data itself.
but in may case, my http response is an array itself, not play as a member of the response.
my model:
abstract class Post implements Built<Post, PostBuilder> {
static Serializer<Post> get serializer => _$postSerializer;
int get userId;
String get title;
factory Post([updates(PostBuilder b)]) = _$Post;
Post._();
}
my request is:
static Future<List<Post>> getPosts() async {
final response = await http.get('https://jsonplaceholder.typicode.com/posts');
if (response.statusCode == 200) {
return serializers.deserialize(
json.decode(response.body),
specifiedType: FullType(Post)
);
} else {
throw Exception('Failed to load post');
}
}
response.body:
[
{'userId': 1, 'title': 'title1'},
{'userId': 2, 'title': 'title2'}
]
I looked up every tutorial or network discussion, no one mentions this scenario, or should I can only iterate the response.body and deserialize to object one by one? for example:
static Future<List<Post>> getPosts() async {
final response = await http.get('https://jsonplaceholder.typicode.com/posts');
if (response.statusCode == 200) {
final List<dynamic> data = json.decode(response.body);
return data.map((m){
return serializers.deserializeWith(Post.serializer, m);
}).toList();
} else {
throw Exception('Failed to load post');
}
}
according to #David Morgan 's reply, built_value is not support deserialize a list yet.
Currently there’s no equivalent to deserializeWith for a list. You’ll need to deserialize one by one.