"Unexpected Character" on Decoding JSON - json

The following is the code:
static TodoState fromJson(json) {
JsonCodec codec = new JsonCodec();
List<Todo> data = codec.decode(json["todos"]);
VisibilityFilter filter = codec.decode(json['visibilityFilter']);
return new TodoState(todos: data,
visibilityFilter: filter);
}
Error produced by Android Studio:
[VERBOSE-2:dart_error.cc(16)] Unhandled exception:
FormatException: Unexpected character (at character 3)
Any idea how to make it work?
This is the output of the Json as produced by Redux.

There's a problem with your code as well as the string you're trying to parse. I'd try to figure out where that string is being generated, or if you're doing it yourself post that code as well.
Valid Json uses "" around names, and "" around strings. Your string uses nothing around names and '' around strings.
If you paste this into DartPad, the first will error out while the second will succeed:
import 'dart:convert';
void main() {
JsonCodec codec = new JsonCodec();
try{
var decoded = codec.decode("[{id:1, text:'fdsf', completed: false},{id:2, text:'qwer', completed: true}]");
print("Decoded 1: $decoded");
} catch(e) {
print("Error: $e");
}
try{
var decoded = codec.decode("""[{"id":1, "text":"fdsf", "completed": false},{"id":2, "text":"qwer", "completed": true}]""");
print("Decoded 2: $decoded");
} catch(e) {
print("Error: $e");
}
}
The issue with your code is that you expect the decoder to decode directly to a List. It will not do this; it will decode to a dynamic which happens to be a List<dynamic> whose items happen to be Map<String, dynamic>.
See flutter's Json documentation for information on how to handle json in Dart.

I don't know if that's the case, but I got a similar error when me JSON looks like this
[
{
...
},
]
and not like this
[
{
...
}
]
The comma was causing the issue.

If Anyone came here and your are using dio package to call http request you need to set responseType to plain
BaseOptions options = new BaseOptions(
baseUrl: "<URL>",
responseType: ResponseType.plain
);

I also have similar type of error, Be make sure that the argument of .decode method shouldn't be empty object.
try {
if(json["todos"].isNotEmpty) {
List<Todo> data = codec.decode(json["todos"]);
}
if(json["todos"].isNotEmpty) {
VisibilityFilter filter = codec.decode(json['visibilityFilter']);
}
}
catch(e) {
print(e);
}
Do try this, hope it will work for you.

Related

Decoding json from google firebase rest api using Dart, not Flutter

I can retrieve a list of documents from a collection in a Cloud Firestore instance, in Firebase. The response contains the most verbose json I have ever seen. Here is a taste, ...
{
documents: [
{
name: projects/myprojectId/databases/(default)/documents/mycollection/0HC2spBFxEMNUc8VQLFg,
fields: {
name: {
stringValue: Jim's Bait Shop},
taxId: {
stringValue:
},
mailingAddress: {
mapValue: {
fields: {
streetAddress1: {
stringValue:
}
},
streetAddress2: {
stringValue:
},
state: {
stringValue: NC
},
city: {
stringValue: Boone
},
zipCode: {
stringValue:
}
}
}
}
},
createTime: 2020-08-31T19
:
54: 28.643464Z,
updateTime: 2020-09-01T02
:
35: 08.203028Z
},
{ ...
When trying to use jsonDecode, in dart:convert, it fails to de-serialize the json response into a collection of Dart objects.
'_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'String'
And if I use cUrl instead of Dart, the json response looks just as verbose.
I'm using the FirebaseClient in "package:firebase/firebase_io.dart" to authenticate and read the collection.
I tried to build a "reviver" function but jsonDecode would not accept it so I'm not sure how I messed that up.
Anyway, I'm not seeing much guidance in the documentation on how to marshal this verbose json response into Dart objects. I suspect this server-side Dart is somewhat new territory. I want to avoid packages that require Flutter because I'm using a prebuilt docker image, with the Dart runtime preinstalled, on Google Cloud Run. (Truthfully, I've already tried a few Flutter packages for Firestore and a Flutter docker image.) I'll take any suggestions you have.
Below is the file I've been using for testing.
import 'package:firebase/firebase_io.dart';
import 'credentials.dart'; // borrowed from a SO post
import 'dart:convert';
const base = 'https://firestore.googleapis.com/v1/projects/';
void main() async {
// get private key...
final credential = await Credentials.fetch(); // string
final fbClient = FirebaseClient(credential);
final path = base + 'my_project_id/databases/(default)/documents/my_collection'
'?mask.fieldPaths=name&mask.fieldPaths=taxId&mask.fieldPaths=mailingAddress&orderBy=orgId';
final response = await fbClient.get(path);
print(response);
final orgs = jsonDecode(response); // unhandled exception
fbClient.close();
}
I think I might need to switch to a more sophisticated json deserializer package, and annotate my model classes to explicitly map this gnarly json to specific Dart class properties. But I have not yet seen a Dart package that supports such capabilities.
I have tried to use "json_serializable: 3.4.1" but failed to get code generation to work.
An online json validator is saying the response is malformed due to an apostrophe but can I trust that? Doubt I can escape special chars.
The error message says that response is not a String, it's a Map.
That means that Firebase has already parsed the JSON for you and returns the parsed structure.
You don't need to use jsonDecode, just final orgs = response;.
The solution was to stop using FirebaseClient, because it was not wrapping the name-value pairs in double quotation marks. Just use normal http instead.
import 'package:http/http.dart' as http;
const base = 'https://firestore.googleapis.com/v1/projects/';
void main() async {
//
final uri = base + 'myproject/databases/(default)/documents/mycollection' +
'?mask.fieldPaths=name&mask.fieldPaths=taxId&mask.fieldPaths=mailingAddress&orderBy=orgId&alt=json';
var response = await http.get(uri);
// print(response.body);
var json = jsonDecode(response.body)['documents'] as List;
List l = json.map((o) => MyModelClass.fromJson(o)).toList();
https://pub.dev/packages/http

Is it possible to read a JSON sub-object directly as a JSON DOM object or Data or some other generic format?

Is there a JSON DOM that can read any JSON, regardless of type, and store it into something like a Dictionary hierarchy?
Consider this JSON and code...
var jsonData = """
{
"type":"ListWrapper",
"objectData": {
"label":"SuperItems",
"items":[
{
"value":"First"
},
{
"value":"Second"
},
{
"value":"Third"
},
{
"value":"Fourth"
}
]
}
}
""".data(using:.utf8)!
Here's the first part of what we're trying to do. (Note: This doesn't actually work.)
class TestObject : Codable {
var name:String?
var childObject:Data? // <-- This is the 'wishful' part
}
do{
let testObject = try JSONDecoder().decode(TestObject.self, from:jsonData)
processTestObject(testObject)
}
catch let error {
print(error.localizedDescription)
}
Now somewhere else in the application, we have this defined (Note it matches the object stored in 'objectData' above) and we're trying to do this...
class ListWrapper : Codable {
var label:String?
var items:[ListItem]?
}
class ListItem : Codable {
var value:String?
}
do{
switch testObject.type{
case "ListWrapper":
var listWrapper = try JSONDecoder().decode(ListWrapper.self, from:testObject.objectData)
processList(listWrapper)
case "Foo":
var listWrapper = try JSONDecoder().decode(Foo.self, from:testObject.objectData)
processFoo(foo)
}
}
catch let error {
print(error.localizedDescription)
}
Note: Yes, we know we can encode the JSON in 'objectData' and store it as a string, but we're trying to leave the JSON as-is.
One approach would be to write a custom deserializer for TestObject but we'd have to tell it all the possible types we're looking for. We can do that, but I was hoping to make it a 'dumb' object that just stores the JSON as data or something similar. So is this possible?

SwiftyJSON - Unable to get transactionId from JSON String

I am using CocoaAsyncSocket to retrieve a message from a servers API that's using JSON. I am able to get the data and convert it to a printable string, what I am unable to do is retrieve a value (transactionId) from my attempts to parse the JSON string I already have, using SwiftyJSON. I know there are other posts that are similar to this one but none have solved my problem.
In ViewController:
func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) {
guard let msg = String(data: data, encoding: .utf8) else { return }
var response = " "
if msg.contains("Reset") {
transactionID = ParseJSON().parse(message: msg)
response = String(format: "{\"Response\":{\"transactionId\":\"%#%\",\"content\":{\"Reset\":{}}}}", transactionID)
socket.write((response.data(using: .utf8))!, withTimeout: -1, tag: 0)
}
socket?.readData(withTimeout: -1, tag: 0)
}
ParseJSON class:
func parse (message: String) -> String {
var parsedMessage = " "
let json = JSON(parseJSON: message)
let transactionId = json["Request"]["transactionId"].stringValue
parsedMessage = transactionId
print(parsedMessage)
return parsedMessage
}
The result that is displayed is an empty transactionId value. Nothing prints or anything.
If you spot any errors in my code or have a better approach then please let me know!
Edit:
Here is the string I am attempting to parse:
{"Request": {"content": {"Reset": {}}, "transactionId": "f7c4d630-552b-46d9-a37d-44450537b48d"}}
Here is my output:
{\"Response\":{\"transactionId\":\"\",\"content\":{\"Reset\":{}}}}
The problem is not the code. Consider:
func parse(message: String) -> String {
let json = JSON(parseJSON: message)
return json["Request"]["transactionId"].stringValue
}
let input = """
{"Request": {"content": {"Reset": {}}, "transactionId": "f7c4d630-552b-46d9-a37d-44450537b48d"}}
"""
let transactionID = parse(message: input)
print("transactionId:", transactionID)
let response = String(format: "{\"Response\":{\"transactionId\":\"%#\",\"content\":{\"Reset\":{}}}}", transactionID)
print("response:", response)
The result of the above, as you’d expect, is:
transactionId: f7c4d630-552b-46d9-a37d-44450537b48d
response: {"Response":{"transactionId":"f7c4d630-552b-46d9-a37d-44450537b48d","content":{"Reset":{}}}}
So I suspect that the input message is not quite what you expected. So I might suggest adding some error handling so you can diagnose precisely where it is going wrong:
func parse(message: String) -> String {
let json = JSON(parseJSON: message)
if let error = json.error {
fatalError("JSON parsing error: \(error)")
}
let request = json["Request"]
if let error = request.error {
fatalError("request error: \(error)")
}
let transactionId = request["transactionId"]
if let error = transactionId.error {
fatalError("transactionId error: \(error)")
}
return transactionId.stringValue
}
Now, in practice, you probably wouldn’t use fatalError, but rather would do some graceful error handling (e.g. change parse such that it throws, then throw the errors encountered, if any, and then catch the error where you call parse and handle any runtime issues gracefully). But during this diagnostic process, fatalError is useful because it will stop your debugger at the offending line, simplifying your diagnostic process.
Bottom line, the request must not be quite in the form you expect. Note, it’s going to be very sensitive to capitalization, malformed JSON, etc. So, by looking at the errors provided by SwiftyJSON, you should be able to narrow down the issue quite quickly.
Below, you tell us that the Data is:
<7b225265 71756573 74223a20 7b22636f 6e74656e 74223a20 7b225265 73657422
3a207b7d 7d2c2022 7472616e 73616374 696f6e49 64223a20 22643937 36303036
622d3130 38302d34 3864652d 39323232 2d623139 63363663 35303164 31227d7d
00>
The problem is that last byte, 0x00. If you remove that, it works.
FWIW, when I convert that hex string back to a Data and run it through JSONSerialization, it confirms the diagnosis:
Error Domain=NSCocoaErrorDomain
Code=3840 "Garbage at end."
UserInfo={NSDebugDescription=Garbage at end.}
You need to figure out why that 0x00 was included in the end of your payload and remove it.

How would I parse this type of JSON using SwiftyJSON?

[
{
"ID": 0,
"Name": "PHI"
},
{
"ID": 0,
"Name": "ATL"
}
]
I'm using SwiftyJSON and Alamofire. This is what is being returned. I want to loop through each of these objects now in my code. I'm having trouble getting this information though.
json[0]["Name"].string seems to return nil, and I'm not sure why. The JSON object is definitely getting the JSON, when I print it to the console it looks exactly like above.
I also tried:
var name = json[0].dictionary?["Name"]
Still nil though.
Any ideas?
EDIT
Here's the code I'm using to retrieve the JSON.
Alamofire.request(.GET, "url", parameters: nil, encoding: .JSON, headers: nil).responseJSON
{
(request, response, data, error) in
var json = JSON(data!)
//var name = json[0].dictionary?["Name"]
}
Your JSON is valid (at least this snippet is), and your first method to retrieve the data from SwiftyJSON is correct.
On the other hand, the Alamofire snippet you showed didn't compile for me, I had to change the signature.
Given that in the comments you say that not only json[0]["Name"] is nil but json[0] is also nil, I think you have a problem with how your data is fetched.
I tested this version of the Alamofire method in a sample app and it worked well:
Alamofire.request(.GET, yourURL).responseJSON(options: nil) { (request, response, data, error) in
let json = JSON(data!)
let name = json[0]["Name"].string
println(name)
}
In the screenshot, the URL is my local server, with your JSON copy pasted in "test.json".
This "answer" is an extension of my comment, I needed room to show more info...
I think you might try this.
first convert JSON(data) using swifyJSON
let json = JSON(data!)
then count the array element in data.
let count: Int? = json.array?.count
after that use for loop to reach array element one.
for index in 0...count-1 {
self.idArray.append(json[index]["ID"].stringValue)
self.nameArray.append(json[index]["Name"].stringValue)
}
you will get sorted data in "idArray" and "nameArray". try to print both the array.
You do not have to use index to parse array. You can get single json object from array and use it to access the data:
for (index: String, subJson: JSON) in json {
println("Current Index = \(index)")
if let _id = subJson["ID"].int {
println("ID = \(_id)")
} else {
println("Error ID")
}
if let _name = subJson["Name"].string {
println("Name = \(_name)")
} else {
println("Error Name")
}
}

SwiftyJSON - Returning json array from func

I am working on my local json api and it works quite fine with receiving and parsing data using swiftyjson and alamofire, but when it comes to return these data, I have some troubles:
import Alamofire
import SwiftyJSON
...
func getApi() -> Array<JSON> {
let user = "user"
let password = "password"
Alamofire.request(.GET, "http://localhost/api/")
.authenticate(user: user, password: password)
.responseString { (req, res, body, error) in
if let data = (body)!.dataUsingEncoding(NSUTF8StringEncoding) {
let json = JSON(data: data)
println(json) // works fine
return json // does not work
}
}
}
So it prints "JSON is not convertible to Void"...
Anybody knows how to deal with this?
You think i am doing right using alamofire for a "http-basic-authentification" ?
Greetings and thanks!
If your api returns a JSON object, alamofire provides a .responseJSON, and swiftyJSON can wrap that response.
Also don't forget that this request is asynchronous, so you probably need to get these results in a completion handler, something like this:
func getApi(completionHandler: (jsonResponse: JSON) -> () {
let user = "user"
let password = "password"
Alamofire.request(.GET, "http://localhost/api/")
.authenticate(user: user, password: password)
.responseJSON { (req, res, JSON, error) in
println(json) // works fine
completionHandler(json)
}
}
}
Call the method like so:
getAPI(completionHandler: { (response: JSON) -> () in
// do something with your response. If the JSON contains an array you can iterate through it here.
}
Next good thing will be to check the "Response Serialization" section on Alamofire doc.
You are attempting to return an object of type JSON inside a block that has no return type (Void).
according to your variable names, you should set
res = json
or
res.setResponse(json)
or smt like that. Can't check now because I'm far away from my mac at the moment
Your function returns an array of JSON: [JSON].
You are trying to return a single JSON type object.
Change your return statement to return an array:
return [json] // should work
EDIT: I misread the code when I posted my answer. You are calling an async method, Alamofire.request. That method returns immediately, but the data isn't actually loaded until sometime later. Your current design won't work.
#Gwendle's answer is the right one. You need to refactor your method to take a completion block and put the response handling in that completion block.