How to use JSON in Core Data
Opened this issue · 7 comments
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:
- Add
NSCoding
conformance toJSON
allowing the attribute's type to beTransformable
.- This does not seem technically possible as mentioned above.
- Use a custom ValueTransformer.
- This is not possible because
JSON
does not derive fromNSObject
.
- This is not possible because
- Add a
toDictionary()
andfromDictionary
methods facilitating[String: Any]
transformations.- The attribute type will be
Transformable
with a custom setter and getter in theNSManagedObject
's subclass to do the transformation.
- The attribute type will be
- 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.
To be honest, I have never used Core Data. This feels like one of the Swift ↔︎ Objective-C interoperability pain points. Would it be acceptable for you to introduce a second, serializable version of the problematic property?
class Person: NSManagedObject {
var metaData: JSON?
@NSManaged private var encodedMetaData: Data?
}
And then you could keep those two in sync using didSet
or custom accessors. (You can’t use the @NSManaged
annotation in that case AFAIK: @NSManaged not allowed on observing properties
, but that seems to be solvable.) I don’t know how acceptable the resulting performance is for you.
just to say I'm interested in this issue as well. My current hack is to store my JSON as a String in core data. Then do a conversion like this back to generic JSON:
extension String {
func toGenericJSON() -> JSON? {
guard let data = self.data(using: .utf8, allowLossyConversion: false) else { return nil }
return try? JSONDecoder().decode(JSON.self, from: data)
}
}
Personally I’d use an ObjC_JSON
class that would inherit from NSObject
and conform to NSCoding
, and would have a single property, the underlying JSON object.
You could then have a computed property in an extension of your model to access the underlying JSON directly (custom getter and setter that would get and set the objcJSON
property of the model).
In my specific case, I have a lot of different types of JSON objects (sometimes of unknown structure). So I do not want to again build a large library of classes to convert json to object class and then back again to json. It is much easier for me to use GenericJSON and keep the objects as JSON. So I was looking for a way to store that in core data. One way is to use Data type, at the moment I just use String as a temporary fix. I was looking for a better more principled way to store JSON in core data.
I stand by my comment… The ObjC_JSON
class I would create would contain a (Generic)JSON
object. The NSCoding
implementation of the class would simply create a JSONEncoder()
and store the encoded JSON
to encode.
If you’re not sure what I mean, I might be able to provide you w/ a code example.
yes please, it would be great if you could provide some code. Note I know nothing about Objective-c.