diff --git a/Sources/Automerge/Codable/Encoding/AutomergeEncoderImpl.swift b/Sources/Automerge/Codable/Encoding/AutomergeEncoderImpl.swift index 0aecf087..41c0f45e 100644 --- a/Sources/Automerge/Codable/Encoding/AutomergeEncoderImpl.swift +++ b/Sources/Automerge/Codable/Encoding/AutomergeEncoderImpl.swift @@ -65,16 +65,20 @@ final class AutomergeEncoderImpl { } } case .Index: - precondition(highestUnkeyedIndexWritten != nil) - guard let highestUnkeyedIndexWritten else { - return + var highestIndexWritten: Int64 = -1 + if let highestUnkeyedIndexWritten { + // If highestUnkeyedIndexWritten is nil, then a list/array was encoded + // with no items within it. + highestIndexWritten = Int64(highestUnkeyedIndexWritten) } - // Remove index elements that exist in this objectId beyond - // the max written during encode. (allow arrays to 'shrink') - var highestAutomergeIndex = document.length(obj: objectIdForContainer) - 1 - while highestAutomergeIndex > highestUnkeyedIndexWritten { + let lengthOfAutomergeContainer = document.length(obj: objectIdForContainer) + if lengthOfAutomergeContainer > 0 { + var highestAutomergeIndex = Int64(lengthOfAutomergeContainer - 1) + // Remove index elements that exist in this objectId beyond + // the max written during encode. (allow arrays to 'shrink') + while highestAutomergeIndex > highestIndexWritten { do { - try document.delete(obj: objectIdForContainer, index: highestAutomergeIndex) + try document.delete(obj: objectIdForContainer, index: UInt64(highestAutomergeIndex)) highestAutomergeIndex -= 1 } catch { fatalError( @@ -82,6 +86,7 @@ final class AutomergeEncoderImpl { ) } } + } case .Value: return } diff --git a/Tests/AutomergeTests/CodableTests/AutomergeArrayEncodeDecodeTests.swift b/Tests/AutomergeTests/CodableTests/AutomergeArrayEncodeDecodeTests.swift index 83bb8625..9c2f536c 100644 --- a/Tests/AutomergeTests/CodableTests/AutomergeArrayEncodeDecodeTests.swift +++ b/Tests/AutomergeTests/CodableTests/AutomergeArrayEncodeDecodeTests.swift @@ -35,4 +35,32 @@ final class AutomergeArrayEncodeDecodeTests: XCTestCase { // ("StructWithArray(names: ["one", "one", "two"])") is not equal to // ("StructWithArray(names: ["one"])") } + + func testEmptyArrayEncode() throws { + // illustrates https://github.com/automerge/automerge-swift/issues/54 + + struct StructWithArray: Codable, Equatable { + var names: [String] = [] + } + + let encoder = AutomergeEncoder(doc: doc) + let decoder = AutomergeDecoder(doc: doc) + var sample = StructWithArray() + try encoder.encode(sample) + + sample.names.append("one") + sample.names.append("two") + try encoder.encode(sample) + let replica = try decoder.decode(StructWithArray.self) + XCTAssertEqual(replica, sample) + + _ = sample.names.popLast() + _ = sample.names.popLast() + try encoder.encode(sample) + let secondReplica = try decoder.decode(StructWithArray.self) + XCTAssertEqual(secondReplica, sample) + // XCTAssertEqual failed: + // ("StructWithArray(names: ["one", "one", "two"])") is not equal to + // ("StructWithArray(names: ["one"])") + } }