Parse a JSON file using class in Flutter - arrays

I have a following problem. I just cannot understand what can be the easiest way to parse an information from JSON to an array of objects in flutter.
I have a following class:
class Organization {
String subject;
String organization;
String shortDescription;
String address;
String phoneNumber;
String contactInfo;
List<String> tags;
Organization({
this.subject,
this.organization,
this.shortDescription,
this.address,
this.phoneNumber,
this.contactInfo,
this.tags,
});
Organization.fromJson(Map<String, dynamic> json) {
subject = json['Subject'];
organization = json['Organization'];
shortDescription = json['Short description'];
address = json['Address'];
phoneNumber = json['Phone number'];
contactInfo = json['Contact info'];
tags = json['Tags'].cast<String>();
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['Subject'] = this.subject;
data['Organization'] = this.organization;
data['Short description'] = this.shortDescription;
data['Address'] = this.address;
data['Phone number'] = this.phoneNumber;
data['Contact info'] = this.contactInfo;
data['Tags'] = this.tags;
return data;
}
}
This is the small sample of Json file that I have in assets folder.
[
{
"Subject": "Cultuur - Culture ",
"Organization": "Museum aan de stroom ",
"Short description": "The MAS is a museum that provides you with permanent and temporary exhibitions. These offer a refreshing look at the city and it's history. At the top, on the tenth floor, you can enjoy a 360-degree panorama of the city, the port and the river.",
"Address": "Hanzestedenplaats 1, 2000 Antwerpen",
"Phone number": "+32 3 338 44 00",
"Contact info": "www.mas.be",
"Tags": ["Meeting place", "Museum", "Antwerp", "Art"]
},
{
"Subject": "Cultuur - Culture ",
"Organization": "Red star line museum ",
"Short description": "Through personal story's of immigrants the Red star line museum paints a picture of the immigration history in Antwerp. ",
"Address": "Montevideostraat 3, 2000 Antwerpen",
"Phone number": "+32 3 298 27 70",
"Contact info": "www.redstarline.be",
"Tags": ["Museum", "Antwerp", "History", "Immigration"]
}
]
I am just trying to parse information from this file to a list of Organisations as presented below:
List<Organisation> organisations = [];
Future<void> readJson() async {
final String response =
await rootBundle.loadString('assets/organisations.json');
final data = await json.decode(response);
organisations = data;
}
However I get Unhandled Exception: type List dynamic is not a subtype of type List Organisation error.
In my opinion data should be an array of my objects. What am I doing wrong? Or maybe is there simpler method to achieve this result?

At the final of readJson función, you can call Organization.fromJson like this:
Future<void> _readJson() async {
final String response =
await rootBundle.loadString('assets/data/organizations.json');
final List<dynamic> data = await json.decode(response);
for (dynamic it in data) {
final Organization organization = Organization.fromJson(it); // Parse data
organizations.add(organization); // and organization to List
}
}

Related

Flutter : Facing an error like - The argument type 'Map<String, dynamic>?' can't be assigned to the parameter type 'Map<String, Object?>'

I am practicing with flutter SQFLite. That's why I create a model for user info. Here is my model code-
class Contact {
static const tblContact = "contacts";
static const colId = "id";
static const colName = "name";
static const colMobile = "mobile";
Contact({
this.id,
this.name = '',
this.mobile = '',
});
int? id;
String name;
String mobile;
Map<String, dynamic>? toMap() => {
"id": colId,
"name": colName.toString(),
"mobile": colMobile.toString(),
};
factory Contact.fromMap(Map<String, dynamic> json) =>
Contact(name: json[colName], mobile: json[colMobile]);
}
and then I create a database helper for insert and fetch data from database. But I faced a problem to insert value ( The argument type 'Map<dynamic, dynamic>?' can't be assigned to the parameter type 'Map<String, Object?>' ). Here is my database helper code -
import 'dart:io';
......
.......
class DatabaseHelper {
static const _databaseName = "ContactData.db";
static const _databaseVersion = 1;
//<====== Singleton Class
DatabaseHelper._();
static final DatabaseHelper instance = DatabaseHelper._();
Database? _database;
Future<Database?> get database async {
if (_database != null) {
return _database;
} else {
_database = await _initDatabase();
return _database;
}
}
//CREATE DATABASE
_initDatabase() async {
Directory dataDirectory = await getApplicationDocumentsDirectory();
String dbPath = join(dataDirectory.path, _databaseName);
print(dbPath);
return await openDatabase(dbPath,
version: _databaseVersion, onCreate: _onCreate);
}
//CREATE TABLE
_onCreate(Database db, int version) async {
db.execute('''
CREATE TABLE ${Contact.tblContact}(
${Contact.colId} INTEGER PRIMARY KEY AUTOINCREMENT,
${Contact.colName} STRING NOT NULL,
${Contact.colMobile} STRING NOT NULL
);
''');
print("Done on Create");
}
//<=================== ADD DATA
Future<int> insertContact(Contact contact) async {
Database? db = await database;
return await db!.insert(Contact.tblContact, contact.toMap());
}
//<==================== Read Data
Future<List<Contact>> fetchContacts() async {
Database? db = await database;
List<Map<String, dynamic>> contacts = await db!.query(Contact.tblContact);
print("Done Fetch");
return contacts.length == 0
? []
: contacts.map((x) => Contact.fromMap(x)).toList();
}
}
Error :
Where is my problem and what I missed ? Please someone help me to solve this.
Update:
I change argument type "Map<dynamic, dynamic>?" to "Map<String, dynamic>?" but now I found another error .
The argument type 'Map<String, dynamic>?' can't be assigned to the parameter type 'Map<String, Object?>'.
Recent Error:
You are getting this error because in insert function the Map type is <String, Object?> and you are passing a map that's type is <dynamic, dynamic>. try changing
Map<dynamic, dynamic>? toMap() => {
"id": colId,
"name": colName.toString(),
"mobile": colMobile.toString(),
};
factory Contact.fromMap(Map<dynamic, dynamic> json) =>
Contact(name: json[colName], mobile: json[colMobile]);
to
//You don't need to pass id because it's auto incremented
[ for null safety Use Map<String, Object?> not Map<String, Object>?]
Map<String, Object?> toMap() => {
"name": colName.toString(),
"mobile": colMobile.toString(),
};
factory Contact.fromMap(Map<String, Object?> json) =>
Contact(name: json[colName], mobile: json[colMobile]);
Your DB says that he wants a map like
Map<String, Object>
here the map's key must be a String
And you are passing a map with dynamic type of key
so change the key of your map from dynamic to String
Change this map type
Map<dynamic, dynamic>? toMap() => {
"id": colId,
"name": colName.toString(),
"mobile": colMobile.toString(),
};
to
Map<String, dynamic>? toMap() => {
"id": colId,
"name": colName.toString(),
"mobile": colMobile.toString(),
};
After that you will get another error like
The argument type 'Map<String, dynamic>?' can't be assigned to the parameter type 'Map<String, Object?>'.
Now at first we have to undertand what that Map<String, Object?> mean?
The type Object is your maps value must me a specific type of Data like int double String or any Custom Object because SQL Database store data in column and each column must mantain a specific data type so try like this
Map<String, Object>? toMap() => {
"id": colId,
"name": colName.toString(),
"mobile": colMobile.toString(),
};
Change
Map<dynamic, dynamic>? toMap() => {
"id": colId,
"name": colName.toString(),
"mobile": colMobile.toString(),
};
to
Map<String, dynamic>? toMap() => {
"id": colId,
"name": colName.toString(),
"mobile": colMobile.toString(),
};

Flutter - Sembast Database insert List of Objects

I'm about to use the database "Sembast" in Flutter.
Simple objects with data types like string and int are working properly. However, it becomes problematic when using Lists.
I have created an example and oriented myself on the following tutorial: https://resocoder.com/2019/04/06/flutter-nosql-database-sembast-tutorial-w-bloc/
In my example, there are fruits and leaves as objects. A fruit contains a list of leaves.
class Fruit {
final String id;
final String name;
final bool isSweet;
final List<Leaves> leaves;
...
}
class Leaves {
final String id;
final String name;
...
}
//Create a sample object
var leaveOne = Leaves(id: "1", name: "leaveOne");
var leaveTwo = Leaves(id: "2", name: "leaveTwo");
var leaveThree = Leaves(id: "3", name: "leaveThree");
var leavesList = List<Leaves>();
leavesList.add(leaveOne);
leavesList.add(leaveTwo);
leavesList.add(leaveThree);
var fruit = Fruit(id: "1", name: "Apple", isSweet: true, leaves: leavesList);
_fruitDao.insert(fruit);
// The fruitDao.insert makes following
Future insert(Fruit fruit) async {
await _fruitStore.add(await _db, fruit.toJson());
}
The JSON looks like that: {id: 1, name: Apple, isSweet: true, leaves: [Instance of 'Leaves', Instance of 'Leaves', Instance of 'Leaves']}
The ERROR is following:
[ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: Invalid argument(s): value Instance of 'Leaves' unsupported type Leaves
As pointed at, Instance of 'Leaves' is not a valid type so each Leave must be converted as well. Hard to guess what you are doing without seeing your toJson() but something like this should work (could be largely optimized):
class Fruit {
final String id;
final String name;
final bool isSweet;
final List<Leaves> leaves;
Fruit({this.id, this.name, this.isSweet, this.leaves});
Map<String, dynamic> toJson() => <String, dynamic>{
'id': id,
'name': name,
'isSweet': isSweet,
'leaves': leaves?.map((leave) => leave.toJson())?.toList(growable: false)
};
}
class Leaves {
final String id;
final String name;
Leaves({this.id, this.name});
Map<String, dynamic> toJson() => <String, dynamic>{'id': id, 'name': name};
}
and your json should look like something this this:
{
"id": "1",
"name": "Apple",
"isSweet": true,
"leaves": [
{
"id": "1",
"name": "leaveOne"
},
{
"id": "2",
"name": "leaveTwo"
},
{
"id": "3",
"name": "leaveThree"
}
]
}
Here is an example in addition to #alextk answer with converting to and from without any code generation or library's.
class Fruit {
final String id;
final String name;
final bool isSweet;
final List<Leaves> leaves;
Fruit({this.id, this.name, this.isSweet, this.leaves});
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'isSweet': isSweet,
'leaves': leaves.map((leave) => leave.toMap()).toList(growable: false)
};
}
static Fruit fromMap(Map<String, dynamic> map) {
return Fruit(
id: map['id'],
name: map['name'],
isSweet: map['isSweet'],
leaves: map['leaves'].map((mapping) => Leaves.fromMap(mapping)).toList().cast<Leaves>(),
);
}
}
class Leaves {
final String id;
final String name;
Leaves({this.id, this.name});
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
};
}
static Leaves fromMap(Map<String, dynamic> map) {
return Leaves(
id: map['id'],
name: map['name'],
);
}
}
For cases when you use freezed, I found this solution:
...
...
#freezed
class Fruit with _$Fruit {
//add this line
#JsonSerializable(explicitToJson: true)
const factory Fruit({...}) = _Fruit;
factory Fruit.fromJson(Map<String, dynamic> json) => _$FruitFromJson(json);
}

Read from JSON or XML file and write into array of Core Data Objects

And I have created a Core Data Entity ("Exstand") that looks like the below JSON structure (same fields) and I try to read from the JSON file and write into an array of Core Data objects.
I have a local (stored in Xcode) JSON file that contains data looking like this:
[{
"objectid": 13003,
"lat": 40.198539203831054,
"long": 20.294164128143816,
"adresse": "1 Random Street
"zeitraum": "v. 7-20h",
"stellplatzanzahl": 3
},
{
"objectid": 13004,
"lat": 50.25018761410509,
"long": 30.44382262875748,
"adresse": "2 Random Street",
"zeitraum": "",
"stellplatzanzahl": 6
}]
I have went through many tutorials and posts here but can't for the life of me get it working for me because they either open a remote file and/or don't try to store into Core Data.
This is the code I have so far and it crashes when I try append the object to the array. When the .append line is commented I can see in the console that it doesn't actually write data into "stand".
class EventDetails: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var eventMap: MKMapView!
var annotation:MKAnnotation!
var Standarray: [Exstand] = []
var stand:Exstand? = nil
override func viewDidLoad() {
super.viewDidLoad()
do {
let data = NSData(contentsOf: url!)
let jsonData = try JSONDecoder().decode([Nstand].self, from: data! as Data)
for detail in jsonData {
self.stand?.adresse = detail.adresse as String
self.stand?.lat = detail.lat as Double
self.stand?.long = detail.long as Double
self.stand?.stellplatzanzahl = Int16(detail.stellplatzanzahl as Int)
self.stand?.zeitraum = detail.zeitraum as String
print(stand?.adresse)
print("adressen!")
//The line below crashes the app Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Standarray.append(stand!)
}
print("array!")
print(Standarray)
} catch {
print(error) // Handle Error
}
}
}
I also created this struct because in some tutorials they worked with that instead of Core Data entities, so if that is a better, I can also use that.
struct NStand : Codable{
var objectid: Int
var lat: Double
var long: Double
var adresse: String
var zeitraum: String
var stellplatzanzahl: Int
}
I also have the JSON data in a XML file. I don't care from where to read it, all I want is my app to actually read and store the data.
Thanks in advance for any helpful advice.
Your json is invalid here
"adresse": "1 Random Street
to
"adresse": "1 Random Street",
should be
[{
"objectid": 13003,
"lat": 40.198539203831054,
"long": 20.294164128143816,
"adresse": "1 Random Street",
"zeitraum": "v. 7-20h",
"stellplatzanzahl": 3
},
{
"objectid": 13004,
"lat": 50.25018761410509,
"long": 30.44382262875748,
"adresse": "2 Random Street",
"zeitraum": "",
"stellplatzanzahl": 6
}
]
struct NStand : Codable{ // for core-data NSManagedObject
let objectid,stellplatzanzahl: Int
let lat,long: Double
let adresse,zeitraum: String
}
let str = """
[{
"objectid": 13003,
"lat": 40.198539203831054,
"long": 20.294164128143816,
"adresse": "1 Random Street",
"zeitraum": "v. 7-20h",
"stellplatzanzahl": 3
},
{
"objectid": 13004,
"lat": 50.25018761410509,
"long": 30.44382262875748,
"adresse": "2 Random Street",
"zeitraum": "",
"stellplatzanzahl": 6
}]
"""
do {
let res = try JSONDecoder().decode([NStand].self, from: Data(str.utf8))
print(res)
}
catch {
print(error)
}
The error occurs because stand is nil and never gets created.
Adopting (De)codable in a NSManagedObject subclass is a bit tricky because you have to call the designated initializer which requires the managed object context.
Based on this great answer by casademora first create an extension of CodingUserInfoKey and JSONDecoder to be able to pass the managed object context along.
extension CodingUserInfoKey {
static let context = CodingUserInfoKey(rawValue: "context")!
}
extension JSONDecoder {
convenience init(context: NSManagedObjectContext) {
self.init()
self.userInfo[.context] = context
}
}
Then add CodingKeys and the required initializer to your NSManagedObject subclass (in the class, not in the extension).
class Exstand: NSManagedObject, Decodable {
private enum CodingKeys: String, CodingKey { case objectid, lat, long, adresse, zeitraum, stellplatzanzahl }
public required convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError("Exstand Error: no managed object context!") }
let entity = NSEntityDescription.entity(forEntityName: "Exstand", in: context)!
self.init(entity: entity, insertInto: context)
let values = try decoder.container(keyedBy: CodingKeys.self)
let objectid = try values.decode(String.self, forKey: .objectid) // do something with objectid
adresse = try values.decode(String.self, forKey: . adresse)
lat = try values.decode(Double.self, forKey: .lat)
long = try values.decode(Double.self, forKey: .long)
zeitraum = try values.decode(String.self, forKey: .zeitraum)
stellplatzanzahl = try values.decode(Int16.self, forKey: .stellplatzanzahl)
}
...
}
Thats all you need, now you can decode the JSON directly into Core Data
var standArray = [Exstand]()
override func viewDidLoad() {
super.viewDidLoad()
do {
let data = try Data(contentsOf: url!)
let context = // here add the reference to the managed object context
let decoder = JSONDecoder(context: context)
standArray = try decoder.decode([Exstand].self, from: data)
try context.save()
} catch {
print(error) // Handle Error
}
}
The other structs / classes are not needed.

How to convert a List<Map<String, dynamic>> inside a Map<String, dynamic> to JSON in Dart?

I'm trying to send a Map<String, dynamic>, and one of the dynamic is actually a List<Map<String, dynamic>>.
I assemble it like that :
Packet packet = Packet(
datas: stocks.map((stock) => stock.toJson()).toList(),
);
String json = jsonEncode(packet);
The problem is what's being sent is actually this :
{
"datas": {
"rows": "[{NoArticle: 051131626638, Description: Ruban pour tapis, qty: 5}]"
}
}
The expected output is this :
{
"datas": {
"rows": [{
"NoArticle": "051131626638",
"Description": "Ruban pour tapis",
"qty": 5,
}]
}
}
I want to send a List<Map<String, dynamic>>, not a String. How do I do that?
Answer : I am a dumbass.
I passed the parameter trough a function, like this :
Server.send("sendInventoryBatch", {
"rows": "${stocks.map((stock) => stock.toJson()).toList()}",
});
Of course it would return a String.
Question is invalid. If you have a similar problem, please open a different question. Sorry for the inconvenience.
Now if anyone actually has this question and stumbles upon this thread, here's how to do it :
Assemble your object, and make sure you didn't stringify it along the way
Use jsonEncode
In case of doubt, make everything a Map<String, dynamic>, a List<dynamic>, or a sub-class of those two first.
Packet packet = Packet(
appType: "inventoryManager",
module: "",
action: action,
datas: data,
deviceID: Globals.map["UUID"],
cbackid: cback,
);
You can generate classes like my custom Packet from JSON using multiple online resources because jsonEncode will use the auto-generated Map<String, dynamic> toJson().
https://app.quicktype.io/ - Was recommended to me on Discord by Miyoyo#5957
https://javiercbk.github.io/json_to_dart/ - I used this one before
http://json2dart.com/
String json = jsonEncode(packet);
And voilà, you're done.
Did you look at packages json_serializable?
Here's an example of a Person that has many Orders : example
// Copyright (c) 2015, 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 'example.g.dart';
#JsonSerializable()
class Person {
final String firstName;
#JsonKey(includeIfNull: false)
final String middleName;
final String lastName;
#JsonKey(name: 'date-of-birth', nullable: false)
final DateTime dateOfBirth;
#JsonKey(name: 'last-order')
final DateTime lastOrder;
#JsonKey(nullable: false)
List<Order> orders;
Person(this.firstName, this.lastName, this.dateOfBirth,
{this.middleName, this.lastOrder, List<Order> orders})
: orders = orders ?? <Order>[];
factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
Map<String, dynamic> toJson() => _$PersonToJson(this);
}
#JsonSerializable(includeIfNull: false)
class Order {
int count;
int itemNumber;
bool isRushed;
Item item;
#JsonKey(
name: 'prep-time',
fromJson: _durationFromMilliseconds,
toJson: _durationToMilliseconds)
Duration prepTime;
#JsonKey(fromJson: _dateTimeFromEpochUs, toJson: _dateTimeToEpochUs)
final DateTime date;
Order(this.date);
factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
Map<String, dynamic> toJson() => _$OrderToJson(this);
static Duration _durationFromMilliseconds(int milliseconds) =>
Duration(milliseconds: milliseconds);
static int _durationToMilliseconds(Duration duration) =>
duration.inMilliseconds;
static DateTime _dateTimeFromEpochUs(int us) =>
DateTime.fromMicrosecondsSinceEpoch(us);
static int _dateTimeToEpochUs(DateTime dateTime) =>
dateTime.microsecondsSinceEpoch;
}
#JsonSerializable()
class Item {
int count;
int itemNumber;
bool isRushed;
Item();
factory Item.fromJson(Map<String, dynamic> json) => _$ItemFromJson(json);
Map<String, dynamic> toJson() => _$ItemToJson(this);
}
#JsonLiteral('data.json')
Map get glossaryData => _$glossaryDataJsonLiteral;

How do I get Highlighted Snippets using SolrJ?

I am migrating my application from Lucene to Solr. Solr handles highlighting a lot better, however if for instance I search the key word "city", I would expect a response like:
{
"id":"fdc3833a-0e4f-4314-ba8c",
"title": "Paris is a beautiful <b>city</b>",
"description": "The <b>city</b> is a great example of......",
}
while I am getting the following response instead:
{
"id":"fdc3833a-0e4f-4314-ba8c",
"title": "Paris is a beautiful city",
"description": "The city is a great example of......",
}
"highlighting": {
"fdc3833a-0e4f-4314-ba8c": {
"title": [
"Paris is a beautiful <b>city</b>"
],
"description": [
"The <b>city</b> is a great example of......"
]
}
}
So as you can see, instead of getting the highlighted term within the result, I am getting an extra section called highlighting and that means that my Java code has to change.
My question is: how will I get the highlight snippets in SolrJ?
In SolrJ is possible to get the highlighted snippets using the following code:
public String getHighlightedText(final QueryResponse queryResponse, final String fieldName, final String docId) {
String highlightedText = "";
Map<String, Map<String, List<String>>> highlights = queryResponse.getHighlighting();
if (highlights!=null && MapUtils.isNotEmpty(highlights.get(docId))) {
List<String> snippets = highlights.get(docId).get(fieldName);
if (CollectionUtils.isNotEmpty(snippets)) {
highlightedText = getFragments(snippets);
}
}
return highlightedText;
}
private static final String getFragments(List<String> snippets){
StringBuilder fragments = new StringBuilder();
for (int i = 0; i < snippets.size(); i++) {
if (i > 0) {
fragments.append("............");
}
fragments.append(snippets.get(i));
}
return fragments.toString();
}
Please notice that this code will get you the best snippets for single-value fields while you will need some variations for multi-value fields.

Resources