Custom types with ExplorerValue

This commit is contained in:
Alexis Bridoux 2021-06-30 00:09:22 +02:00
parent 723bb2d229
commit 2eecb0807e
9 changed files with 92 additions and 78 deletions

View File

@ -11,6 +11,7 @@ import Foundation
/// - note: Default implementation provided for types conforming to `Encodable`
public protocol ExplorerValueRepresentable {
/// Convert `self` to an ``ExplorerValue``
func explorerValue() throws -> ExplorerValue
}
@ -18,10 +19,11 @@ public protocol ExplorerValueRepresentable {
/// - note: Default implementation provided for types conforming to `Decodable`
public protocol ExplorerValueCreatable {
/// Instantiate a new value from an ``ExplorerValue``
init(from explorerValue: ExplorerValue) throws
}
/// Can be represented as and instantiated from as `ExplorerValue`
/// Can be represented *as* and instantiated *from* an `ExplorerValue`
/// - note: Default implementation provided for types conforming to `Codable`
public typealias ExplorerValueConvertible = ExplorerValueRepresentable & ExplorerValueCreatable

View File

@ -125,20 +125,20 @@ public extension PathExplorer {
/// Add a value at the given path.
///
/// ### Appending
/// To add a key at the end of an array, specify the `PathElement.count`
/// To add a key at the end of an array, specify ``PathElement/count``
mutating func add(_ value: ExplorerValue, at path: PathElement...) throws { try add(value, at: Path(path)) }
/// Add a value at the given path.
///
/// ### Appending
/// To add a key at the end of an array, specify the `PathElement.count`
/// To add a key at the end of an array, specify ``PathElement/count``
/// - Throws: If the `newValue.explorerValue` function fails
mutating func add(_ value: ExplorerValueRepresentable, at path: Path) throws { try add(value.explorerValue(), at: path) }
/// Add a value at the given path.
///
/// ### Appending
/// To add a key at the end of an array, specify the `PathElement.count`
/// To add a key at the end of an array, specify ``PathElement/count``
/// - Throws: If the `newValue.explorerValue()` function fails
mutating func add(_ value: ExplorerValueRepresentable, at path: PathElement...) throws { try add(value.explorerValue(), at: Path(path)) }
@ -147,23 +147,20 @@ public extension PathExplorer {
/// Add a value at the given path, and return a new modified `PathExplorer`
///
/// ### Appending
/// To add a key at the end of an array, specify the `PathElement.count`
///
/// ### Non-existing key
/// Any non existing key encountered in the path will be created.
/// To add a key at the end of an array, specify ``PathElement/count``
func adding(_ value: ExplorerValue, at path: PathElement...) throws -> Self { try adding(value, at: Path(path)) }
/// Add a value at the given path, and return a new modified `PathExplorer`
///
/// ### Appending
/// To add a key at the end of an array, specify the `PathElement.count`
/// To add a key at the end of an array, specify ``PathElement/count``
/// - Throws: If the `newValue.explorerValue()` function fails
func adding(_ value: ExplorerValueRepresentable, at path: Path) throws -> Self { try adding(value.explorerValue(), at: path) }
/// Add a value at the given path, and return a new modified `PathExplorer`
///
/// ### Appending
/// To add a key at the end of an array, specify the `PathElement.count`
/// To add a key at the end of an array, specify ``PathElement/count``
/// - Throws: If the `newValue.explorerValue()` function fails
func adding(_ value: ExplorerValueRepresentable, at path: PathElement...) throws -> Self { try adding(value.explorerValue(), at: Path(path)) }
}

View File

@ -68,71 +68,34 @@ where
// MARK: - Get
/// Get the key at the given path
///
/// #### Negative index
/// It's possible to specify a negative index to target the last nth element of an array.
/// For example, -1 targets the last element and -3 the last 3rd element.
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
func get(_ path: Path) throws -> Self
// MARK: - Set
/// Set the value of the key at the given path
///
/// #### Negative index
/// It's possible to specify a negative index to target the last nth element of an array.
/// For example, -1 targets the last element and -3 the last 3rd element.
///
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
mutating func set(_ path: Path, to newValue: ExplorerValue) throws
/// Set the value of the key at the given path and returns a new modified `PathExplorer`
///
/// #### Negative index
/// It's possible to specify a negative index to target the last nth element of an array.
/// For example, -1 targets the last element and -3 the last 3rd element.
///
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
func setting(_ path: Path, to newValue: ExplorerValue) throws -> Self
// MARK: - Set key name
/// Set the name of the key at the given path
///
/// #### Negative index
/// It's possible to specify a negative index to target the last nth element of an array.
/// For example, -1 targets the last element and -3 the last 3rd element.
///
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary)
mutating func set(_ path: Path, keyNameTo newKeyName: String) throws
/// Set the name of the key at the given path, and return a new modified `PathExplorer`
///
/// #### Negative index
/// It's possible to specify a negative index to target the last nth element of an array.
/// For example, -1 targets the last element and -3 the last 3rd element.
///
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary)
func setting(_ path: Path, keyNameTo keyName: String) throws -> Self
// MARK: - Delete
/// Delete the key at the given path.
///
/// #### Negative index
/// It's possible to specify a negative index to target the last nth element of an array.
/// For example, -1 targets the last element and -3 the last 3rd element.
///
/// - parameter deleteIfEmpty: When `true`, the dictionary or array holding the value will be deleted too if empty after the key deletion. Default: `false`
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
mutating func delete(_ path: Path, deleteIfEmpty: Bool) throws
/// Delete the key at the given path and return a new modified `PathExplorer`
///
/// #### Negative index
/// It's possible to specify a negative index to target the last nth element of an array.
/// For example, -1 targets the last element and -3 the last 3rd element.
///
/// - parameter deleteIfEmpty: When `true`, the dictionary or array holding the value will be deleted too if empty after the key deletion. Default: `false`
/// - Throws: If the path is invalid (e.g. a key does not exist in a dictionary, or indicating an index on a non-array key)
func deleting(_ path: Path, deleteIfEmpty: Bool) throws -> Self
@ -141,25 +104,14 @@ where
/// Add a value at the given path.
///
/// #### Negative index
/// It's possible to specify a negative index to target the last nth element.
/// For example, -1 targets the last element and -3 the last 3rd element.
///
/// #### Appending
/// To add a key at the end of an array, specify the `PathElement.count`
/// To add a key at the end of an array, specify ``PathElement/count``
mutating func add(_ value: ExplorerValue, at path: Path) throws
/// Add a value at the given path, and return a new modified `PathExplorer`
///
/// #### Negative index
/// It's possible to specify a negative index to target the last nth element.
/// For example, -1 targets the last element and -3 the last 3rd element.
///
/// #### Appending
/// ### Appending
/// To add a key at the end of an array, specify the `PathElement.count`
///
/// ### Non-existing key
/// Any non existing key encountered in the path will be created.
func adding(_ value: ExplorerValue, at path: Path) throws -> Self
// MARK: - Paths listing

View File

@ -0,0 +1,63 @@
# Custom types with ExplorerValue
Learn more about the back bone of the serializable ``PathExplorer``s and understand how you can use it to inject your own types when setting or adding values.
## Meet ExplorerValue
`ExplorerValue` is a type implementing the ``PathExplorer`` protocol. As it implements `Codable` too, it can be used as a `PathExplorer` as long as a coder exists for the data format. Thus, JSON, Plist and YAML `PathExplorer`s can use the `ExplorerValue` type to get simple conformance to the protocol.
That's why ``CodablePathExplorer`` is mainly a wrapper around `ExplorerValue` to provide a generic structure implementing `PathExplorer`. ``PathExplorers/Json``, ``PathExplorers/Plist`` and ``PathExplorers/Yaml`` are simply type aliases for `CodablePathExplorer`, and differs only by the generic type ``CodableFormat``.
As `ExplorerValue` conforms to `Codable`, it's possible to provide a custom `Encoder` or `Decoder` rather than using the default ones coming with the ``PathExplorers`` namespace. This allows to specify date coding strategies for example, or to support new data formats in a blink of an eye with a dedicated Encoder/Decoder.
## ExplorerValueCreatable
To take things further, it's also possible to convert any type to an `ExplorerValue` with ``ExplorerValueRepresentable``. This protocol's only requirement is a function that returns an `ExplorerValue`. This way, it's possible to set or add a value of a custom type with a `PathExplorer`.
It's worth to note that making a type conform to `Encodable` is enough to make it `ExplorerValueRepresentable` too. A value of this type will be *encoded* to an `ExplorerValue`. Thus, using the following structure:
```swift
struct Record: Codable, ExplorerValueConvertible {
var name: String
var score: Int
}
```
It's possible to set a `Record` value with any `PathExplorer`
```swift
let record = Record(name: "Riri", score: 20)
// plist: CodablePathExplorer<PlistFormat>
try plist.set("ducks", "records", 0, to: record)
```
> Note: Even if primitive types conform to `Encodable`, it would be less efficient to encode them. A simpler implementation for `ExplorerValueRepresentable` is provided. The same goes for an array of a primitive type and for a dictionary where `Value` is a primitive type.
## ExplorerValueCreatable
The counterpart of `ExplorerValueRepresentable` is ``ExplorerValueCreatable``. Types conforming to this protocol declare an initialization from an `ExplorerValue`. This allows to export the value of an `ExplorerValue` to the type.
> Tip: Similarly with `ExplorerValueRepresentable`, a default implementation is provided for primitive types and types conforming to `Decodable`.
With the `Record` structure from above,
```swift
struct Record: Codable, ExplorerValueConvertible {
var name: String
var score: Int
}
```
it's possible to try to export a value of a `PathExplorer` as an array of `Record`s with ``PathExplorer/array(of:)``
```swift
// plist: CodablePathExplorer<PlistFormat>
let records = try plist.get("Riri", "records").array(of: Record.self)
```
## ExplorerValueConvertible
``ExplorerValueConvertible`` is simply a type alias for both `ExplorerValueRepresentable` and `ExplorerValueCreatable` protocols.

View File

@ -1,6 +0,0 @@
# Diving into ExplorerValue
Learn more about the back bone of the serializable ``PathExplorer``s and understand how you can use it to inject your own types when setting or adding values.
## Meet ExplorerValue

View File

@ -69,7 +69,7 @@ More concisely, if you are only interested into getting Tom's height, you could
let tomHeight = try yaml.get("Tom", "height").double
```
> Note: As you might have noticed, calling `get()` can throw an error. This is the case for most `PathExplorer` functions. Whenever an element in the provided path does not exist, for instance an index out of bounds, or a missing key, a relevant error will be thrown to let you take the relevant action.
> Note: As you might have noticed, calling `get()` can throw an error. This is the case for most `PathExplorer` functions. Whenever an element in the provided path does not exist, for instance an index out of bounds, or a missing key, a relevant error will be thrown.
As a last example, here's how to read Robert first hobby inside an array:

View File

@ -0,0 +1,9 @@
# Paths listing
`PathExplorer` list path features is useful to get all paths leading to a value or a key.
## Overview
In this article, learn how to use the paths listing feature to precisely get the paths you want.

View File

@ -6,12 +6,6 @@
## Overview
`ExplorerValue` is the back bone of serializable ``PathExplorer`` (JSON, Plist, YAML). It's the type that implements all the logic to conform to `PathExplorer`. Then ``SerializablePathExplorer`` simply interfaces it with the proper data format. Also, it's the type that is used to encode and decode to those formats.
`ExplorerValue` is the back bone of serializable ``PathExplorer`` (JSON, Plist, YAML). It's the type that implements all the logic to conform to `PathExplorer`. Then ``CodablePathExplorer`` simply interfaces it with the proper data format to conform to ``SerializablePathExplorer``. Also, it's the type that is used to encode and decode to those formats.
But it also allows to use your own types to inject them in a `PathExplorer`.
## Topics
### <!--@START_MENU_TOKEN@-->Group<!--@END_MENU_TOKEN@-->
- <!--@START_MENU_TOKEN@-->``Symbol``<!--@END_MENU_TOKEN@-->
But it also allows to use your own types to inject them in a `PathExplorer`. Read more with <doc:custom-types-explorerValue>

View File

@ -14,16 +14,19 @@ Supported formats:
### Essential
- <doc:getting-started>
- ``PathExplorer``
- ``Path``
### Exploring data
### Explore data
- ``PathExplorer``
- ``PathExplorers``
- ``ExplorerError``
### Manipulating paths
### Manipulate paths
- <doc:mastering-paths>
- <doc:listing-path>
- ``Path``
- ``PathElement``
- ``PathElementRepresentable``
@ -43,7 +46,7 @@ Supported formats:
### Set and add custom types
- <doc:explorer-value-diving>
- <doc:custom-types-explorerValue>
- ``ExplorerValue``
- ``ExplorerValueCreatable``
- ``ExplorerValueRepresentable``