Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
Merge pull request #27 from Sage-Bionetworks/syoung/polymorphic-wrappers
Browse files Browse the repository at this point in the history
Polymorphic serialization - property wrappers and additional documentation
  • Loading branch information
syoung-smallwisdom authored Mar 16, 2023
2 parents 7f1b8d2 + 1d415bd commit 2f8ebd4
Show file tree
Hide file tree
Showing 15 changed files with 896 additions and 120 deletions.
124 changes: 123 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,133 @@ Moved the results protocols and objects into a separate target within the JsonMo
library. To migrate to this version, you will need to `import ResultModel` anywhere
that you reference `ResultData` model objects.

### Version 2.1

- Added property wrappers that can be used in polymorphic serialization.
- Deprecated `PolymorphicSerializer` and replaced with `GenericPolymorphicSerializer`

Note: Polymorphic encoding using the static `typeName` defined by the `PolymorphicStaticTyped`
protocol is not currently supported for encoding root objects, and is therefore *not*
used by any of the `SerializableResultData` model objects defined within this library.

A root object can be encoded and decoded using the `PolymorphicValue` as a wrapper or
by defining the `typeName` as a read/write instance property.

For example,

```
public protocol GooProtocol {
var value: Int { get }
}
public struct FooObject : Codable, PolymorphicStaticTyped, GooProtocol {
public static var typeName: String { "foo" }
public let value: Int
public init(value: Int = 0) {
self.value = value
}
}
public struct MooObject : Codable, PolymorphicTyped, GooProtocol {
private enum CodingKeys : String, CodingKey {
case typeName = "type", goos
}
public private(set) var typeName: String = "moo"
public var value: Int {
goos.count
}
@PolymorphicArray public var goos: [GooProtocol]
public init(goos: [GooProtocol] = []) {
self.goos = goos
}
}
public struct RaguObject : Codable, PolymorphicStaticTyped, GooProtocol {
public static let typeName: String = "ragu"
public let value: Int
@PolymorphicValue public private(set) var goo: GooProtocol
public init(value: Int, goo: GooProtocol) {
self.value = value
self.goo = goo
}
}
open class GooFactory : SerializationFactory {
public let gooSerializer = GenericPolymorphicSerializer<GooProtocol>([
MooObject(),
FooObject(),
])
public required init() {
super.init()
self.registerSerializer(gooSerializer)
gooSerializer.add(typeOf: RaguObject.self)
}
}
```

In this example, `MooObject` can be directly serialized because the `typeName` is a read/write
instance property. Decoding can be handled like this:

```
let factory = GooFactory()
let decoder = factory.createJSONDecoder()
let json = """
{
"type" : "moo",
"goos" : [
{ "type" : "foo", "value" : 2 },
{ "type" : "moo", "goos" : [{ "type" : "foo", "value" : 5 }] }
]
}
""".data(using: .utf8)!
let decodedObject = try decoder.decode(MooObject.self, from: json)
```

And because the root object does *not* use a static `typeName`, can be encoded as follows:

```
let encoder = JSONEncoder()
let encodedData = try encoder.encode(decodedObject)
```

Whereas `RaguObject` must be wrapped:

```
let factory = GooFactory()
let decoder = factory.createJSONDecoder()
let encoder = factory.createJSONEncoder()
let json = """
{
"type" : "ragu",
"value" : 7,
"goo" : { "type" : "foo", "value" : 2 }
}
""".data(using: .utf8)!
let decodedObject = try decoder.decode(PolymorphicValue<GooProtocol>.self, from: json)
let encodedData = try encoder.encode(decodedObject)
```

## License

JsonModel is available under the BSD license:

Copyright (c) 2017-2022, Sage Bionetworks
Copyright (c) 2017-2023, Sage Bionetworks
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
2 changes: 2 additions & 0 deletions Sources/JsonModel/IdentifiableInterfaceSerializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import Foundation

/// Convenience implementation for a serializer that includes a required `identifier` key.
@available(*, deprecated, message: "Use `GenericPolymorphicSerializer` instead.")
open class IdentifiableInterfaceSerializer : AbstractPolymorphicSerializer {
private enum InterfaceKeys : String, OpenOrderedCodingKey {
case identifier
Expand All @@ -30,3 +31,4 @@ open class IdentifiableInterfaceSerializer : AbstractPolymorphicSerializer {
}
}
}

32 changes: 13 additions & 19 deletions Sources/JsonModel/OrderedJSONEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,21 @@ public protocol OpenOrderedCodingKey : OrderedCodingKey {
/// `OrderedCodingKey` protocol.
open class OrderedJSONEncoder : JSONEncoder {

public override init() {
self._keyEncodingStrategy = .custom({ codingPath in
return IndexedCodingKey(key: codingPath.last!) ?? codingPath.last!
})
super.init()
}

/// Should the encoded data be sorted to order the keys for coding keys that implement the `OrderedCodingKey` protocol?
/// By default, keys are *not* ordered so that encoding will run faster, but they can be if the protocol supports doing so.
public var shouldOrderKeys: Bool = false

override open var keyEncodingStrategy: JSONEncoder.KeyEncodingStrategy {
get {
shouldOrderKeys ? _keyEncodingStrategy : super.keyEncodingStrategy
}
set {
super.keyEncodingStrategy = newValue
shouldOrderKeys = false
/// Should the encoded data be sorted to order the keys for coding keys that implement the
/// `OrderedCodingKey` protocol? By default, keys are *not* ordered so that encoding will
/// run faster, but they can be if the protocol supports doing so.
public var shouldOrderKeys: Bool = false {
didSet {
if shouldOrderKeys {
self.keyEncodingStrategy = .custom({ codingPath in
return IndexedCodingKey(key: codingPath.last!) ?? codingPath.last!
})
}
else {
self.keyEncodingStrategy = .useDefaultKeys
}
}
}
private var _keyEncodingStrategy: JSONEncoder.KeyEncodingStrategy

override open var outputFormatting: JSONEncoder.OutputFormatting {
get {
Expand Down
Loading

0 comments on commit 2f8ebd4

Please sign in to comment.