Sorry for the messy formatting: tried to correct it, improved it a bit.
This is a struct that I'm trying to encode. It does conform to Codable now, but any inclusion of the rest of the content, always based on ObjectIdentifiers, blows up, so I conclude they present a problem for encoding.
What I'm trying to accomplish is to be able to restore the entity relationships between a number of classes when I decode elsewhere. I don't see a way to get the JSON Encoder to see those relationships, so maybe there's a way to do that.
But in lieu I initialize the struct with the basic data. This is the simplest such struct, with only a name property in the class, but it also grabs the id of the project the Block belongs to, and attempts to initialize that value.
Afterwards, I loop through the different entities, and update the structs that are initialized with the Array values for comps, gitpaths, inProject, and selectionThreads.
So that's what I'm doing and the goal. The stumbling block is encoding the ObjectIdentifiers. What am I missing?
struct ProjectBlock : Codable {
/*
var comps : Array<ObjectIdentifier>?
var gitpaths : Array<ObjectIdentifier>?
var inProject : ObjectIdentifier?
var selectionThreads : Array<ObjectIdentifier>?
*/
var name : String
/*
var exportObjID : ObjectIdentifier
*/
init(block : Blocks) {
name = block.name!
/*
exportObjID = block.id
inProject = block.inProject!.id
*/
}
enum CodingKeys: String, CodingKey {
/*
case comps = "comps"
case gitpaths = "gitpaths"
case inProject = "inProject"
case selectionThreads = "selectionThreads"
case exportObjID = "exportObjID"
*/
case name = "name"
}
}
Related
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
}
]
I am writing a multilingual API with go and mongodb. I have a mongo db document with format:
{
_id : ObjectID(bla)
"key" : {
"en" : "Hello",
"es" : "Hola"
}
}
However, the API needs report json in the form:
{
_id : ObjectID(bla),
"key" : "Hola"
}
if the client sends language headers.
Is there an easy/efficent way to do this? The only working solution I have is to make two separate structs and then merge them together with a bunch of switch/case statements, like:
var api MyStruct
var mgo MyMgoStruct
session.DB("db").C("col").Find(nil).One(&mgo)
api.ID = mgo.ID
switch lang {
default:
{
api.Key = string(mgo.Key.En)
}
case "es":
{
api.Key = string(mgo.Key.Es)
}
}
Structure defs:
type Translation struct {
En string `bson:"en"`
Es string `bson:"es"`
}
type MyStruct struct {
ID bson.ObjectID `json:"_id" bson:"_id"`
Key string `json:"key" bson:"key"`
}
type MyMgoStruct struct {
ID bson.ObjectID `json:"_id" bson:"_id"`
Key Translation `json:"key" bson:"key"`
}
I foresee this becoming a huge pain to maintain, as my structures have tens of translated fields. I would prefer a way to transform the MongoDB document, replacing the Translation json structure with a simple key-value pair as in the MyStruct struct.
The only working solution I have is to make two separate structs and then merge them together with a bunch of switch/case statements
An alternative is you could use MongoDB projection on the Find(). Given your example document format, for example:
// Client input language header as variable
var languageInput = "es"
// Concatenate to get field nest 'key'
key := "key." + languageInput
// Only project the specific fields
cursor := coll.Find(nil).Select(bson.M{key: 1})
See also Project fields to return from query
If you have many translation fields in your struct that you don't want to map, you could utilise bson inline. For example:
type MyStruct struct {
ID bson.ObjectId `json:"id" bson:"_id"`
OtherFields bson.M `bson:",inline"`
}
This would capture unstructured fields in OtherFields. See also bson.Marshal
This will be my first question on StackOverflow and my first question posed on Swift 3; I'm a beginner and perhaps biting off more than I can chew, however...
I am trying to create a class which will assist in creating menu items (the class is called 'MenuItems') to populate into a dynamic table in my IOS application. I have created the bulk of the class which identifies from the data passed to it what the headers are and how many of each type will be separated into sections within the table. I am now at the stage of trying to make the class more generic so it will work for different data structures that I may want to populate into a similar table in the future.
The data that I wish to have in the table comes from a structure that is in its own swift file. It looks like this:
struct EquipmentStruct {
var name : String!
var serialNumber : String?
var alias : String?
var image : UIImage?
}
I have an array of type EquipmentStruct which, for the short term, is initialised in my tableViewController file (it will not stay here in the future) and I hope to create a public function in my MenuItems class which will allow me to add an item to the table as necessary
func addItem(item, dataType) // types for item and dataType are part of my question
In designing this function I discover my questions:
How do I pass a variable of type EquipmentStruct to an instance of my MenuItems class so I can add it to my table - please note, all I am asking is for guidance on how to complete the addItem method and not the rest of the class. In my mind I want to do something like:
var dataArray : [EquipmentStruct] =
[EquipmentStruct(name: "SA80", serialNumber:"01234-56-789", alias: "29", image: #imageLiteral(resourceName: "SA80")),
EquipmentStruct(name: "LSW", serialNumber:"11111-22-333-4444", alias: "98", image: #imageLiteral(resourceName: "LSW"))]
var tableMenuItems = MenuItems() // create instance of class MenuItems
override func viewDidLoad() {
super.viewDidLoad()
for var itemNumber in 0..<dataArray.count{
tableMenuItems.addItem(item: generalHoldingsDataArray[itemNumber], dataType: EquipmentStruct)
}
The addItems method prototype would therefore be something like:
// Add new item of type 'dataType' to MenuItems.tableDataArray
// Store tableDataArrayType for use throughout the class
//
func addItem(item: [Something], dataType: SomeVariableType){
if let newItem = item as! dataType{ // cast the variable received to its type
tableDataArrayType = dataType
tableDataArray.append(newItem)
}
}
Is this a good way of doing things?
Is there a simpler way of doing what I'm trying to do?
If I continue down this path, what issues might I come up against in the future?
Your assistance would be gratefully appreciated.
Kind regards
A table view is supposed to hold only one type of stuff as model. A single table view is not designed to show info about an EquipmentStruct, a SomeOtherStruct and maybe 3 Foos. You have to generalize all these into one single type.
Another possibility is that you want to create a MenuItems type that can create a table view that shows EquipmentStructs, a table view that shows SomeOtherStructs, basically a table view that shows whatever type you like. If this is the case, you should use generics.
Make the MenuItems class like this:
class MenuItems<T : SomeProtocol> {
var tableDataArray: [T] = []
func addItem(_ newItem: T) {
tableDataArray.append(newItem)
}
// ...
// since you just need guidance on creating addItems method, figure the rest yourself please! XD
}
To create a table view that shows EquipmentStructs,
let tableMenuItems = MenuItems<EquipmentStruct>()
To create a table view that shows SomeOtherStructs,
let tableMenuItems = MenuItems<SomeOtherStruct>()
Notice the SomeProtocol in the first line? That defines what properties/methods a type should have, in order to be shown in a table view. A possible SomeProtocol might be:
protocol SomeProtocol {
var displayTitle: String { get }
var displaySubtitle: String? { get }
var displayImage: UIImage? { get }
}
This just ensures that the type you want to display in the table view have properties called displayTitle, displaySubtitle, displayImage. You can use these properties in your MenuItems class to set the table view cells' appearences.
UPDATE: This has now been fixed in Unbox 1.9.
I'm using the awesome Unbox JSON decoder in a small Swift project and so far it has proven very easy to work with. However, I've run into a small problem trying to decode and transform a string array in JSON to an integer array property.
I have a Conversation class that looks like this:
final class Conversation: Unboxable {
var participants: [Int]?
var subject: String?
var timestamp: Int?
var conversationId: String?
init(unboxer: Unboxer) {
self.participants = unboxer.unbox("participants")
self.subject = unboxer.unbox("subject")
self.timestamp = unboxer.unbox("timestamp")
self.conversationId = unboxer.unbox("conversationId")
}
}
The JSON that I'm working with looks like this:
{
"subject" : "subject line goes here",
"participants" : [
"201984",
"237810",
"149092",
"202577"
],
"timestamp" : 1468322845885,
"conversationId" : "9bf356d1-4823-11e6a51e-2d6dfda33d93"
}
As you can see, the participants array in the JSON is a string array and I'd like to transform it into an integer array. When I run my code, the participants property in my class is nil after unboxing. If I change its type to [String]?, then it unboxes ok. One of the nice things about Unbox is that it will automagically transform (where possible) a string value into a numeric type for you. But either I'm doing something wrong or Unbox doesn't provide this same capability for arrays. Has anyone done this using Unbox before?
I'm using Realm in my Swift project and have a rather long JSON file with a couple of nested properties. I'm aware that in order for Realm to use this serialized JSON data directly, the properties need to match exactly (https://realm.io/docs/swift/latest/#json).
But because Realm Lists need to have an Object instead of a String, I have to use something like List with Requirement being a Realm Object that holds a single String called 'value'.
When I run this code:
try! realm.write {
let json = try! NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
let exhibit = Exhibit(value: json)
exhibit.id = "1"
realm.add(exhibit, update: true)
}
I get this error message:
*** Terminating app due to uncaught exception 'RLMException', reason: 'req1' to initialize object of type 'Requirements': missing key 'value''
Here's a shortened version of the JSON I'm feeding in there:
{
"exhibit_name": "test1",
"requirements": [
"req1",
"req2"
],
"geofence": {
"latitude": 36.40599779999999,
"longitude": -105.57696279999999,
"radius": 500
}
}
And my Realm model classes are this:
class Exhibit: Object {
override static func primaryKey() -> String? {
return "id"
}
dynamic var id = "0" //primary key
dynamic var exhibit_name: String = ""
let requirements = List<Requirements>()
dynamic var geofence: Geofence?
}
class Geofence: Object {
dynamic var latitude: Float = 0.0
dynamic var longitude: Float = 0.0
dynamic var radius: Float = 0.0
}
class Requirements: Object {
dynamic var value = ""
}
I find it interesting that I'm not getting any errors for the Geofence property, since that's a dictionary.
How do I set up the Requirements model to make this work properly?
Unfortunately you can't just setup your Requirements model in a different way, which would allow you to directly map your JSON to Realm objects.
The init(value: AnyObject) initializer expects either a dictionary, where the keys are the names of your object properties, or an array, where the property values are ordered in the same like they are defined in your object model. This initializer is recursively called for related objects.
So to make that work, you will need to transform your JSON, so that you nest the string values into either dictionaries or arrays. In your specific case you could achieve that like seen below:
…
var jsonDict = json as! [String : AnyObject]
jsonDict["requirements"] = jsonDict["requirements"].map { ["value": $0] }
let exhibit = Exhibit(value: jsonDict)
…
Side Note
I'd recommend using singular names for your Realm model object classes (here Requirement instead Requirements) as each object just represent a single entity, even if you use them only in to-many relationships.