diff --git a/Sources/XXClient/Helpers/JSONDecoder.swift b/Sources/XXClient/Helpers/JSONDecoder.swift
new file mode 100644
index 0000000000000000000000000000000000000000..751a8b3997f956d4ea535947a4c418a2dc48d6a6
--- /dev/null
+++ b/Sources/XXClient/Helpers/JSONDecoder.swift
@@ -0,0 +1,64 @@
+import CustomDump
+import Foundation
+
+class JSONDecoder: Foundation.JSONDecoder {
+  override init() {
+    super.init()
+  }
+
+  override func decode<T>(_ type: T.Type, from data: Data) throws -> T where T: Decodable {
+    do {
+      let data = convertNumberToString(in: data, at: "Value")
+      return try super.decode(type, from: data)
+    } catch {
+      throw JSONDecodingError(error, data: data)
+    }
+  }
+
+  func convertNumberToString(
+    in input: Data,
+    at key: String
+  ) -> Data {
+    guard var string = String(data: input, encoding: .utf8) else {
+      return input
+    }
+    string = string.replacingOccurrences(
+      of: #""\#(key)"( *):( *)([0-9]+)( *)(,*)"#,
+      with: #""\#(key)"$1:$2"$3"$4$5"#,
+      options: [.regularExpression]
+    )
+    guard let output = string.data(using: .utf8) else {
+      return input
+    }
+    return output
+  }
+}
+
+public struct JSONDecodingError: Error, CustomStringConvertible, CustomDumpReflectable {
+  public init(_ underlayingError: Error, data: Data) {
+    self.underlayingError = underlayingError
+    self.data = data
+    self.string = String(data: data, encoding: .utf8)
+  }
+
+  public var underlayingError: Error
+  public var data: Data
+  public var string: String?
+
+  public var description: String {
+    var description = ""
+    customDump(self, to: &description)
+    return description
+  }
+
+  public var customDumpMirror: Mirror {
+    Mirror(
+      self,
+      children: [
+        "underlayingError": underlayingError,
+        "data": String(data: data, encoding: .utf8) ?? data
+      ],
+      displayStyle: .struct
+    )
+  }
+}
diff --git a/Sources/XXClient/Helpers/JSONEncoder.swift b/Sources/XXClient/Helpers/JSONEncoder.swift
new file mode 100644
index 0000000000000000000000000000000000000000..69b368594111f33a8b2c207a9104facf628f4143
--- /dev/null
+++ b/Sources/XXClient/Helpers/JSONEncoder.swift
@@ -0,0 +1,52 @@
+import CustomDump
+import Foundation
+
+class JSONEncoder: Foundation.JSONEncoder {
+  override init() {
+    super.init()
+  }
+
+  override func encode<T>(_ value: T) throws -> Data where T: Encodable {
+    do {
+      var data = try super.encode(value)
+      data = convertStringToNumber(in: data, at: "Value")
+      return data
+    } catch {
+      throw JSONEncodingError(error, value: value)
+    }
+  }
+
+  func convertStringToNumber(
+    in input: Data,
+    at key: String
+  ) -> Data {
+    guard var string = String(data: input, encoding: .utf8) else {
+      return input
+    }
+    string = string.replacingOccurrences(
+      of: #""\#(key)"( *):( *)"([0-9]+)"( *)(,*)"#,
+      with: #""\#(key)"$1:$2$3$4$5"#,
+      options: [.regularExpression]
+    )
+    guard let output = string.data(using: .utf8) else {
+      return input
+    }
+    return output
+  }
+}
+
+public struct JSONEncodingError: Error, CustomStringConvertible {
+  public init(_ underlayingError: Error, value: Any) {
+    self.underlayingError = underlayingError
+    self.value = value
+  }
+
+  public var underlayingError: Error
+  public var value: Any
+
+  public var description: String {
+    var description = ""
+    customDump(self, to: &description)
+    return description
+  }
+}
diff --git a/Sources/XXClient/Helpers/JSONHelpers.swift b/Sources/XXClient/Helpers/JSONHelpers.swift
deleted file mode 100644
index 7527ac9ae217c188ff837501fd472b23ff478862..0000000000000000000000000000000000000000
--- a/Sources/XXClient/Helpers/JSONHelpers.swift
+++ /dev/null
@@ -1,85 +0,0 @@
-import Foundation
-
-/// Replaces all numbers at provided key with string equivalents
-///
-/// Example input:
-/// {
-///   "key": 123,
-///   "object": {
-///     "hello": "world",
-///     "key": 321
-///   }
-/// }
-///
-/// Example output:
-/// {
-///   "key": "123",
-///   "object": {
-///     "hello": "world",
-///     "key": "321"
-///   }
-/// }
-///
-/// - Parameters:
-///   - input: JSON data
-///   - key: the key which values should be converted
-/// - Returns: JSON data
-func convertJsonNumberToString(
-  in input: Data,
-  at key: String
-) -> Data {
-  guard var string = String(data: input, encoding: .utf8) else {
-    return input
-  }
-  string = string.replacingOccurrences(
-    of: #""\#(key)"( *):( *)([0-9]+)( *)(,*)"#,
-    with: #""\#(key)"$1:$2"$3"$4$5"#,
-    options: [.regularExpression]
-  )
-  guard let output = string.data(using: .utf8) else {
-    return input
-  }
-  return output
-}
-
-/// Replaces all strings at provided key with number equivalents
-///
-/// Example input:
-/// {
-///   "key": "123",
-///   "object": {
-///     "hello": "world",
-///     "key": "321"
-///   }
-/// }
-///
-/// Example output:
-/// {
-///   "key": 123,
-///   "object": {
-///     "hello": "world",
-///     "key": 321
-///   }
-/// }
-///
-/// - Parameters:
-///   - input: JSON data
-///   - key: the key which values should be converted
-/// - Returns: JSON data
-func convertJsonStringToNumber(
-  in input: Data,
-  at key: String
-) -> Data {
-  guard var string = String(data: input, encoding: .utf8) else {
-    return input
-  }
-  string = string.replacingOccurrences(
-    of: #""\#(key)"( *):( *)"([0-9]+)"( *)(,*)"#,
-    with: #""\#(key)"$1:$2$3$4$5"#,
-    options: [.regularExpression]
-  )
-  guard let output = string.data(using: .utf8) else {
-    return input
-  }
-  return output
-}
diff --git a/Sources/XXClient/Models/DHKey.swift b/Sources/XXClient/Models/DHKey.swift
index 5dc94983016cced7c2d4b2c0dc450c63f07acdcc..9ca08dfcfd46652c2cb9d91b040d77401278a564 100644
--- a/Sources/XXClient/Models/DHKey.swift
+++ b/Sources/XXClient/Models/DHKey.swift
@@ -17,12 +17,10 @@ extension DHKey: Codable {
   }
 
   public static func decode(_ data: Data) throws -> Self {
-    let data = convertJsonNumberToString(in: data, at: "Value")
-    return try JSONDecoder().decode(Self.self, from: data)
+    try JSONDecoder().decode(Self.self, from: data)
   }
 
   public func encode() throws -> Data {
-    let data = try JSONEncoder().encode(self)
-    return convertJsonStringToNumber(in: data, at: "Value")
+    try JSONEncoder().encode(self)
   }
 }
diff --git a/Sources/XXClient/Models/GroupMember.swift b/Sources/XXClient/Models/GroupMember.swift
index cb5d9843bce2a661bdeb93560fbc77860b71d28c..8b8c2b75356c244898c86f2f52dbfba333b2838b 100644
--- a/Sources/XXClient/Models/GroupMember.swift
+++ b/Sources/XXClient/Models/GroupMember.swift
@@ -17,24 +17,20 @@ extension GroupMember: Codable {
   }
 
   public static func decode(_ data: Data) throws -> Self {
-    let data = convertJsonNumberToString(in: data, at: "Value")
-    return try JSONDecoder().decode(Self.self, from: data)
+    try JSONDecoder().decode(Self.self, from: data)
   }
 
   public func encode() throws -> Data {
-    let data = try JSONEncoder().encode(self)
-    return convertJsonStringToNumber(in: data, at: "Value")
+    try JSONEncoder().encode(self)
   }
 }
 
 extension Array where Element == GroupMember {
   public static func decode(_ data: Data) throws -> Self {
-    let data = convertJsonNumberToString(in: data, at: "Value")
-    return try JSONDecoder().decode(Self.self, from: data)
+    try JSONDecoder().decode(Self.self, from: data)
   }
 
   public func encode() throws -> Data {
-    let data = try JSONEncoder().encode(self)
-    return convertJsonStringToNumber(in: data, at: "Value")
+    try JSONEncoder().encode(self)
   }
 }
diff --git a/Tests/XXClientTests/Helpers/JSONDecoderTests.swift b/Tests/XXClientTests/Helpers/JSONDecoderTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..16f2afcbf4e909fa428500289ab58ae73356b8b7
--- /dev/null
+++ b/Tests/XXClientTests/Helpers/JSONDecoderTests.swift
@@ -0,0 +1,84 @@
+import CustomDump
+import XCTest
+@testable import XXClient
+
+final class JSONDecoderTests: XCTestCase {
+  func testConvertingNumberToString() {
+    assertConvertingNumberToString(
+      input: #"{"number":1234567890,"text":"hello"}"#,
+      key: "number",
+      expectedOutput: #"{"number":"1234567890","text":"hello"}"#
+    )
+
+    assertConvertingNumberToString(
+      input: #"{"text":"hello","number":1234567890}"#,
+      key: "number",
+      expectedOutput: #"{"text":"hello","number":"1234567890"}"#
+    )
+
+    assertConvertingNumberToString(
+      input: #"{  "number"  :  1234567890  ,  "text"  :  "hello"  }"#,
+      key: "number",
+      expectedOutput: #"{  "number"  :  "1234567890"  ,  "text"  :  "hello"  }"#
+    )
+
+    assertConvertingNumberToString(
+      input: #"{  "text"  :  "hello"  ,  "number"  :  1234567890  }"#,
+      key: "number",
+      expectedOutput: #"{  "text"  :  "hello"  ,  "number"  :  "1234567890"  }"#
+    )
+
+    assertConvertingNumberToString(
+      input: """
+      {
+        "number": 1234567890,
+        "text": "hello"
+      }
+      """,
+      key: "number",
+      expectedOutput: """
+      {
+        "number": "1234567890",
+        "text": "hello"
+      }
+      """
+    )
+
+    assertConvertingNumberToString(
+      input: """
+      {
+        "text": "hello",
+        "number": 1234567890
+      }
+      """,
+      key: "number",
+      expectedOutput: """
+      {
+        "text": "hello",
+        "number": "1234567890"
+      }
+      """
+    )
+  }
+}
+
+private func assertConvertingNumberToString(
+  input: String,
+  key: String,
+  expectedOutput: String,
+  file: StaticString = #file,
+  line: UInt = #line
+) {
+  XCTAssertNoDifference(
+    String(
+      data: JSONDecoder().convertNumberToString(
+        in: input.data(using: .utf8)!,
+        at: key
+      ),
+      encoding: .utf8
+    )!,
+    expectedOutput,
+    file: file,
+    line: line
+  )
+}
diff --git a/Tests/XXClientTests/Helpers/JSONEncoderTests.swift b/Tests/XXClientTests/Helpers/JSONEncoderTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..58fbfae7a4bc41ec1feb620fdb8cd75031252889
--- /dev/null
+++ b/Tests/XXClientTests/Helpers/JSONEncoderTests.swift
@@ -0,0 +1,84 @@
+import CustomDump
+import XCTest
+@testable import XXClient
+
+final class JSONEncoderTests: XCTestCase {
+  func testConvertingStringToNumber() {
+    assertConvertingStringToNumber(
+      input: #"{"number":"1234567890","text":"hello"}"#,
+      key: "number",
+      expectedOutput: #"{"number":1234567890,"text":"hello"}"#
+    )
+
+    assertConvertingStringToNumber(
+      input: #"{"text":"hello","number":"1234567890"}"#,
+      key: "number",
+      expectedOutput: #"{"text":"hello","number":1234567890}"#
+    )
+
+    assertConvertingStringToNumber(
+      input: #"{  "number"  :  "1234567890"  ,  "text"  :  "hello"  }"#,
+      key: "number",
+      expectedOutput: #"{  "number"  :  1234567890  ,  "text"  :  "hello"  }"#
+    )
+
+    assertConvertingStringToNumber(
+      input: #"{  "text"  :  "hello"  ,  "number"  :  "1234567890"  }"#,
+      key: "number",
+      expectedOutput: #"{  "text"  :  "hello"  ,  "number"  :  1234567890  }"#
+    )
+
+    assertConvertingStringToNumber(
+      input: """
+      {
+        "number": "1234567890",
+        "text": "hello"
+      }
+      """,
+      key: "number",
+      expectedOutput: """
+      {
+        "number": 1234567890,
+        "text": "hello"
+      }
+      """
+    )
+
+    assertConvertingStringToNumber(
+      input: """
+      {
+        "text": "hello",
+        "number": "1234567890"
+      }
+      """,
+      key: "number",
+      expectedOutput: """
+      {
+        "text": "hello",
+        "number": 1234567890
+      }
+      """
+    )
+  }
+}
+
+private func assertConvertingStringToNumber(
+  input: String,
+  key: String,
+  expectedOutput: String,
+  file: StaticString = #file,
+  line: UInt = #line
+) {
+  XCTAssertNoDifference(
+    String(
+      data: JSONEncoder().convertStringToNumber(
+        in: input.data(using: .utf8)!,
+        at: key
+      ),
+      encoding: .utf8
+    )!,
+    expectedOutput,
+    file: file,
+    line: line
+  )
+}
diff --git a/Tests/XXClientTests/Helpers/JSONHelpersTests.swift b/Tests/XXClientTests/Helpers/JSONHelpersTests.swift
deleted file mode 100644
index 2ebe853034c56ea04fcbb52791fe6dbd56c079b2..0000000000000000000000000000000000000000
--- a/Tests/XXClientTests/Helpers/JSONHelpersTests.swift
+++ /dev/null
@@ -1,163 +0,0 @@
-import CustomDump
-import XCTest
-@testable import XXClient
-
-final class JSONHelpersTests: XCTestCase {
-  func testConvertingNumberToStringByKey() {
-    assertConvertingJsonNumberToString(
-      input: #"{"number":1234567890,"text":"hello"}"#,
-      key: "number",
-      expected: #"{"number":"1234567890","text":"hello"}"#
-    )
-
-    assertConvertingJsonNumberToString(
-      input: #"{"text":"hello","number":1234567890}"#,
-      key: "number",
-      expected: #"{"text":"hello","number":"1234567890"}"#
-    )
-
-    assertConvertingJsonNumberToString(
-      input: #"{  "number"  :  1234567890  ,  "text"  :  "hello"  }"#,
-      key: "number",
-      expected: #"{  "number"  :  "1234567890"  ,  "text"  :  "hello"  }"#
-    )
-
-    assertConvertingJsonNumberToString(
-      input: #"{  "text"  :  "hello"  ,  "number"  :  1234567890  }"#,
-      key: "number",
-      expected: #"{  "text"  :  "hello"  ,  "number"  :  "1234567890"  }"#
-    )
-
-    assertConvertingJsonNumberToString(
-      input: """
-      {
-        "number": 1234567890,
-        "text": "hello"
-      }
-      """,
-      key: "number",
-      expected: """
-      {
-        "number": "1234567890",
-        "text": "hello"
-      }
-      """
-    )
-
-    assertConvertingJsonNumberToString(
-      input: """
-      {
-        "text": "hello",
-        "number": 1234567890
-      }
-      """,
-      key: "number",
-      expected: """
-      {
-        "text": "hello",
-        "number": "1234567890"
-      }
-      """
-    )
-  }
-
-  func testConvertingStringToNumber() {
-    assertConvertingJsonStringToNumber(
-      input: #"{"number":"1234567890","text":"hello"}"#,
-      key: "number",
-      expected: #"{"number":1234567890,"text":"hello"}"#
-    )
-
-    assertConvertingJsonStringToNumber(
-      input: #"{"text":"hello","number":"1234567890"}"#,
-      key: "number",
-      expected: #"{"text":"hello","number":1234567890}"#
-    )
-
-    assertConvertingJsonStringToNumber(
-      input: #"{  "number"  :  "1234567890"  ,  "text"  :  "hello"  }"#,
-      key: "number",
-      expected: #"{  "number"  :  1234567890  ,  "text"  :  "hello"  }"#
-    )
-
-    assertConvertingJsonStringToNumber(
-      input: #"{  "text"  :  "hello"  ,  "number"  :  "1234567890"  }"#,
-      key: "number",
-      expected: #"{  "text"  :  "hello"  ,  "number"  :  1234567890  }"#
-    )
-
-    assertConvertingJsonStringToNumber(
-      input: """
-      {
-        "number": "1234567890",
-        "text": "hello"
-      }
-      """,
-      key: "number",
-      expected: """
-      {
-        "number": 1234567890,
-        "text": "hello"
-      }
-      """
-    )
-
-    assertConvertingJsonStringToNumber(
-      input: """
-      {
-        "text": "hello",
-        "number": "1234567890"
-      }
-      """,
-      key: "number",
-      expected: """
-      {
-        "text": "hello",
-        "number": 1234567890
-      }
-      """
-    )
-  }
-}
-
-private func assertConvertingJsonNumberToString(
-  input: String,
-  key: String,
-  expected: String,
-  file: StaticString = #file,
-  line: UInt = #line
-) {
-  XCTAssertNoDifference(
-    String(
-      data: convertJsonNumberToString(
-        in: input.data(using: .utf8)!,
-        at: key
-      ),
-      encoding: .utf8
-    )!,
-    expected,
-    file: file,
-    line: line
-  )
-}
-
-private func assertConvertingJsonStringToNumber(
-  input: String,
-  key: String,
-  expected: String,
-  file: StaticString = #file,
-  line: UInt = #line
-) {
-  XCTAssertNoDifference(
-    String(
-      data: convertJsonStringToNumber(
-        in: input.data(using: .utf8)!,
-        at: key
-      ),
-      encoding: .utf8
-    )!,
-    expected,
-    file: file,
-    line: line
-  )
-}