How to parse JSON into NSManagedObject with Codable? [duplicate] - json

Codable seems a very exciting feature. But I wonder how we can use it in Core Data? In particular, is it possible to directly encode/decode a JSON from/to a NSManagedObject?
I tried a very simple example:
and defined Foo myself:
import CoreData
#objc(Foo)
public class Foo: NSManagedObject, Codable {}
But when using it like this:
let json = """
{
"name": "foo",
"bars": [{
"name": "bar1",
}], [{
"name": "bar2"
}]
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let foo = try! decoder.decode(Foo.self, from: json)
print(foo)
The compiler failed with this errror:
super.init isn't called on all paths before returning from initializer
and the target file was the file that defined Foo
I guess I probably did it wrong, since I didn't even pass a NSManagedObjectContext, but I have no idea where to stick it.
Does Core Data support Codable?

You can use the Codable interface with CoreData objects to encode and decode data, however it's not as automatic as when used with plain old swift objects. Here's how you can implement JSON Decoding directly with Core Data objects:
First, you make your object implement Codable. This interface must be defined on the object, and not in an extension. You can also define your Coding Keys in this class.
class MyManagedObject: NSManagedObject, Codable {
#NSManaged var property: String?
enum CodingKeys: String, CodingKey {
case property = "json_key"
}
}
Next, you can define the init method. This must also be defined in the class method because the init method is required by the Decodable protocol.
required convenience init(from decoder: Decoder) throws {
}
However, the proper initializer for use with managed objects is:
NSManagedObject.init(entity: NSEntityDescription, into context: NSManagedObjectContext)
So, the secret here is to use the userInfo dictionary to pass in the proper context object into the initializer. To do this, you'll need to extend the CodingUserInfoKey struct with a new key:
extension CodingUserInfoKey {
static let context = CodingUserInfoKey(rawValue: "context")
}
Now, you can just as the decoder for the context:
required convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }
guard let entity = NSEntityDescription.entity(forEntityName: "MyManagedObject", in: context) else { fatalError() }
self.init(entity: entity, in: context)
let container = decoder.container(keyedBy: CodingKeys.self)
self.property = container.decodeIfPresent(String.self, forKey: .property)
}
Now, when you set up the decoding for Managed Objects, you'll need to pass along the proper context object:
let data = //raw json data in Data object
let context = persistentContainer.newBackgroundContext()
let decoder = JSONDecoder()
decoder.userInfo[.context] = context
_ = try decoder.decode(MyManagedObject.self, from: data) //we'll get the value from another context using a fetch request later...
try context.save() //make sure to save your data once decoding is complete
To encode data, you'll need to do something similar using the encode protocol function.

CoreData is its own persistence framework and, per its thorough documentation, you must use its designated initializers and follow a rather specific path to creating and storing objects with it.
You can still use Codable with it in limited ways just as you can use NSCoding, however.
One way is to decode an object (or a struct) with either of these protocols and transfer its properties into a new NSManagedObject instance you've created per Core Data's docs.
Another way (which is very common) is to use one of the protocols only for a non-standard object you want to store in a managed object's properties. By "non-standard", I mean anything thst doesn't conform to Core Data's standard attribute types as specified in your model. For example, NSColor can't be stored directly as a Managed Object property since it's not one of the basic attribute types CD supports. Instead, you can use NSKeyedArchiver to serialize the color into an NSData instance and store it as a Data property in the Managed Object. Reverse this process with NSKeyedUnarchiver. That's simplistic and there is a much better way to do this with Core Data (see Transient Attributes) but it illustrates my point.
You could also conceivably adopt Encodable (one of the two protocols that compose Codable - can you guess the name of the other?) to convert a Managed Object instance directly to JSON for sharing but you'd have to specify coding keys and your own custom encode implementation since it won't be auto-synthesized by the compiler with custom coding keys. In this case you'd want to specify only the keys (properties) you want to be included.
Hope this helps.

Swift 4.2:
Following casademora's solution,
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError() }
should be
guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }.
This prevents errors that Xcode falsely recognizes as array slice problems.
Edit: Use implicitly unwrapped optionals to remove the need to force unwrap .context every time it is being used.

As an alternative for those who would like to make use of XCode's modern approach to NSManagedObject file generation, I have created a DecoderWrapper class to expose a Decoder object which I then use within my object which conforms to a JSONDecoding protocol:
class DecoderWrapper: Decodable {
let decoder:Decoder
required init(from decoder:Decoder) throws {
self.decoder = decoder
}
}
protocol JSONDecoding {
func decodeWith(_ decoder: Decoder) throws
}
extension JSONDecoding where Self:NSManagedObject {
func decode(json:[String:Any]) throws {
let data = try JSONSerialization.data(withJSONObject: json, options: [])
let wrapper = try JSONDecoder().decode(DecoderWrapper.self, from: data)
try decodeWith(wrapper.decoder)
}
}
extension MyCoreDataClass: JSONDecoding {
enum CodingKeys: String, CodingKey {
case name // For example
}
func decodeWith(_ decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
}
}
This is probably only useful for models without any non-optional attributes, but it solves my problem of wanting to use Decodable but also manage relationships and persistence with Core Data without having to manually create all my classes / properties.
Edit: Example of it in use
If I have a json object:
let myjson = [ "name" : "Something" ]
I create the object in Core Data (force cast here for brevity):
let myObject = NSEntityDescription.insertNewObject(forEntityName: "MyCoreDataClass", into: myContext) as! MyCoreDataClass
And I use the extension to have the object decode the json:
do {
try myObject.decode(json: myjson)
}
catch {
// handle any error
}
Now myObject.name is "Something"

Related

Swift JSON Decoder Error: Missing something

I am using a JSON encoder and decoder to be able to save a custom object to my user defaults. The object I am trying to encode and decode is a Mapbox Directions Route Object (See their documentation here: Mapbox Route
I have successfully used a JSON Encoder and Decoder for many things in my app. But for some reason, I can't get it to work with the Route object. Following is my JSON Encoder:
//Encode route response Object to pass it to Defaults
do {
// Create JSON Encoder
let encoder = JSONEncoder()
// Encode Note
let encodedRoute = try encoder.encode(route)
// Write/Set Data
UserDefaults.standard.set(encodedRoute, forKey: "routeObject")
print("Encoded route Object.")
} catch {
print("Unable to Encode route (\(error))")
}
Note that the console prints "Encoded route Object.", which should mean it worked.
Following now is my Decoder, which does not work and throws me the error I added to the catch statement:
//Read/Get response Object
if let encodedRoute = UserDefaults.standard.data(forKey: "routeObject") {
do {
// Create JSON Decoder
let decoder = JSONDecoder()
// Decode Note
myRoute = try decoder.decode(MapboxDirections.Route.self, from: encodedRoute)
print("Successfully decoded route Object!")
} catch {
print("Unable to Decode route. Error: \(error)")
}
}
The error which I get is: "Unable to Decode route. Error: missingOptions". Since RouteOptions is part of the Route object, I am guessing that something went wrong while encoding or decoding, leading to a missing piece of the object. I tried encoding and decoding a RouteResponse (which contains multiple Routes), which returned me "missingCredentials". Again, Credentials is part of RouteResponse. What could I have done wrong? Both classes are working well otherwise and both already use many other Mapbox functions, so it cannot be something with the imports or such.
If there is a workaround to pass an object to another class without using getter and setters (which bugs also), I'd be happy to hear it.
It's a custom error thrown by the MapBox API.
From their code:
/**
An error that occurs when encoding or decoding a type defined by the MapboxDirections framework.
*/
public enum DirectionsCodingError: Error {
/**
Decoding this type requires the `Decoder.userInfo` dictionary to contain the `CodingUserInfoKey.options` key.
*/
case missingOptions
...
}
In Route, we can see the comments on init(from decoder:), which is, under the hood called by decoder.decode(_:from:):
/**
Initializes a route from a decoder.
- precondition: If the decoder is decoding JSON data from an API response, the `Decoder.userInfo` dictionary must contain a `RouteOptions` or `MatchOptions` object in the `CodingUserInfoKey.options` key. If it does not, a `DirectionsCodingError.missingOptions` error is thrown.
- parameter decoder: The decoder of JSON-formatted API response data or a previously encoded `Route` object.
*/
public required init(from decoder: Decoder) throws {
try super.init(from: decoder)
}
So you need to put, something like:
let decoder = JSONDecoder()
decoder.underInfo = [CodingUserInfoKey.options: someRouteOptions]
// or
decoder.underInfo = [CodingUserInfoKey.options: someMatchOptions]
I don't use that framework, but I guess that the usage on why which one to use should appear on their documentation or the git repo code. You can search maybe for RouteOptions/MatchOptions there.

Swift JsonEncoder element order

I'm currently working on a project were the provided API sadly is a hot mess. I'm encoding a Codable with several nested Types following the first solution in this medium article about Indeterminate Types with Codable in Swift.
So, I'm using an enum to allow different types in my Codable Messages roughly as follows:
struct CommandBase: Encodable {
let command: CommandType
enum CodingKeys: String, CodingKey {
let command = "c"
}
}
enum CommandType: Encodable {
case someCommand(someContainer)
case someOtherCommand(someOtherContainer)
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .someCommand(let someContainer):
try container.encode("something", forkey: .name)
try container.encode(someContainer, forKey: .container)
try container.encode("etc", forKey: .etc)
case .someOtherCommand(let someOtherContainer):
// about the same as above case
}
}
enum CodingKeys: String, CodingKey {
case name
case container
case etc
}
}
The problem is, as I mentioned the API I have to work with is a mess and NEEDS the json in the correct order. But for some reason the Standard JsonEncoder always puts the .container first.
This is a refactor the earlier version used several generic types and resulted in hardly readable code but the order was met.
The JsonEncoder has an outputFormatting attribute which uses a OutputFormatting type like .sortedKeys to sort the keys alphabetical. But I don't understand if and how its possible to utilize this attribute to sort a nested codable.
has anyone an idea how I could sort the encoded message like I need?

Decode nested JSON child with Decodable [duplicate]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
The new Swift "Decoder" class sounds like a great way to parse JSON data, but all of the examples I've found use a well-known, well-defined 'struct' to do so.
In my case I'm querying an arbitrary website that returns a HUGE JSON string and I only care about a few of the (deeply nested) fields, so I don't want to take all that time to define a 'struct' to get at them.
Is it even possible to do this with "Decoder"? And if so, how does one go about it?
The question seems to be based on a misapprehension about how Decodable works. As a convenience, Decodable is willing to do some automatic code generation behind the scenes so that you can define a struct or nest of structs and just decode the entirety of the JSON. But you are not required to take advantage of that in order to decode JSON.
There is no need to define struct properties for "fields" you don't care about. If a JSON dictionary contains 100 keys and your corresponding struct contains just one property, no problem; that key will be fetched, and no others.
With regard to the "deeply nested" part, it should not take you much time to write simple nested structs that perform the dive to reach the dictionary you really care about. But if you don't want to do even that, you could write an implementation of init(from:) that dives down and fetches out the desired values.
In other words, if you think of Decodable as consisting primarily of your implementation of init(from:), and learn to write the code that it needs, you will see that this JSON can be parsed in a few quick simple lines of code.
As an example, here's a JSON sketch of a deeply nested piece of information with a bunch of extra information at every level that we want to ignore:
{
"ignore": true,
"outer1": {
"ignore": true,
"outer2": {
"ignore": true,
"outer3": {
"name": "matt",
"ignore": true
}
}
}
}
What I'd like to do is define a very simple struct Person that consists solely of the deeply nested name:
struct Person : Decodable {
let name : String
}
I can do that! To do so, I implement Decodable myself, supplying a "hoover" CodingKey adopter struct and an implementation of init(from:), like this (this may look like a lot of work, but it isn't, because the AnyCodingKey implementation is boilerplate, copied and pasted from here, and the init(coder:) implementation is just a few lines of code that were easy to write):
struct Person : Decodable {
let name : String
struct AnyCodingKey : CodingKey {
var stringValue: String
var intValue: Int?
init(_ codingKey: CodingKey) {
self.stringValue = codingKey.stringValue
self.intValue = codingKey.intValue
}
init(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
init(from decoder: Decoder) throws {
var con = try! decoder.container(keyedBy: AnyCodingKey.self)
con = try! con.nestedContainer(keyedBy: AnyCodingKey.self, forKey: AnyCodingKey(stringValue:"outer1"))
con = try! con.nestedContainer(keyedBy: AnyCodingKey.self, forKey: AnyCodingKey(stringValue:"outer2"))
con = try! con.nestedContainer(keyedBy: AnyCodingKey.self, forKey: AnyCodingKey(stringValue:"outer3"))
let name = try! con.decode(String.self, forKey: AnyCodingKey(stringValue:"name"))
self.name = name
}
}
When I want to dive into the JSON and grab the name information, it's trivial:
let person = try! JSONDecoder().decode(Person.self, from: json)
The result is a Person object with name value "matt". Note that I didn't have to add any of the ignore keys and I didn't need to make a nest of structs.
Sure you can achieve this but with both JSonSerialization & Decodable , you have to serialize the json until reach the desired content then decode it ,but instead I recommend to create structs for root keys only then decode , think of it as it's a path from top to bottom don't decode a key that isn't in the path of your desired content

How to decode a nested JSON file into a swift class with an unowned property

I have a custom model with several classes that I've written in Swift for a document-based Mac app. As the user inputs data to their document, instances of one class Itinerary are created and stored within an array which is a property of another "root" object of a different class Course. On top of that, multiple courses can be created and are stored in an array in the app’s Document class. I'd like the user to be able to save their data and load it back up again at a later point. So I'm attempting to implement the Codable protocol on my custom types.
The Encodable conformance works fine; when I attempt to save, the desired JSON file is produced. The problem comes when decoding.
When attempting to decode each itinerary, its course property needs to be set before the end of initialization because it was defined as an unowned var that points back to the course it belong's to. The place where that happens is within the course's addItinerary(_:) func, which is called from directly within the Itinerary's designated initializer. That means that I also need to pass a Course to the Itinerary's designated init. Therein lies the problem...when I try to reinitialize an itinerary from within the decodable initializer I don't have access to the course that the itinerary needs to be added to and so can't set the course property during initialization.
The issue seems to revolve around how I am attempting to initialize itineraries along with how I am attempting to manage the memory. A little research has pointed me in the direction of changing the course property either to an optional weak var, i.e. weak var course: Course? or marking it as an implicitly unwrapped optional, i.e. var course: Course!. Both of those options seem as though they may be introducing more complexity into the model than is necessary because of how I'll need to rewrite the Itinerary initializers. According to Apple's docs, it's appropriate to mark a property as unowned when the other instance has the same lifetime or a longer lifetime, which accurately describes the scenario here.
My question is sort of two-fold; there seems to be an inherent flaw with how I am attempting to initialize my model:
If not, how would I go about reassembling my model when decoding? Do I need to reconsider my initialization logic?
If so, what is the flaw? Am I using the right ARC strategy to manage the memory? Should I be considering a data structure other than JSON?
In the interest of brevity I excluded all the properties from the classes that seemed irrelevant. But I'll include more code if people feel it would be necessary to offer a complete solution.
Cliff notes of the important points:
the desired JSON is produced w/ out encoding the course property of the Itinerary class.
course's addItinerary(_:) func is when the itinerary's course property is set...it's called within Itinerary's designated init.
BUT...I can't call that init while decoding because I never encoded a course...when I try to encode a course in the Itinerary's encode(to:), Xcode throws BAD ACCESS errors and points to an address in memory that I am unsure of how to debug.
class Document: NSDocument {
var masterCourseList = [Course]()
}
class Course: NSObject {
// ...other properties
private(set) var itineraries: [Itinerary]() {
didSet {
// sorting code
}
}
func addItinerary(_ itinerary: Itinerary) {
itinerary.course = self
self.itineraries.append(itinerary)
}
// encodable conformance
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
// ...encode other properties
try container.encode(itineraries, forKey: .itineraries)
}
// decodable conformance
required convenience init decode(from: Decoder) throws {
let decoder = try decoder.container(keyedBy: CodingKeys.self)
// ...decode other properties
let decodedItineraries = container.decode(Array<Itinerary>.self, forKey: .itineraries)
self.init()
for itinerary in decodedItineraries {
self.addItinerary(itinerary)
}
}
enum CodingKeys: CodingKey {
case itineraries, // other keys... //
}
}
class Itinerary: NSObject {
#objc dynamic let date: Date
unowned var course: Course
// ...other properties
private init(date: Date, course: Course) {
self.course = course
self.date = date
super.init()
course.addItinerary(self)
}
// encodable conformance
func encode(to encoder: Encoder) throws {
// ...encode all the desired properties of itinerary class
// ...never encoded the 'course' property but still got the desired JSON file
}
// decodable conformance
required convenience init(from decoder: Decoder) throws {
// ...decode all the properties
// ...never decoded the 'course' property because it was never encoded
self.init(date: decodedDate, course: decodedCourse)
}
}
And a sample of the JSON that is being produced...
[
{
"itineraries" : [
{
"date" : 587357150.51135898
},
{
"date" : 587443563.588081
}
],
"title" : "sample course 1",
"startDate" : 587357146.98558402,
"endDate" : 587789146.98558402
},
{
"itineraries" : [
{
"date" : 587357150.51135898
},
{
"date" : 587443563.588081
},
{
"date" : 587443563.588081
}
],
"title" : "sample course 2",
"startDate" : 587957146.98558402,
"endDate" : 588389146.98558402
}
]

escaped json string on Swift

In an iPhone app using SWIFT, I'm having to deal with a third-party API that sends a escaped string instead of a JSON object as response.
The response looks like this:
"[
{
\"ID\":3880,
\"Name\":\"Exploration And Production Inc.\",
\"ContractNumber\":\"123-123\",
\"Location\":\"Booker #1\",
\"Volume\":1225.75,
\"OtherFees\":10.0
}
]"
Up until now I have been dealing with this by manipulating the string to remove the unwanted characters until I get a JSON-like string and then parsing that as usual.
Angular has a handy function to deal with this:
angular.fromJson(response.data);
Java has its own way to deal with it. Is an equivalent function in Swift?
If you are parsing it to a dictionary then the simplest solution will be to convert String to Data and use JSONSerialization:
if let data = string.data(using: .utf8) {
do {
let responseArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]]
print(responseArray)
}
catch {
print(error)
}
}
But ofcourse it would be better to process it as a Codable model in which case its again just as simple as:
try JSONDecoder().decode([Item].self, from: data)
Provided that you have a valid Decodable model like so:
struct Item: Decodable {
let id: Int
let name: String
//add the other keys you want to use (note its type sensitive)
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
}
}
Lastly, avoid stringified jsons because its an easy source of errors.
Malformed strings or small/large deviations in the structure can go easily unnoticed.
Let the backend team know that they should follow a protocol in their API that its consumers can rely on.
By setting the json format, its essentially like a contract that showcases its content and purpose with clarity.
Sending a stringified json is simply lazy and reflects poorly on its designer, imho.