Json oneOf in Dart/Flutter for FHIR resources - json

I'm trying to create Dart classes for FHIR resources defined in Json. The full Json schema for FHIR is here if anyone wants to look. My issue is with a oneOf declaration. Specifically, I have a class like the following (I'm not including the full class definition here, although I can if anyone thinks it would be helpful):
class Bundle_Entry {
Resource resource;
Bundle_Entry(this.resource});
factory Bundle_Entry.fromJson(Map<String, dynamic> json) => _$Bundle_EntryFromJson(json);
Map<String, dynamic> toJson() => _$Bundle_EntryToJson(this);
}
My problem is that ResourceList is defined as oneOf a number of other classes.
"ResourceList": {
"oneOf": [
{ "$ref": "#/definitions/Account" },
{ "$ref": "#/definitions/ActivityDefinition" },
...
]
}
I've tried declaring the 'resource' variable as types 'var', 'dynamic', and 'ResourceList' and created a class ResourceList that just contains a resource.
Each resource has a field titled 'resourceType', so I've also tried creating a ResourceList function that returns different types based on argument of 'resourceType', which also doesn't work.
If I do an http request, the actual response I'm trying to parse looks like this:
{
"resourceType": "Bundle",
"type": "searchset",
"entry": [
{
"resource": {
"name": "Jaba the Hutt"
"birthDate": "1980-07-27",
"id": "b26646dd-c549-4981-834e-bb4145d104b8",
"resourceType": "Patient"
}
}
]
}
Any suggestions would be appreciated.
Updating my question. It's interesting, that the first answer is similar to what I've come up with currently.
class Bundle_Entry {
dynamic resource;
Bundle_Entry({this.resource});
Bundle_Entry.fromJson(Map<String, dynamic> json) =>
Bundle_Entry(
resource: json['resource'] == null
? null
: ResourceTypes(
json['resource']['resourceType'], json['resource'] as Map<String, dynamic>)
);}
Map<String, dynamic> toJson() => _$Bundle_EntryToJson(this);
}
dynamic ResourceTypes(String type, Map<String, dynamic> json) {
if (type == 'Element') return (new Element.fromJson(json));
if (type == 'Extension') return (new Extension.fromJson(json));
if (type == 'Patient') return (new Narrative.fromJson(json));
My issue is that then I have to hard code each ResourceType, and it seemed like there should be an easier way.

I have been trying to build a similar thing. I approached it using inheritance and created a function to return the resource based on the ResourceType field.
Resource getResource(json) {
if(json == null) return null;
if(json["resourceType"] == "Patient") return Patient.fromJson(json);
if(json["resourceType"]=="Procedure") return Procedure.fromJson(json);
if(json["resourceType"]=="Encounter") return Encounter.fromJson(json);
if(json["resourceType"]=="Appointment") return Appointment.fromJson(json);
if(json["resourceType"]=="Slot") return Slot.fromJson(json);
if(json["resourceType"]=="Slot") return Slot.fromJson(json);
if(json["resourceType"]=="Observation") return Observation.fromJson(json);
if(json["resourceType"]=="Condition") return Condition.fromJson(json);
if(json["resourceType"]=="DiagnosticReport") return DiagnosticReport.fromJson(json);
if(json["resourceType"]=="MedicationRequest") return MedicationRequest.fromJson(json);
if(json["resourceType"]=="CarePlan") return CarePlan.fromJson(json);
if(json["resourceType"]=="Practitioner") return Practitioner.fromJson(json);
if(json["resourceType"]=="AllergyIntolerance") return AllergyIntolerance.fromJson(json);
return Resource.fromJson(json);
}
Where Patient, Procedure, etc. are subclasses of Resource:
class Entry {
String fullUrl;
Resource resource;
factory Entry.fromJson(Map<String, dynamic> json) => Entry(
fullUrl: json["fullUrl"] == null ? null : json["fullUrl"],
//This line is the magic
resource: getResource(json["resource"]),
search: json["search"] == null ? null : Search.fromJson(json["search"]),
response: json["response"] == null
? null
: Response.fromJson(json["response"]),
);

While not ideal, I did end up using the answer above, just modified slightly.
Resource _resourceFromJson(Map<String, dynamic> json) {
final dynamic resourceType = json['resourceType'];
switch (resourceType) {
case 'Account':
return Account.fromJson(json);
case 'ActivityDefinition':
return ActivityDefinition.fromJson(json);
case 'AdministrableProductDefinition':
return AdministrableProductDefinition.fromJson(json);
case 'AdverseEvent':
return AdverseEvent.fromJson(json);
case 'AllergyIntolerance':
return AllergyIntolerance.fromJson(json);
...
For anyone who runs into this. Full file is here

Related

parsing airtable json to flutter podo

I'm new in flutter and I have issue with parsing JSON on HTTP response.
I'm using Airtable backend, to store information about posts. These always contain images, and sometimes attachments - PDFs.
I built PODO, like this:
class Post {
String recordid;
String timecreated;
String title;
String content;
String imgurl;
List<Pdf>? pdf;
Post({
required this.recordid,
required this.timecreated,
required this.title,
required this.content,
required this.imgurl,
required this.pdf
});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
// fields: Fields.fromJson(json['fields']),
recordid: json['id'] as String,
timecreated: json['createdTime'] as String,
title: json['fields']['field1'] as String,
content: json['fields']['field2'] as String,
imgurl: json['fields']['IMG'][0]['url'] as String,
pdf: json['fields']['PDF'] == null ? null : List<Map<String, dynamic>>.from(json['fields']['PDF']).map((dynamic value) => Pdf.fromJson(value)).toList()
);
}
Map<String, dynamic> toJson() => {
"recordid": recordid,
"timecreated": timecreated,
"title": title,
"content": content,
"imgurl": imgurl,
"pdf": pdf
// "fields": List<dynamic>.from(fields.map((x) => x.toJson())),
};
}
class Pdf {
Pdf({
required this.url,
required this.filename
});
Pdf.fromJson(Map<String, dynamic> json) :
url = json['url'],
filename = json['filename'];
final String? url;
final String? filename;
}
I'm not getting any errors, but when I'm trying to use PDF URL in UI, eg. in Text:
ListTile(title: Text(post.pdf.url)),
I'm getting error:
The property 'url' can't be unconditionally accessed because the
receiver can be 'null'.
I'm aiming to create a button on a page, that is clickable when PDF URL exists. When it exists, button navigates to PDF view that use PDF URL to get and display PDF.
Any ideas?
The pdf attribute is nullable, hence it cannot be accessed unconditionally. This is assuming you somehow have the pdf not as a list, otherwise you would need to index your list, your code is incomplete. You could try to do something like this:
if (post.pdf != null) {
//wrap it with a button or whatever
return ListTile(title: Text(post.pdf!.url));
}
else {
return Text('no pdf');
}
Well, sth seems to work, but still cannot parse pdf URL.
I'm getting Text "sth is there" when post.pdf != null and it works, but when I'm changing to get value from model using post.pdf!.url I'm getting same error:
Try correcting the name to the name of an existing getter, or defining
a getter or field named 'url'.
child:Text(post.pdf!.url));
Here's piece of code:
LayoutBuilder(builder: (context, constraints) {
if(post.pdf != null){
return ElevatedButton(onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (context) => PDFview(pdf: [])
)),
child:Text(post.pdf!.url));
}else{
return ElevatedButton(onPressed: null,
child:Text("no PDF"));
}
}
)
A fix in PODO worked for me.
Here's solution for my problem :)
pdf: json['fields']['PDF'] == null ? null : json['fields']['PDF'][0]['url'] as String,

How to parse dynamic JSON keys in dart

I have an API that returns JSON with dynamic keys, i.e. the key value changes on every GET request.
Ex: This is the format of the JSON.
I have a book model in dart,
class Book {
String id = "";
String title = "";
Book();
Book.fromJson(Map<String, dynamic> json) {
id = json['id'];
title = json['title'];
}
static List<Book> listFromJson(List<dynamic> json) {
return json.map((val) => Book.fromJson(val)).toList();
}
}
and JSON response,
{
"Recommended For You" : [
{
"id" : "001",
"title" : "title001"
},
...
],
"Thrillers" : [
{
"id" : "005",
"title" : "title005"
},
...
]
}
How to parse this into lists of books according to the genre, given that genres (like Thrillers) can change(i.e. it is dynamic and may change for each user)?
Edit 1
Thrillers and Recommended For You aren't constant, some times it may change to Horror and Romantic or something else. It is dynamic
Edit 2 : The solution
Thanks to #lrn's solution, I came up with a new Class
class Recommendations {
String recommendationType;
List<Book> recommendations;
#override
String toString() {
return 'Recomendations[title: $recommendationType]';
}
Recommendations.fromJson(MapEntry<String, dynamic> json) {
if (json == null) return;
this.recommendationType = json.key;
this.recommendations = Book.listFromJson(json.value);
}
static List<Recommendations> listFromJson(Map<String, dynamic> json) {
return json == null
? List<Recommendations>.empty()
: json.entries
.map((value) => new Recommendations.fromJson(value))
.toList();
}
}
If you don't know the keys, but do know that it's a JSON map where the values are the book lists you're looking for, I'd write:
List<Book> parseCategorizedBooksJson(Map<String, dynamic> json) =>
[for (var books in json.values) ...Book.listFromJson(books)];
or
List<Book> parseCategorizedBooksJson(Map<String, dynamic> json) =>
[for (var books in json.values)
for (var book in books) Book.fromJson(book)];
(which avoids building intermediate lists of books).
With this you simply iterate the values of the outer map, and ignore the keys. JSON maps are just plain Dart Maps and you can access the keys as .keys (and then go trough them without needing to known them ahead of time) and access the values as .values (and completely ignore the keys).
Each value is a list of books, which you can parse using your existing static functions.
To include the genre, you have to say how you want the genre remembered. The simplest is to retain the data as a map:
var categoriedBooks = {for (var genre in json)
genre: Book.listFromJson(json[genre])
};
This builds a map with the same keys (the genres), but where the values are now lists of Books.
Your response model contains two different arrays, recommendedForYou and thrillers. So, you can handle it like this:
// To parse this JSON data, do
//
// final responseModel = responseModelFromJson(jsonString);
import 'dart:convert';
ResponseModel responseModelFromJson(String str) => ResponseModel.fromJson(json.decode(str));
String responseModelToJson(ResponseModel data) => json.encode(data.toJson());
class ResponseModel {
ResponseModel({
this.recommendedForYou,
this.thrillers,
});
List<Book> recommendedForYou;
List<Book> thrillers;
factory ResponseModel.fromJson(Map<String, dynamic> json) => ResponseModel(
recommendedForYou: json["Recommended For You"] == null ? null : List<Book>.from(json["Recommended For You"].map((x) => Book.fromJson(x))),
thrillers: json["Thrillers"] == null ? null : List<Book>.from(json["Thrillers"].map((x) => Book.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"Recommended For You": recommendedForYou == null ? null : List<dynamic>.from(recommendedForYou.map((x) => x.toJson())),
"Thrillers": thrillers == null ? null : List<dynamic>.from(thrillers.map((x) => x.toJson())),
};
}
class Book {
Book({
this.id,
this.title,
});
String id;
String title;
factory Book.fromJson(Map<String, dynamic> json) => Book(
id: json["id"] == null ? null : json["id"],
title: json["title"] == null ? null : json["title"],
);
Map<String, dynamic> toJson() => {
"id": id == null ? null : id,
"title": title == null ? null : title,
};
}
You can use this site to convert your json to any dart model.

How to check if JSON array is empty in Flutter

Trying to create an if statement that checks if an API returns 'items' containing data
Here is the API url, you can see that items is empty for that specific data query https://data.food.gov.uk/food-alerts/id?since=2021-01-04T00:00:00Z
My code is below, been trying to see if I can check 'items' to see if it is null, as well as checking for its length and see if it equals 0 but has not worked so far
class AllergyAlert {
final String title;
AllergyAlert({this.title});
factory AllergyAlert.fromJson(Map<String, dynamic> json) {
if (json['items'] == null) {
return AllergyAlert(
title: 'No new allergy alerts',
);
} else {
return AllergyAlert(
title: json['items'][0]['title'],
);
}
}
}
You can try this
return AllergyAlert(
title: json['items'].isEmpty ? 'No new allergy alerts' : json['items'][0]['title'],
);
First create a class to decode your json string with. You can paste your json here Quicktype
This can then be used with a FutureBuilder to load the data. Here is a full example
Solution
Then after that you will just use the defined names to check. Like:
class Allergen {
Allergen({
this.meta,
this.items,
});
final Meta meta;
final List<dynamic> items;
factory Allergen.fromJson(Map<String, dynamic> json) => Allergen(
meta: Meta.fromJson(json["meta"]),
items: List<dynamic>.from(json["items"].map((x) => x)),
);
Map<String, dynamic> toJson() => {
"meta": meta.toJson(),
"items": List<dynamic>.from(items.map((x) => x)),
};
}
class Meta {
Meta({...//more here
you can just check the length of the list
Allergen.items.length==0?//Do This://Else This

Flutter converting Nested Object from Json returns null

I have a Nested Object like this just a bit bigger:
"name": "String",
"exercise": [
{
"index": 1,
}
],
"pause": [
{"index":2},
]
I convert the exercise and pause to a Json String and save them in a column in SQFLite.
The problem
When I read the Data everything works fine including the List (not nested) but both list's of nested Object are empty when I read a value of the nested object it gives an error.
item.exercise[0].index.toString()
Valid Value range is empty: 0
When I read only item.exercise.toString() it returns []. Without != null ? [...] : List<Exercise>() it also throws an error
Data I get from my Database (shortened)
List of:
[{name: number 1, id: 56, exercise: [{"index":1,"weightGoal":[15,16,17]}, {"index":3,"weightGoal":[15,16,17]}], pause: [{"index":2}]},{"index":4}]}]
What I do with it
Here I try to go through the list and convert it into a List of PlanModel:
List<PlanModel> list =
res.isNotEmpty ? res.map((c) => PlanModel.fromJson(c)).toList() : [];
return list;
Full model
PlanModel planModelFromJson(String str) => PlanModel.fromJson(json.decode(str));
String planModelToJson(PlanModel data) => json.encode(data.toJson());
class PlanModel {
PlanModel({
this.name,
this.id,
this.workoutDays,
this.pastId,
this.timesDone,
this.exercise,
this.pause,
});
String name;
int id;
List<String> workoutDays;
int pastId;
int timesDone;
List<Exercise> exercise;
List<Pause> pause;
factory PlanModel.fromJson(Map<String, dynamic> json) => PlanModel(
name: json["name"],
id: json["id"],
workoutDays: List<String>.from(jsonDecode(json["workoutDays"])),
pastId: json["pastId"],
timesDone: json["timesDone"],
exercise: json["Exercise"] != null ? new List<Exercise>.from(json["Exercise"].map((x) => Exercise.fromJson(x))): List<Exercise>(),
pause: json["Pause"] != null ? new List<Pause>.from(json["Pause"].map((x) => Pause.fromJson(x))): List<Pause>(),
);
Map<String, dynamic> toJson() => {
"name": name,
"id": id,
"workoutDays": List<dynamic>.from(workoutDays.map((x) => x)),
"pastId": pastId,
"timesDone": timesDone,
"Exercise": List<dynamic>.from(exercise.map((x) => x.toJson())),
"Pause": List<dynamic>.from(pause.map((x) => x.toJson())),
};
}
class Exercise {
Exercise({
this.index,
this.name,
this.goal,
this.repGoal,
this.weightGoal,
this.timeGoal,
this.setGoal,
});
int index;
String name;
int goal;
int repGoal;
List<int> weightGoal;
int timeGoal;
List<String> setGoal;
Exercise.fromJson(dynamic json) {
// anything that is wrapped around with this [] in json is converted as list
// anything that is wrapped around with this {} is map
index = json["index"];
name = json["name"];
goal = json["goal"];
repGoal = json["repGoal"];
weightGoal = json["weightGoal"] != null ? json["weightGoal"].cast<int>() : [];
timeGoal = json["timeGoal"];
setGoal = json["setGoal"] != null ? json["setGoal"].cast<String>() : [];
}
Map<String, dynamic> toJson() => {
"index": index,
"name": name,
"goal": goal,
"repGoal": repGoal,
"weightGoal": List<dynamic>.from(weightGoal.map((x) => x)),
"timeGoal": timeGoal,
"setGoal": List<dynamic>.from(setGoal.map((x) => x)),
};
}
class Pause {
Pause({
this.index,
this.timeInMilSec,
});
int index;
int timeInMilSec;
factory Pause.fromJson(Map<String, dynamic> json) => Pause(
index: json["index"],
timeInMilSec: json["timeInMilSec"],
);
Map<String, dynamic> toJson() => {
"index": index,
"timeInMilSec": timeInMilSec,
};
}
Read this first.
You need to tweek this code a little to work for you but the idea is that;
also read comment in the code.
if json string comes with [] those around, json.decode will decode it as List<Map>.
if it comes with {} this json.decode will decode it as Map.
note: be careful while using generics on json.decode I reccommend not to.
data inside the jsonString does not really corresponds with the values inside the fromJson function. json string which you have provided was not really good. so I think you will understand how to manipulate it for your needs.
also main constructor Exercise you can use for initial data.
import 'dart:convert';
class Exercise{
Exercise({this.index,
this.name,
this.repGoal,
this.weightGoal,
this.setGoal});
String index;
String name;
String repGoal;
String weightGoal;
String setGoal;
Exercise.fromJson(dynamic json) :
// anything that is wrapped around with this [] in json is converted as list
// anything that is wrapped around with this {} is map
index = json["exercise"][0]["index"].toString(),
name = json["name"].toString(),
repGoal = json["repGoal"].toString(),
weightGoal = json["weightGoal"].toString(),
setGoal = json["setGoal"].toString();
}
void main(){
String jsonString = '{name: number 1, id: 56, exercise: [{"index":1,"weightGoal":[15,16,17], pause: [{"index":2}]}';
Map json = json.decode(jsonString);
Exercise.fromJson(json);
}
I found it out :)
I have restructured my fromJson to this, especially the jsonDecode was important, because json["exercise "] was only a String.
PlanModel.fromJson(dynamic json) {
name = json["name"];
if (json["exercise"] != null) {
exercise = [];
jsonDecode(json["exercise"]).forEach((v) {
exercise.add(Exercise.fromJson(v));
});
}}
now I can access it with
PlanModel item = snapshot.data[index];
item.exercise[0].timeGoal.toString()

Unable to load JSON data to show in ListView

I need to get the json array data in flutter app by providing the get method.
The problem that I am facing is that I am not able to put the records in the empty list carList.
but I am getting the array of objects as shown below(POSTMAN) by
print(json.decode(response.body)) // in the file cars.dart
Hence if you could please help me on how to get the response in the List carlist = [];
Car_list.dart file calls the method for the get request.
Please let me know if you require any further information from my end.
Thankyou
POSTMAN
[
{
"id": "1",
"carModel" : "Maruti swift",
"carDescription" : "The car has a top speed of 180 km/hr"
},
{
"id": "1",
"carModel" : "Hyundai santro",
"carDescription" : "The car has a top speed of 150 km/hr"
},
{
"id": "1",
"carModel" : "Hyundai creta",
"carDescription" : "The car has a top speed of 160 km/hr"
}
]
CarsModel.dart
class Cars with ChangeNotifier{
String userId;
String carModel
String carDescription;
Cars(
{
this.userId = '1111',
this.carModel,
this.carDescription,
}
);
factory Cars.fromJSON(Map<String,dynamic> json) => Cars(
userId : json['userId'],
carModel : json['CarModel'],
carDescription : json['carDescription'],
);
toJSON() {
return {
'userId':'111',
'carModel':carModel,
'carDescription' : carDescription
};
}
cars.dart
class CarProvider with ChangeNotifier{
List<Cars> carlist = [
//Sample data to show the format in which the data needs to be shown as the listview gets populated from below records which we are supposed to get from postman
//Cars (
// userid: 1111,
// carModel: 'Maruti Alto',
// carDescription: 'Top speed 140 km/hr'
),
];
Future<void> readListofCars(int id) async {
print(id.toString());
const url = 'http://localhost/userlist';
// {'id': id.toString()
Map<String, String> headers = {
"Content-type": "application/json"};
try
{
final response = await http.get(url,headers: headers);
List<dynamic> bodyCars = jsonDecode(response.body);
List<Cars> loadedCars = bodyCars.map((dynamic item) => new Cars.fromJSON(item)).toList();
/*
for (Map i in bodyCars) {
_notificationitems.add(Cars.fromJSON(i));
}
*/
});
print(response.statusCode);
carlist = loadedCars;
print(json.decode(response.body));
notifyListeners();
}catch (error){
throw error;
}
}
Car_list.dart
class TabScreenCar extends StatefulWidget {
final String userId;
TabScreenCar(this.userId);
#override
_TabScreenCarState createState() => _TabScreenCarState();
}
class _TabScreenCarState extends State<TabScreenCar> {
#override
void didChangeDependencies() {
final id = 1111;
Provider.of<CarProvider>(context).readListofCars(id);
super.didChangeDependencies();
}
In your model class you have incorrect variable to fetch the json data
to make the process simple you can have a look at this link
https://stackoverflow.com/a/58708634/9236994
just paste your json response in https://javiercbk.github.io/json_to_dart/
and use the json model class which will be generated.
your issue will be resolved.
variable which you are using doesn't match with your json response
OR to keep it simple
use below mentioned fromJSON method
factory Cars.fromJSON(Map<String,dynamic> json) => Cars(
userId : json['id'],
carModel : json['carModel'],
carDescription : json['carDescription'],
);
EDIT
I think you are facing issue with setting up the data in your list variable too.
use below shown code to fill the response into list if cars data
List<Cars> loadedCars = List<Cars>();
var data = jsonDecode(response.body);
data.forEach((val) {
loadedCars.add(Cars.fromJSON(val));
}