owensd / json-swift Goto Github PK
View Code? Open in Web Editor NEWA basic library for working with JSON in Swift.
License: MIT License
A basic library for working with JSON in Swift.
License: MIT License
I have a really weird bug.
JSON cannot be parsed by my iPhone 6 that runs iOS 8.1. It parses fine with the iPhone 6 Simulator.
This is an example of the code that I am trying to execute:
func testParsingObject() {
let str = NSString(string: "{}")
let data = str.dataUsingEncoding(NSUTF8StringEncoding)
let json = JSON.parse(data!) // This line fails.
}
func testParsingNumber() {
let str = NSString(string: "{\"number\": 50}")
let data = str.dataUsingEncoding(NSUTF8StringEncoding)
let json = JSON.parse(data!) // This line fails.
}
func testParsingString() {
let str = NSString(string: "{\"string\":\"here is a string\"}")
let data = str.dataUsingEncoding(NSUTF8StringEncoding)
let json = JSON.parse(data!) // This line fails.
}
Here is the kind of error crash that I am seeing (I just checked and it happens irrelevant of the data I pass in):
#0 0x0000000120009088 in dyld_fatal_error ()
#1 0x000000012000be48 in dyld::halt(char const*) ()
#2 0x000000012000bf18 in dyld::fastBindLazySymbol(ImageLoader**, unsigned long) ()
#3 0x0000000197ba5198 in dyld_stub_binder ()
#4 0x00000001007789c8 in JSONLib.JSValue.parseObject (JSONLib.JSValue.Type)<A : Swift.SequenceType>(JSONLib.ReplayableGenerator<A>) -> (value : Swift.Optional<JSONLib.JSValue>, error : Swift.Optional<JSONLib.Error>) ()
#5 0x0000000100777bfc in JSONLib.JSValue.parse (JSONLib.JSValue.Type)<A : Swift.SequenceType>(JSONLib.ReplayableGenerator<A>) -> (value : Swift.Optional<JSONLib.JSValue>, error : Swift.Optional<JSONLib.Error>) ()
#6 0x0000000100776578 in JSONLib.JSValue.parse (JSONLib.JSValue.Type)(Swift.UnsafeBufferPointer<Swift.UInt8>) -> (value : Swift.Optional<JSONLib.JSValue>, error : Swift.Optional<JSONLib.Error>) ()
#7 0x0000000100777a68 in JSONLib.JSValue.parse (JSONLib.JSValue.Type)(ObjectiveC.NSData) -> (value : Swift.Optional<JSONLib.JSValue>, error : Swift.Optional<JSONLib.Error>) ()
#8 0x000000010012ead8 in Spokes.AppDelegate.testParsingObject (Spokes.AppDelegate)() -> () at /Users/sebinsua/Development/Spokes/Spokes/AppDelegate.swift:40
I have hardcoded the data and am executing it from the AppDelegate to prove to myself that it's not related to any other logic I have in play and seems to be dependent on whether we've compiled for a device or not.
Any ideas? I'll come back if I work anything out myself.
pretty printing a .JSArray
is missing the opening brace like:
"something":
{
"a": "a",
"b": "b",
"c": "c"
}
]
when this is expected
"something":
[
{
"a": "a",
"b": "b",
"c": "c"
}
]
A possible fix is to replace line 251
of JSValue
with:
case .JSArray(let array): return "[\(newline)" + (array.map({ "\(nextIndent)\($0.prettyPrint(indent, level + 1))" })) .joinWithSeparator(",\(newline)") + "\(newline)\(currentIndent)]"
Do it!
Currently, json-swift only handles escaped quote characters, but according to ECMA-404 (json.org), there are other control sequences which need to be unescaped while parsing strings, i.e. ", , /, \b, \f, \n, \r, \t and \u.
The following test fails with the current HEAD:
func testParseStringWithEscapedControlCharacters() {
let string = "\"\\\\\\/\\n\\r\\t\""
let jsvalue = JSValue.parse(string)
XCTAssertTrue(jsvalue.error == nil, jsvalue.error?.userInfo?.description ?? "No error info")
XCTAssertEqual(jsvalue.value!.string!, "\\/\n\r\t")
}
json-swift will parse these strings, even though they are not JSON:
[-]
[0.3e+]
[0.3e]
[0E+]
[0E]
[0e+]
[0e]
[1.0e+]
[1.0e-]
[1.0e]
diff --git a/src/JSValue.Parsing.swift b/src/JSValue.Parsing.swift
index e9cdd8e..26dac32 100644
--- a/src/JSValue.Parsing.swift
+++ b/src/JSValue.Parsing.swift
@@ -268,7 +268,7 @@ extension JSValue {
fallthrough
case (_, Token.Zero...Token.Nine, NumberParsingState.ExponentDigits):
- exponent = exponent * 10 + codeunit - Token.Zero
+ exponent = exponent * 10 + Int(codeunit) - Int(Token.Zero)
case (_, Token.Period, NumberParsingState.Whole):
state = .Decimal
@@ -383,11 +383,11 @@ extension JSValue {
private static func parseHexDigit(digit: UInt8) -> Int? {
if Token.Zero <= digit && digit <= Token.Nine {
- return digit - Token.Zero
+ return Int(digit - Token.Zero)
} else if Token.a <= digit && digit <= Token.f {
- return 10 + digit - Token.a
+ return Int(10 + digit - Token.a)
} else if Token.A <= digit && digit <= Token.F {
- return 10 + digit - Token.A
+ return Int(10 + digit - Token.A)
} else {
return nil
}
The line:
https://github.com/owensd/json-swift/blob/master/src/JSValue.swift#L255
The output is
{ "key1": "value1"{
,
"key2": "value2"
}
But should be:
{
"key1": "value1",
"key2": "value2"
}
Changing the line to:
return "{\(newline)" + (dict.map({ "\(nextIndent)\"\($0)\":\(space)\($1.prettyPrint(indent, level + 1))"})).joinWithSeparator(",\(newline)") + "\(newline)\(currentIndent)}"
fixes the problem
Need to do some work to re-enable them.
In JavaScript I can do this to receive a string with no spaces:
JSON.stringify(json, null, 0);
I've tried this but it didn't work as I expected:
json.stringify(indent: "")
It seems that this library isn't parsing the unicode value \u0026
within a string correctly.
The string I'm getting (in this case from the Instagram API) is:
access_token=29347\u0026max_id=80776
Which should translate to:
access_token=29347&max_id=80776
Here's the output from json-swift compared to the python equivilant.
let string = "\"access_token=29347\\u0026max_id=80776\""
println(string)
"access_token=29347\u0026max_id=80776" // string literal is valid
let jsvalue = JSValue.parse(string)
println(jsvalue)
// Prints (Optional("access_token=293476200max_id=80776"), nil)
As you can see, what should have been decoded into "&" is instead replaced with "6200"
Python:
>>> import json
>>> value = json.loads('"access_token=29347\u0026max_id=80776"')
>>> if value == "access_token=29347&max_id=80776":
... print "Passed"
Passed
When I look at the JSON object that this is parsing, the order is not the same as the end parsed data. I have ordered the array in the API, but when I loop through the array in the app, the order is different.
json-swift does crash while parsing file i_number_huge_exp.json with fatal error: Double value cannot be converted to Int because the result would be greater than Int.max
Show I just drag 'src' into the project and add to targets?
I've updated to the newest Xcode, pulled down the latest changes, and found that trying to create a JSON object throws up a compiler error. I was doing things like so:
var error: NSError?
let bundlePath = NSBundle.mainBundle()
let jsonPath = bundlePath.pathForResource(filename, ofType: "json")
let jsonData = NSData.dataWithContentsOfFile(jsonPath!, options: .DataReadingMappedIfSafe, error: nil)
let jsonDict = NSJSONSerialization.JSONObjectWithData(jsonData, options: .MutableContainers, error: &error) as NSDictionary
let json = JSON(jsonDict)
IIRC it was Beta 5 that added the access control stuff, so I wasn't quite sure what had changed here. Adding public
to JSValue
's init doesn't work. I'm getting more comfortable with Swift (thanks for the blog posts!) and iOS development, but that was basically the limit of my ability to debug things. Any ideas what might be going on?
I'm pulling in some data from the Instagram API and I'm getting something very strange go on when using this json library.
The following code does not fall into the if block, so nextURLString is never unwrapped, as if the value does not exist:
if let nextURLString = json["pagination"]["next_url"].string {
self.nextPaginationURL = NSURL(string: nextURLString) // doesnt exeute
}
However a quick check in the console shows that the value does exist at that path:
(lldb) po json["pagination"]["next_url"].string!
"https://api.instagram.com/v1/users/..."
Strangely, the following code (as an alternative to the if let
unwrapping) does work, and the code in the if block is executed.
let nextURLString = json["pagination"]["next_url"].string
if nextURLString != nil {
self.nextPaginationURL = NSURL(string: nextURLString!) // this *is* executed
}
Any idea why this could be?
I thought the whole
buffer.next()
while buffer.current != nil {
if let unicode = buffer.current { // ... somewhere, buffer.next() is called
dance was kind of ugly: you're dealing with the overhead of using a generator, but receiving none of the benefits it provides (e.g. for in
loops). Also, using a struct for your BufferedGenerator seems odd -- you end up using a class as a backing store anyway, and having it as a struct means using inout parameters all over the place. There's a discussion on the dev forums that argues the case why GeneratorTypes should, in general, just be reference types.
Anyway, I took a different view on the parsing issue -- it's not that you need access to the "current" element so much as it is that determining what sub-parser to use forces the generator to consume one too many characters. Really, if you could just rewind the generator back a character, everything would be simple. So I wrote a class that lets you do that:
/// Creates a `GeneratorType`/`SequenceType` that allows rewinding and can be passed around.
final public class RewindableGenerator<S : CollectionType where S.Index : BidirectionalIndexType> : GeneratorType, SequenceType {
typealias Sequence = S
private var currentIndex: Sequence.Index
private let prestartIndex: Sequence.Index
private let seq: Sequence
// store the `current` element, but it's not really necessary
private(set) var current: Sequence.Generator.Element?
/// Initializes a new `RewindableGenerator<S>` with an underlying `CollectionType`.
/// Requires that `CollectionType.Index` be a `BidirectionalIndexType`.
///
/// :param: sequence the sequence that will be used to traverse the content.
public init(_ sequence: Sequence) {
self.seq = sequence
self.currentIndex = sequence.startIndex
self.prestartIndex = sequence.startIndex.predecessor()
}
/// Moves the `current` element to the next element if one exists.
///
/// :return: The `current` element or `nil` if the element does not exist.
public func next() -> Sequence.Generator.Element? {
if currentIndex == seq.endIndex {
return nil
}
currentIndex = currentIndex.successor()
if currentIndex != seq.endIndex {
self.current = seq[currentIndex]
} else {
self.current = nil
}
return self.current
}
/// Moves the `current` element to the previous element if one exists.
///
/// :return: The `current` element or `nil` if the element does not exist.
public func previous() -> Sequence.Generator.Element? {
if currentIndex == self.prestartIndex {
return nil
}
currentIndex = currentIndex.predecessor()
if currentIndex != self.prestartIndex {
self.current = seq[currentIndex]
} else {
self.current = nil
}
return self.current
}
public func generate() -> Self {
self.previous()
return self
}
}
I don't have time to do a full pull request, but I believe this technique should enable you to use for scalar in buffer
loops in all the various parsing functions; each new time you start to iterate through the JSON in one of the sub-parsers, there will implicitly be a call to generate()
which will in turn automatically rewind the parsing by a character, revealing that lost character as the first item of iteration.
Here's a goofy example use from my testing, that just grabs the characters between paired brackets and adds them to an array:
var words: [String] = []
var unmatched: [String] = []
func parseBrackets(gen: RewindableGenerator<String.UnicodeScalarView>) {
var sbuf = ""
for (idx, c) in enumerate(gen) {
if idx == 0 {
if c == "[" {
continue
} else {
// error or something
return
}
}
switch c {
case "[":
// nested brackets
parseBrackets(gen)
case "]":
words.append(sbuf)
return
default:
sbuf.append(c)
}
}
unmatched.append(sbuf)
}
let s = "[This] [is ][a] weird [ne[st]e[d]] [\u{aaef}string!"
let g = RewindableGenerator(s.unicodeScalars)
for c in g {
if c == "[" {
parseBrackets(g)
}
}
// words == ["This", "is ", "a", "st", "d", "nee"]
// unmatched == "ꫯstring!"
json-swift does crash while parsing file n_structure_open_array_object.json with EXC_BAD_ACCESS
Number parsing performance is ok:
NSJONSerialization:
performance results: min: 10.716ms, max: 17.004ms, avg: 11.802ms
JSONLib:
performance results: min: 7.801ms, max: 20.115ms, avg: 8.418ms
From this file: https://github.com/chadaustin/sajson/blob/master/testdata/mesh.json
String parsing performance is terrible:
NSJONSerialization:
performance results: min: 4.687ms, max: 9.183ms, avg: 5.242ms
JSONLib:
performance results: min: 24.347ms, max: 36.947ms, avg: 26.928ms
From this file: https://github.com/chadaustin/sajson/blob/master/testdata/twitter.json
In addition to having an arbitrary depth limit, some of this needs to be revisited.
https://github.com/owensd/json-swift/blob/master/JSON/JSON.swift#L11
typealias JSValue = JSON
JSValue is defined in JavaScriptCore.framework. A JSValue is a reference to a value within the JavaScript object space of a JSVirtualMachine. Therefore, I think it's better to use another alias to avoid confusion.
Due to the apparent lack of keyed subscript support in Swift for Objective-C objects, I'm currently doing this:
let jsFunction = context.valueForKey("foo") as JSValue
In this case, JSValue
is the type from JavaScriptCore
.
As soon as I import JSONLib
that same line won't compile because 'JSValue' is ambiguous for type lookup in this context
.
Any advice on how to work around this?
I'm not terribly familiar with how to import libraries, especially with Swift. Cocoa pods made everything really easy. So who can I use this in my project?
Also, how can I get this into a playground?
It would be great to have Swift 3.0 support
JSValue.number returns "Double?", but JSValue literals cannot accept them. This seems a little inconsistent.
extension JSValue {
static func Array() -> JSValue {
return JSValue(JSArrayType())
}
static func Object(minimumCapacity: Int = 8) -> JSValue {
return JSValue(JSObjectType(minimumCapacity: minimumCapacity))
}
func append(value: JSValue) {
if var array = self.array {
array.append(value)
}
}
}
func +=(inout lhs: JSValue, rhs: JSValue) {
lhs.append(rhs)
}
// Making use of the JSON alias
var object = JSON.Object()
object["one"] = JSON(1)
var array = JSON.Array()
for index in 1...10 {
array += JSON(index)
}
object["array"] = array
var root = JSON.Object()
root["object"] = object
When I try the following:
enum Foo: String {
case Bar = "bar"
}
let array: JSON = [
Foo.Bar.toRaw()
]
I get this error:
'Array<$T4>' is not convertible to 'JSON'
It seems reasonable to be able to do this - am I going about this the wrong way?
[ "url" : "should escape double quotes \"" ]
result in "{\"url\":should escape double quotes \"\"}"
should be "{\"url\":should escape double quotes \\\"\"}"
I was going through the code, and I noticed that you're doing assert(true, "The JSON value is invalid")
, but you probably mean assert(false)
:)
Swift 1.2 and Xcode v6.3 (6D570)
I keep trying to do something like this:
json["something"] = JSON.JSONString("this")
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.