Giter Club home page Giter Club logo

generic-json-swift's Introduction

generic-json-swift's People

Contributors

cjmconie avatar crumpf avatar ilyapuchka avatar iwill avatar jonnolen avatar roznet avatar rudedogdhc avatar zoul avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

generic-json-swift's Issues

Add Linux support

Running tests through Docker:

docker run --rm --volume "$(pwd):/package" --workdir "/package" swift:4.2 /bin/bash -c "swift test"
  • Make swift test --generate-linuxmain a part of the build process so that the list of tests for Linux never gets out of sync?
  • Make the Linux build & test part of the CI build
  • Change the repo layout to the standard SPM one?
  • Contribute a better error message for BuildPlan.missingLinuxMain to SPM?

Usage after decoding

Hi, I am looking to understand the proper call-site usage of the JSON type after decoding.

For context, I use the Swift 4 Codable protocol extensively, however, in some cases the structure of the JSON is unknown - which is how I came across generic-json - and so I'd like to use the JSON type as the decode target.

In the example below, I am trying to access resource and create an array of String containing the typevalues.

/*
 {
  "type": "string",
  "subject": {
    "resource": "https://host/party/...",
    "value": {
      "identities": [
        {
          "type": "link_id"
        },
        {
          "type": "parse_id"
        }
      ]
    }
  }
}
*/

let responseData =
    """
    {"type":"string","subject":{"resource":"https://host/party/...","value":{"identities":[{"type":"link_id"},{"type":"parse_id"}]}}}
    """.data(using: .utf8)!

//: ## The 'traditional' method

let jsonObject = try? JSONSerialization.jsonObject(with: responseData, options: [])

if
    let jsonDictionary = jsonObject as? [String : Any],
    let subject = jsonDictionary["subject"] as? [String : Any],
    let resource = subject["resource"] as? String {
    
    print("Resource: \(resource)")
    
    if
        let value = subject["value"] as? [String : Any],
        let identities = value["identities"] as? [[String : Any]],
        let types: [String] = identities.compactMap({ $0["type"] as? String }) {
        
        print("Types: \(types)")
    }
    
}

//: ## The GenericJSON method

let decoder = JSONDecoder()
let json = decoder.decode(JSON.self, from: responseData)

// What is the best way of accessing `resources` and creating the array of `types`?

Automate release process

I keep forgetting to bump CocoaPods (see #10). It would be nice to automate the release process so that we update a changelog, tag the release and bump CocoaPods. Fastlane?

Mutation support

The demand for mutation support is obvious (#6, #25). I was trying to stick to immutability, but if people need to patch a JSON value, the foo["bar"] = baz syntax is very hard to beat – it’s intuitive, discoverable and terse. And if people want to stick to immutable values, they can always just declare their JSON values using let and that’s it. So I decided we should give in and offer both a mutable API (writable subscripts, merge(with:)) and an immutable update API (merging(with:), something like updating(key:to:)), allowing the callers to pick whatever is best for them.

How to use JSON in Core Data

Core Data Interop

How should JSON properties be used in conjunction with Core Data?

Discussion

JSON neatly solves the case where a Swift type needs to be created from a freeform json string (i.e. the json schema is unknown at compile time and subject to change). Furthermore, it allows the json to modelled in a type safe way.

Codable conformance allows the type to be easily converted to and from Data. This makes persisting the type to disk as data relatively simple.

However using the JSON type in the context of Core Data poses challenges. JSON was designed as a pure Swift type whereas Core Data has is origin in Objective-C.

The issue is that using a property of type JSON on an NSManagedObject subclass is not permitted. The compiler produces an error:

class Person: NSManagedObject {
    
    @NSManaged var firstName: String
    @NSManaged var lastName: String
    @NSManaged var metaData: JSON // Property cannot be marked @NSManaged because its type cannot be represented in Objective-C
    
}

This is expected since Core Data does not support JSON out of the box, and it turns out, the metaData attribute cannot be marked as Transformable since Swift Enums with associated values (which JSON is built upon) cannot be represented in Objective-C and so conformance to NSCoding seems unlikely.

By contrast Swift dictionaries, say of type [String: Any] can be used on an NSManagedObject subclass directly since they conform to NSCoding and so their corresponding model attribute can be marked as Transformable.

Options

How then should properties of type JSON be used in the context of Core Data? Or, does Core Data's tight integration with Objective-C mean the JSON type cannot be used?

Some options which come to mind:

  1. Add NSCoding conformance to JSON allowing the attribute's type to be Transformable.
    • This does not seem technically possible as mentioned above.
  2. Use a custom ValueTransformer.
    • This is not possible because JSON does not derive from NSObject.
  3. Add a toDictionary() and fromDictionary methods facilitating [String: Any] transformations.
    • The attribute type will be Transformable with a custom setter and getter in the NSManagedObject's subclass to do the transformation.
  • This seems a bit heavy handed. The transformation cost will be paid on each get or set.

Are they other ways to do this? (I am relatively new to Core Data).

Example Object

class Person: NSManagedObject {
    
    @NSManaged var firstName: String
    @NSManaged var lastName: String
    @NSManaged var metaData: JSON // Property cannot be marked @NSManaged because its type cannot be represented in Objective-C
    
}
let personAData: Data = """
    {
      "firstName": "John",
      "lastName": "Appleseed",
      "metaData": {
        "foo": "bar",
        "rah": [1, 2, 3]
      }
    }
    """.data(using: .utf8)!

let personBData: Data = """
    {
      "firstName": "Joe",
      "lastName": "Lemon",
      "metaData": {
        "yaz": {
          "tar": 1234.00
        }
      }
    }
    """.data(using: .utf8)!

Here the metaData property needs to hold and store flexible json which makes JSON a good candidate.

Tags Are Not SPM-Compliant

I’m trying to use your project in one of my SPM-based project, but your tags are not SPM-compliant. Tags should be directly the version number, with no suffix nor prefix.

Would you be so kind as to fix that?
Thanks

Provide `Decoder` to turn `JSON` into `Decodable`

Hej, it is nice to have JSON as an intermediate correctly-typed abstraction over same JSON data, but at some point I might still want to work with some dedicated data class. However, at that point I might only have the JSON left and I can not make use of some Decoder to turn my JSON into an appropriate Decodable for the task at hand.

I have a concret use case for that, where the concrete structure of my JSON is only clear at a later point in time.

Thus, I would suggest to provide a GenericJSONDecoder to turn a JSON into a Decodable. Analogous to JSONDecoder and Data.

The solution proposed here solves the issue in the meantime, but is really wasteful and rather just a short-time fix:

func decode<T: Decodable>(from json: JSON) throws -> T {
    let data = try JSONEncoder().encode(json)
    return try JSONDecoder().decode(T.self, from: data)
}

Merging JSON values

In #6 we came across a need to merge JSON values, one expected use case being partial updates of data from the server. The basic idea is to introduce a merging(with: JSON) -> JSON function that would return a new JSON value based on merging the current one with new incoming data. We need to specify the merging algorithm, especially with regards to missing keys, incompatible data types and conflicts. I plan to get back to this in about 10 hours.

Initialisation using `nil` or `NSNull`

Currently, initialisation from an Any type does not allow nil or NSNull values (as far as I can tell). Would it make sense to support this?

There are two use cases I have run across:

  1. Initialising using a Swift dictionary containing nil:
let modelUsingNil: Any = [
            "foo": "hello",
            "bar": nil
        ]
        
let json1 = try! JSON(modelUsingNil) // Thread 1: Fatal error: 'try!' expression unexpectedly raised an error
print(json1)
  1. Initialising using an object containing NSNull. For example deserialising a json object JSONSerialization.jsonObject().
let jsonData = try! JSONSerialization.data(withJSONObject: modelUsingNil)
let modelWithNSNull = try! JSONSerialization.jsonObject(with: jsonData)
print(modelWithNSNull)

// see: https://stackoverflow.com/a/28024012/3589408
let json2 = try! JSON(modelWithNSNull) // Thread 1: Fatal error: 'try!' expression unexpectedly raised an error
print(json2)

Potential solution (wip)

For NSNull initialisation, the following could work:

extension JSON {
    public init(_ value: Any) throws {
        switch value {
        case _ as NSNull: 
            self = .null
        case let num as Float:
            self = .number(num)
        case let num as Int:
            self = .number(Float(num))
        case let str as String:
            self = .string(str)
        case let bool as Bool:
            self = .bool(bool)
        case let array as [Any]:
            self = .array(try array.map(JSON.init))
        case let dict as [String: Any]:
            self = .object(try dict.mapValues(JSON.init))
        default:
            throw BVJSONError.decodingError
        }
    }
}

NSNumber Initialization

Foundation's JSONSerialization.jsonObject(with:options:) transforms a Data type into an object of type Any. Since JSONs initialiser accepts an Any, this should work, but currently does not.

Note Issue 22 solved NSNull initialisation, which turned out to be a special case.

The primary issue is around the handling of NSNumber in the initialiser. JSONSerialization uses NSNumber to represent both numbers and booleans. This causes the following test to fail:

  1. Boolean transformation
func testInitializationFromJSONSerilizationBool() throws {
        
        let jsonData = "{\"boolean\": true}".data(using: .utf8)!
        let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
        let json = try JSON(jsonObject)
        
        XCTAssertEqual(JSON.bool(true), json["boolean"]!)  // XCTAssertEqual failed: ("true") is not equal to ("1.0")
        
    }

In JSON's initialiser, the NSNumber is being downcast as Float, thus loosing it's boolean semantics. This is best explained here.

Here is SwiftyJSON's init for comparison. They also have a convenience function to determine whether an NSNumber is derived from a boolean.

  1. Double floating-point

The handling of NSNumbers's numbers are also leading to some odd results.

Similarly, when creating a JSON value with a fairly typical floating point value, say a latitude, results in precision loss:

let lat = JSON(18.471249902149363)  // 18.47125

Swift's Float represents a 32-bit floating-point number which results in precision loss in the cast above. This would not be the case if Double, which represents a 64-bit, were used.

My initial thinking was that we should change JSON's number associated value from Float to Double to accommodate double floating-point, especially since JavaScript's Number type also uses a 64-bit value. However now, looking at the SwiftyJSON implementation, maybe it should be done away with in favour of NSNumber.

What are your thoughts?

JSON initialisation using variables of primitive type

When trying to initialise a JSON type, why would the compiler be happy with a literal string (e.g. "b"), but unhappy with a variable with a type of String (e.g. someActualString )?

a.
let foo: JSON = ["a": "b"]   // all good

b.
let someJSONString: JSON = "b"
let bar: JSON = ["a": someJSONString] // all good

c.
let someActualString: String = "b"
let taz: JSON = ["a": someActualString] // compiler error: Cannot convert value of type 'String' to expected dictionary value type 'JSON'

Publish new version of the pod?

There are a lot of useful additions around querying that have been added but are unavailable via general pod install. Could you push a new release to pull up these changes into cocoapods?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.