Skip to content

Commit

Permalink
Merge pull request #16 from iWECon/chainchangevalue
Browse files Browse the repository at this point in the history
chainchangevalue
  • Loading branch information
iWECon authored Jul 16, 2024
2 parents 78dec7c + 0e6f2a3 commit 9c64408
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 46 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,31 @@ try await req.client.post(uri, headers: headers) { inoutReq in
}
```

*setNull & Compact Map Values*
```swift
let json = """
{
"name": "Lookup",
"age": 3,
"versions": [
"2.4.0", "2.0.0", "1.0.0"
],
"birth": "202"
}
let lookup = Lookup(json)
print(lookup.versions.0.string) // -> "2.4.0"
lookup["versions.0"] = "2.4.1"
print(lookup.versions.0.string) // -> "2.4.1"
// setNull
let newLookup = lookup.setNull(keys: ["birth"])
newLookup.hasKey("birth") // -> true, but value is null(nil)
let compactLookup = newLookup.compactMapValues()
newLookup.hasKey("birth") // -> false, no key no value
"""
```

More usage references `LookupTests.swift`: [LookupTests.swift](https://github.com/iWECon/Lookup/blob/main/Tests/LookupTests/LookupTests.swift)


Expand Down
183 changes: 141 additions & 42 deletions Sources/Lookup/Lookup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fileprivate func unwrap(_ object: Any?) -> Any {

// MARK: - Lookup
@dynamicMemberLookup
public struct Lookup: Swift.CustomStringConvertible, Swift.CustomDebugStringConvertible, @unchecked Sendable {
public struct Lookup: @unchecked Sendable {

public enum RawType {
case none
Expand Down Expand Up @@ -76,11 +76,11 @@ public struct Lookup: Swift.CustomStringConvertible, Swift.CustomDebugStringConv
}
}

fileprivate let rawType: RawType
fileprivate private(set) var rawDict: [String: Any] = [:]
fileprivate private(set) var rawArray: [Any] = []
fileprivate private(set) var rawString: String = ""
fileprivate private(set) var rawNumber: NSNumber = 0
var rawType: RawType
var rawDict: [String: Any] = [:]
var rawArray: [Any] = []
var rawString: String = ""
var rawNumber: NSNumber = 0

private init(jsonObject: Any) {
switch jsonObject {
Expand Down Expand Up @@ -201,18 +201,61 @@ public struct Lookup: Swift.CustomStringConvertible, Swift.CustomDebugStringConv
}
}

private mutating func setNewValue(for dynamicMember: String, value: Lookup) {
/**
let json = {
"name": "Lookup",
"age": 18,
"versions: [
"1.0.0", "1.1.0", "1.2.0", "1.3.0"
]
}
let lookup = Lookup(json)
// > print(lookup.versions.0) -> output: 1.0.0
lookup["versions.0"] = "0.0.1"
// > print(lookup.versions.0) -> output: 0.0.1
// > print(lookup.versions.1) -> output: 1.0.0
*/
private mutating func setNewValue(for dynamicMember: String, value: Lookup, inner: Bool = false) {
if dynamicMember.contains(".") {
var keys = dynamicMember.components(separatedBy: ".")
let finalKey = keys.removeLast()
let newKeys = keys.joined(separator: ".")
var _value = self[dynamicMember: newKeys]

switch _value.rawType {
case .none, .number, .string:
_value = value
case .dict, .object:
_value.rawDict[finalKey] = value.rawValue
case .array:
if inner {
_value.rawArray = [value.rawValue]
} else {
if finalKey.isPurnInt, let index = Int(finalKey) {
_value.rawArray.insert(value.rawValue, at: index)
} else {
_value.rawArray = [value.rawValue]
}
}
}

setNewValue(for: newKeys, value: _value, inner: true)
return
}

switch rawType {
case .none:
break
case .none, .number, .string:
self = value
case .dict, .object:
rawDict[dynamicMember] = value.rawValue
case .array:
rawArray = value.rawArray
case .number:
rawNumber = value.rawNumber
case .string:
rawString = value.rawString
if dynamicMember.isPurnInt, let index = Int(dynamicMember) {
rawArray.insert(value.rawValue, at: index)
} else {
rawArray = value.rawArray
}
}
}

Expand Down Expand Up @@ -251,34 +294,6 @@ public struct Lookup: Swift.CustomStringConvertible, Swift.CustomDebugStringConv
}
}

private func castValueToString(value: Any) -> String {
if let data: Data = try? JSONSerialization.data(withJSONObject: value, options: .prettyPrinted),
let str = String(data: data, encoding: .utf8)
{
return str
}
return "Can not cast value to string"
}

public var description: String {
let desc: String
switch rawType {
case .dict, .object:
desc = castValueToString(value: rawDict)
case .array:
desc = castValueToString(value: rawArray)
case .number:
desc = "\(rawNumber)"
case .string:
desc = "\(rawValue)"
case .none:
desc = "nil"
}
return desc
}

public var debugDescription: String { description }

fileprivate static var null: Lookup { Lookup(NSNull()) }

fileprivate mutating func merge(other: Lookup) {
Expand Down Expand Up @@ -826,3 +841,87 @@ extension Lookup: Codable {
}
}
}

// MARK: Description
extension Lookup: Swift.CustomStringConvertible, Swift.CustomDebugStringConvertible {

private func castValueToString(value: Any) -> String {
if #available(macOS 10.15, *) {
if let data: Data = try? JSONSerialization.data(withJSONObject: value, options: [.sortedKeys, .prettyPrinted, .fragmentsAllowed, .withoutEscapingSlashes]),
let str = String(data: data, encoding: .utf8)
{
return str
}
} else {
// Fallback on earlier versions
if let data: Data = try? JSONSerialization.data(withJSONObject: value, options: [.sortedKeys, .prettyPrinted, .fragmentsAllowed]),
let str = String(data: data, encoding: .utf8)
{
return str
}
}
return "Can not cast value to string"
}

public var description: String {
let desc: String
switch rawType {
case .dict, .object:
desc = castValueToString(value: rawDict)
case .array:
desc = castValueToString(value: rawArray)
case .number:
desc = "\(rawNumber)"
case .string:
desc = "\(rawValue)"
case .none:
desc = "nil"
}
return desc
}

public var debugDescription: String { description }

}

// MARK: - Filter
extension Lookup {

public func keep(keys: [String]) -> Lookup {
let res = keys.map { key -> (String, Any?) in
(key, self[key].rawValue)
}
return Lookup(Dictionary(uniqueKeysWithValues: res))
}

public func setNull(keys: [String]) -> Lookup {
var new = self
for key in keys {
new[dynamicMember: key] = nil
}
return new
}

public func compactMapValues(keepEmptyValue: Bool = false) -> Lookup {
switch rawType {
case .none, .number:
return self
case .string:
return self
case .array:
let map = rawArray.map { value in
Lookup(value).compactMapValues()
}
return Lookup(map)
case .object, .dict:
let map = rawDict.compactMap { (k: String, v: Any) -> (String, Any)? in
let vl = Lookup(v)
if vl.isNone || (keepEmptyValue && vl.isEmpty) {
return nil
}
return (k, vl.compactMapValues())
}
return Lookup(Dictionary(uniqueKeysWithValues: map))
}
}
}
74 changes: 70 additions & 4 deletions Tests/LookupTests/LookupTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,10 @@ final class LookupTests: XCTestCase {

lookup["address"] = "in Hangzhou"
XCTAssertEqual(lookup.address.string, "in Hangzhou")

// TODO: dynamicMember change

lookup["list.0"] = "d"
XCTAssertNotEqual(lookup.list.0.string, "d")

XCTAssertEqual(lookup.list.0.string, "d")
let jsonData = try JSONEncoder().encode(lookup)
let _jsonString = String(data: jsonData, encoding: .utf8)
XCTAssertNotNil(_jsonString)
Expand Down Expand Up @@ -325,6 +324,73 @@ final class LookupTests: XCTestCase {
}
try changeValue()

func testSelect() throws {

struct User {
let id: Int
let name: String
let age: Int
}
let user = User(id: 1, name: "wei", age: 18)
let lookup = Lookup(user)
let keepLookup = lookup.keep(keys: ["name"])

XCTAssertEqual(keepLookup.id.isNone, true)
XCTAssertEqual(keepLookup.name.string, "wei")
/**
{
id: xxx,
cars: [Car]
}
let lookup = Lookup(cars)
let carsLookup = Lookup(cars)
carsLookup.0.name = .null
carsLookup.compactMapValues()
lookup.cars = carsLookup
*/
let rejectLookup = lookup.setNull(keys: ["id"])
.compactMapValues()
XCTAssertEqual(rejectLookup.id.isNone, true)
XCTAssertEqual(rejectLookup.name.string, "wei")
XCTAssertEqual(rejectLookup.age.int, 18)

var aLookup = Lookup(
["name": "iwecon", "childs":[
[
"name": "lookup", "id": nil, "age": 18,
"childs": [
["name": "Lookup.dynamicMember", "age": 12, "id": nil]
]
]
]]
)
.compactMapValues()
XCTAssertEqual(aLookup.hasKey("childs.0.id"), false)
XCTAssertEqual(aLookup.hasKey("childs.0.age"), true)
XCTAssertEqual(aLookup.hasKey("childs.0.childs.0.id"), false)
XCTAssertEqual(aLookup.childs.0.age.int, 18)
XCTAssertEqual(aLookup.childs.0.childs.0.name.string, "Lookup.dynamicMember")

aLookup["childs.0.id"] = "1"
XCTAssertEqual(aLookup.childs.0.id.string, "1")

let anlookup = aLookup.setNull(keys: ["childs.0.age"])
XCTAssertEqual(anlookup.hasKey("childs.0.age"), true)
var canlookup = anlookup.compactMapValues()
XCTAssertEqual(canlookup.hasKey("childs.0.age"), false)

canlookup["childs.0.childs.0.id"] = 1
canlookup["childs.0.childs.0.age"] = 18
XCTAssertEqual(canlookup.childs.0.childs.0.id.int, 1)
XCTAssertEqual(canlookup.childs.0.childs.0.age.int, 18)

canlookup["childs.0.childs.0.birth"] = "2024"
XCTAssertEqual(canlookup.childs.0.childs.0.birth.string, "2024")
}
try testSelect()

#if os(iOS)
func uiView() throws {
let view = UIView()
Expand Down

0 comments on commit 9c64408

Please sign in to comment.