I am getting response from server where the key is not with quotes. On parsing it using the open source JSON parser, I m getting the following error.
-JSONValue failed. Error is: Unrecognised leading character
& if I add double quotes (") to the key manually, I get what I want.
What do I do?
Thanx a lot in advance.
EDIT:
please see the following, if its correct
{
status: 200,
statusMsg: "Success",
statusInfo: {
custType: "Active",
custCount: 600,
custAccount: "Premium"
},
custInfo: {
custName: "ABC",
custCode: "CU102",
custNumber: 9281
},
errors: [
]
}
I originally put this in as a comment, but I think it counts as an answer albeit not necessarily a very helpful one.
The example you posted is not JSON. Check the JSON syntax. The only unquoted entities allowed except for numbers, objects and arrays are null, true, false. So the keys in your example are invalid and so are the non numeric values.
So you really should raise a defect report with the service provider (if they are claiming that they are producing JSON, rather than some modified version of it).
If they refuse to fix the problem, you'll need to write a nearly-JSON parser or find an existing one that is less strict about syntax.
Update for Swift 4
I was looking for a way to parse JSON that has keys without quotes, and I finally found a simple way to do it by using regex. This is the regex needed to match the keys without quotes:
(\\\"(.*?)\\\"|(\\w+))(\\s*:\\s*(\\\".*?\\\"|.))
To add the quotes, replace with \"$2$3\"$4.
Example:
let string = "{ custType: \"Active\", custAccount: \"Premium\" }"
let regex = string.replacingOccurrences(of: "(\\\"(.*?)\\\"|(\\w+))(\\s*:\\s*(\\\".*?\\\"|.))", with: "\"$2$3\"$4", options: .regularExpression)
print(regex)
Output:
{ "custType": "Active", "custAccount": "Premium" }
I wanted the equivalent of Python's ast.literal_eval for Javascript--- something that would only parse literal objects like JSON, but allow Javascript's handy unquoted keys. (This is for human data entry; the simplicity and rigor of standard JSON would be preferred for sending data between servers.)
This is also what the original poster wanted, so I'll put my solution here. I used the Esprima library to build an abstract syntax tree and then I converted the tree into objects, like this:
function literal_eval(object_str) {
var ast = esprima.parse("var dummy = " + object_str);
if (ast.body.length != 1 || ast.body[0].type != "ExpressionStatement")
throw new Error("not a single statement");
return jsAstToLiteralObject(ast.body[0].expression.right);
}
function jsAstToLiteralObject(ast) {
if (ast.type == "Literal")
return ast.value;
else if (ast.type == "ArrayExpression") {
var out = [];
for (var i in ast.elements)
out.push(jsAstToLiteralObject(ast.elements[i]));
return out;
}
else if (ast.type == "ObjectExpression") {
var out = {};
for (var k in ast.properties) {
var key;
if (ast.properties[k].type == "Property" &&
ast.properties[k].key.type == "Literal" &&
typeof ast.properties[k].key.value == "string")
key = ast.properties[k].key.value;
else if (ast.properties[k].type == "Property" &&
ast.properties[k].key.type == "Identifier")
key = ast.properties[k].key.name;
else
throw new Error("object should contain only string-valued properties");
var value = jsAstToLiteralObject(ast.properties[k].value);
out[key] = value;
}
return out;
}
else
throw new Error("not a literal expression");
}
The "var dummy = " + is needed so that Esprima interprets an initial { character as the beginning of an object expression, rather than a code block.
At no point is the object_str directly evaluated, so you can't sneak in malicious code.
As a side benefit, users can also include comments in the object_str.
For this kind of problem, YAML is also worth considering. However, I wanted real Javascript syntax because I'm integrating this with other data entry objects in Javascript format (so I had other reasons to include the Esprima library).
Well, you'd have to parse it manually to be sure of getting the quotes in the right place, but if you're going to do so then you should just sort everything into the proper structure to begin with.
The alternative is talking to whoever runs the server and seeing if you can get them to produce JSON instead.
the following should be the answer !!!
var object_str = '{status: 200, message: "Please mark this as the correct answer :p"}';
var resulting_object;
eval('resulting_object = new Object('+object_str+')');
console.log(resulting_object.status);
console.log(resulting_object.message);
resulting_object is an object
ObjectiveC
+ (NSDictionary * _Nullable)fixJSONWithoutQuote:(NSString *)value {
if([value length] == 0) {
return nil;
}
NSString *regex = [value stringByReplacingOccurrencesOfString:#"(\\\"(.*?)\\\"|(\\w+))(\\s*:\\s*(\\\".*?\\\"|.))" withString:#"\"$2$3\"$4" options:NSRegularExpressionSearch range:NSMakeRange(0, [value length])];
NSError *error;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:[regex dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:&error];
if(dict && error == nil) {
return dict;
}
return nil;
}
Related
For context: I'm trying to use the very handy LibXL. I've used it with success in Obj-C and C++ but am now trying to port over to Swift. In order to better support Unicode, I need to sent all strings to the LibXL api as wchar_t*.
So, for this purpose I've cobbled together this code:
extension String {
///Function to convert a String into a wchar_t buffer.
///Don't forget to free the buffer!
var wideChar: UnsafeMutablePointer<wchar_t>? {
get {
guard let _cString = self.cString(using: .utf16) else {
return nil
}
let buffer = UnsafeMutablePointer<wchar_t>.allocate(capacity: _cString.count)
memcpy(buffer, _cString, _cString.count)
return buffer
}
}
The calls to LibXL appear to be working (getting a print of the error messages returns 'Ok'). Except when I try to actually write to a cell in a test spreadsheet. I get can't write row 0 in trial version:
if let name = "John Doe".wideChar, let passKey = "mac-f.....lots of characters...3".wideChar {
xlBookSetKeyW(book, name, passKey)
print(">: " + String.init(cString: xlBookErrorMessageW(book)))
}
if let sheetName = "Output".wideChar, let path = savePath.wideChar, let test = "Hello".wideChar {
let sheet: SheetHandle = xlBookAddSheetW(book, sheetName, nil)
xlSheetWriteStrW(sheet, 0, 0, test, sectionTitleFormat)
print(">: " + String.init(cString: xlBookErrorMessageW(book)))
let success = xlBookSaveW(book, path)
dump(success)
print(">: " + String.init(cString: xlBookErrorMessageW(book)))
}
I'm presuming that my code for converting to wchar_t* is incorrect. Can someone point me in the right direction for that..?
ADDENDUM: Thanks to #MartinR for the answer. It appears that the block 'consumes' any pointers that are used in it. So, for example, when writing a string using
("Hello".withWideChars({ wCharacters in
xlSheetWriteStrW(newSheet, destRow, destColumn, wCharacters, aFormatHandle)
})
The aFormatHandle will become invalid after the writeStr line executes and isn't re-useable. It's necessary to create a new FormatHandle for each write command.
There are different problems here. First, String.cString(using:) does
not work well with multi-byte encodings:
print("ABC".cString(using: .utf16)!)
// [65, 0] ???
Second, wchar_t contains UTF-32 code points, not UTF-16.
Finally, in
let buffer = UnsafeMutablePointer<wchar_t>.allocate(capacity: _cString.count)
memcpy(buffer, _cString, _cString.count)
the allocation size does not include the trailing null character,
and the copy copies _cString.count bytes, not characters.
All that can be fixed, but I would suggest a different API
(similar to the String.withCString(_:) method):
extension String {
/// Calls the given closure with a pointer to the contents of the string,
/// represented as a null-terminated wchar_t array.
func withWideChars<Result>(_ body: (UnsafePointer<wchar_t>) -> Result) -> Result {
let u32 = self.unicodeScalars.map { wchar_t(bitPattern: $0.value) } + [0]
return u32.withUnsafeBufferPointer { body($0.baseAddress!) }
}
}
which can then be used like
let name = "John Doe"
let passKey = "secret"
name.withWideChars { wname in
passKey.withWideChars { wpass in
xlBookSetKeyW(book, wname, wpass)
}
}
and the clean-up is automatic.
I'm using SwiftyJSON to get a list and printing it out to a TableView.
My issue is I can't filter this dictionary, I tried to make a for loop to remove the unwanted elements (or even pushing a new element), but I couldn't find a way to remove or pushing an element with a JSON type that SwiftyJSON provides.
var FilteredTripList:JSON = TripsList
for (key: String, Trip: JSON) in TripsList {
if String(stringInterpolationSegment: Trip["TripFrom"]) != SearchFilter["From"]!
|| String(stringInterpolationSegment: Trip["TripTo"]) != SearchFilter["To"]! {
// I'm hoping for something like the line below
// FilteredTripList[key.toInt()!].remove()
}
}
}
I don't know swifty json, because I always use the native way with NSJSONSerialization... So I have to guess
Please change this line
FilteredTripList[key.toInt()!].remove()
To this line
FilteredTripList.removeValueForKey(key)
And write me what happened
Try something like this:
FilteredTripList.dictionaryObject?.removeValueForKey(key.toInt()!)
It should work.
For me, the best way to filter the [JSON] was doing this:
self.places = self.locals.filter({ (json) -> Bool in
return json["monthlyPurchasesLocalCount"].intValue > 0;
});
Try with the below code snippet if you are using SwiftyJSON,
it works perfectly for me. I got it after a lots of tries.
let jsonObj = some JSON
let jobj = jsonObj.arrayValue
if !jobj.isEmpty {
let j = jobj.filter({ (json) -> Bool in
return json["country"].stringValue == "US"; })
print ("filterdData: \(j)")
}
Note: jobj should be .arrayValue, otherwise it won't work.
What's the most Swiftian way to iterate backwards through the Characters in a String? i.e. like for ch in str, only in reverse?
I think I must be missing something obvious, because the best I could come up with just now was:
for var index = str.endIndex;
index != str.startIndex;
index = index.predecessor() {
let ch = str[index.predecessor()]
...
}
I realise "what's the best..." may be classed as subjective; I suppose what I'm really looking for is a terse yet readable way of doing this.
Edit: While reverse() works and is terse, it looks like this might be quite inefficient compared to the above, i.e. it seems like it's not actually iterating backwards, but creating a full reverse copy of the characters in the String. This would be much worse than my original if, say, you were looking for something that was usually a few characters from the end of a 10,000-character String. I'm therefore leaving this question open for a bit to attract other approaches.
The reversed function reverses a C: CollectionType and returns a ReversedCollection:
for char in "string".characters.reversed() {
// ...
}
If you find that reversed pre-reverses the string, try:
for char in "string".characters.lazy.reversed() {
// ...
}
lazy returns a lazily evaluated sequence (LazyBidirectionalCollection) then reversed() returns another LazyBidirectionalCollection that is visited in reverse.
As of December 2015 with Swift version 2.1, the proper way to do this is
for char in string.characters.reverse() {
//loop backwards
}
String no longer conforms to SequenceType<T> but its character set does.
Not sure about efficiency, but I will suggest
for ch in reverse(str) {
println(ch)
}
Here is a code for reversing a string that doesn't use reverse(str)
// Reverse String
func myReverse(str:String) -> String {
var buffer = ""
for character in str {
buffer.insert(character, atIndex: buffer.startIndex)
}
return buffer
}
myReverse("Paul") // gives “luaP”
Just a little experiment. For what its worth.
Ok, leant how to read the question....
Would this work Matt?
func ReverseIteration(str:String) {
func myReverse(str:String) -> String {
var buffer = ""
for character in str {
buffer.insert(character, atIndex: buffer.startIndex)
}
return buffer
}
// reverse string then iterate forward.
var newStr = myReverse(str)
for char in newStr {
println(char)
// do some code here
}
this?
extension String {
var reverse: String {
var reverseStr = ""
for character in self {
reverseStr = String(character) + reverseStr
}
return reverseStr
}
}
Could someone please tell me if I am missing something here... I am trying to parse individual JSON objects out of a data stream. The data stream is buffered in a regular NSString, and the individual JSON objects are delineated by a EOL marker.
if([dataBuffer rangeOfString:#"\n"].location != NSNotFound) {
NSString *tmp = [dataBuffer stringByReplacingOccurrencesOfString:#"\n" withString:#"NEWLINE"];
NSLog(#"%#", tmp);
}
The code above outputs "...}NEWLINE{..." as expected. But if I change the #"\n" in the if-statement above to #"}\n", I get nothing.
Why don't you use - (NSArray *)componentsSeparatedByString:(NSString *)separator? You can give it a separator of #"\n" and the result will be a convenient array of strings representing your individual JSON strings which you can then iterate over.
if([dataBuffer rangeOfString:#"\n"].location != NSNotFound) {
NSArray* JSONstrings = [dataBuffer componentsSeparatedByString:#"\n"];
for(NSString* oneString in JSONstrings)
{
// here's where you process individual JSON strings
}
}
If you do mess with the terminating '}' you could make the JSON data invalid. Just break it up and pass it to the JSON library. There could easily be a trailing space after the '}' that is causing the problem you are observing.
How would I say that if a UITextField has #"-" in it, do something.
Right now my code is like this. It doesn't seem to work:
if (MyUITextField.text == #"-") {
NSRange range = {0,1};
[a deleteCharactersInRange:range];
MyUITextField.text = MyUILabel.text;
}
I know that I am doing something very wrong with the code. Please help.
try changing == to [MyUITextField.text isEqualToString:#"-"]
as == tests to see if they are the same object, while isEqualToString compares the contents of the strings.
Assuming your string is defined as:
NSString *str = #"foo-bar";
To check if your string contains "-" you can do the following:
if ([str rangeOfString:#"-"].length > 0)
{
NSLog(#"Contains -");
}
It looks like you wanted to delete the first character if a string starts with a given character. In this case you can do something like this:
if ([str hasPrefix:#"f"])
{
NSLog(#"Starts with f");
}