@@ -67,7 +67,9 @@ public final class LoggerMiddleware<M: Middleware>: Middleware where M.StateType
6767 self . queue. async {
6868 let actionMessage = self . actionTransform. transform ( action: action, source: dispatcher)
6969 self . actionPrinter. log ( action: actionMessage)
70- self . stateDiffPrinter. log ( state: self . stateDiffTransform. transform ( oldState: stateBefore, newState: stateAfter) )
70+ if let diffString = self . stateDiffTransform. transform ( oldState: stateBefore, newState: stateAfter) {
71+ self . stateDiffPrinter. log ( state: diffString)
72+ }
7173 }
7274 }
7375 }
@@ -123,9 +125,10 @@ extension LoggerMiddleware {
123125 public enum StateDiffTransform {
124126 case diff( linesOfContext: Int = 2 , prefixLines: String = " 🏛 " )
125127 case newStateOnly
126- case custom( ( StateType ? , StateType ) -> String )
128+ case recursive( prefixLines: String = " 🏛 " , stateName: String )
129+ case custom( ( StateType ? , StateType ) -> String ? )
127130
128- func transform( oldState: StateType ? , newState: StateType ) -> String {
131+ func transform( oldState: StateType ? , newState: StateType ) -> String ? {
129132 switch self {
130133 case let . diff( linesOfContext, prefixLines) :
131134 let stateBefore = dumpToString ( oldState)
@@ -134,11 +137,93 @@ extension LoggerMiddleware {
134137 ?? " \( prefixLines) No state mutation "
135138 case . newStateOnly:
136139 return dumpToString ( newState)
140+ case let . recursive( prefixLines, stateName) :
141+ return recursiveDiff ( prefixLines: prefixLines, stateName: stateName, before: oldState, after: newState)
137142 case let . custom( closure) :
138143 return closure ( oldState, newState)
139144 }
140145 }
141146 }
147+
148+ public static func recursiveDiff( prefixLines: String , stateName: String , before: StateType ? , after: StateType ) -> String ? {
149+ // cuts the redundant newline character from the output
150+ diff ( prefix: prefixLines, name: stateName, lhs: before, rhs: after) ? . trimmingCharacters ( in: . whitespacesAndNewlines)
151+ }
152+
153+ private static func diff< A> ( prefix: String , name: String , level: Int = 0 , lhs: A ? , rhs: A ? ) -> String ? {
154+
155+ guard let rightHandSide = rhs, let leftHandSide = lhs else {
156+ if let rightHandSide = rhs {
157+ return " \( prefix) . \( name) : nil → \( rightHandSide) "
158+ }
159+
160+ if let leftHandSide = lhs {
161+ return " \( prefix) . \( name) : \( leftHandSide) → nil "
162+ }
163+
164+ // nil == lhs == rhs
165+ return nil
166+ }
167+
168+ // special handling for Dictionaries: stringify and order the keys before comparing
169+ if let left = leftHandSide as? Dictionary < AnyHashable , Any > , let right = rightHandSide as? Dictionary < AnyHashable , Any > {
170+
171+ let leftSorted = left. sorted { a, b in " \( a. key) " < " \( b. key) " }
172+ let rightSorted = right. sorted { a, b in " \( a. key) " < " \( b. key) " }
173+
174+ let leftPrintable = leftSorted. map { key, value in " \( key) : \( value) " } . joined ( separator: " , " )
175+ let rightPrintable = rightSorted. map { key, value in " \( key) : \( value) " } . joined ( separator: " , " )
176+
177+ // .difference(from:) gives unpleasant results
178+ if leftPrintable == rightPrintable {
179+ return nil
180+ }
181+
182+ return " \( prefix) . \( name) : 📦 [ \( leftPrintable) ] → [ \( rightPrintable) ] "
183+ }
184+
185+ // special handling for sets as well: order the contents, compare as strings
186+ if let left = leftHandSide as? Set < AnyHashable > , let right = rightHandSide as? Set < AnyHashable > {
187+ let leftSorted = left. map { " \( $0) " } . sorted { a, b in a < b }
188+ let rightSorted = right. map { " \( $0) " } . sorted { a, b in a < b }
189+
190+ let leftPrintable = leftSorted. joined ( separator: " , " )
191+ let rightPrintable = rightSorted. joined ( separator: " , " )
192+
193+ // .difference(from:) gives unpleasant results
194+ if leftPrintable == rightPrintable {
195+ return nil
196+ }
197+ return " \( prefix) . \( name) : 📦 < \( leftPrintable) > → < \( rightPrintable) > "
198+ }
199+
200+ let leftMirror = Mirror ( reflecting: leftHandSide)
201+ let rightMirror = Mirror ( reflecting: rightHandSide)
202+
203+ // if there are no children, compare leftHandSide and rightHandSide directly
204+ if 0 == leftMirror. children. count {
205+ if " \( leftHandSide) " == " \( rightHandSide) " {
206+ return nil
207+ } else {
208+ return " \( prefix) . \( name) : \( leftHandSide) → \( rightHandSide) "
209+ }
210+ }
211+
212+ // there are children -> diff the object graph recursively
213+ let strings : [ String ] = leftMirror. children. map ( { leftChild in
214+ let toDotOrNotToDot = ( level > 0 ) ? " . " : " "
215+ return Self . diff ( prefix: " \( prefix) \( toDotOrNotToDot) \( name) " ,
216+ name: leftChild. label ?? " # " , // label might be missing for items in collections, # represents a collection element
217+ level: level + 1 ,
218+ lhs: leftChild. value,
219+ rhs: rightMirror. children. first ( where: { $0. label == leftChild. label } ) ? . value)
220+ } ) . compactMap { $0 }
221+
222+ if strings. count > 0 {
223+ return strings. joined ( separator: " \n " )
224+ }
225+ return nil
226+ }
142227}
143228
144229// MARK: - Action
0 commit comments