All Projects → swift-extras → Swift Extras Json

swift-extras / Swift Extras Json

Licence: apache-2.0
JSON encoding and decoding without the use of Foundation in pure Swift.

Programming Languages

swift
15916 projects

Labels

Projects that are alternatives of or similar to Swift Extras Json

Hulk
In-browser JSON editor
Stars: ✭ 302 (-4.73%)
Mutual labels:  json
Deepkit Framework
A new full-featured and high-performance web framework for sophisticated Typescript projects like complex admin interfaces, websites, games, desktop and mobile apps.
Stars: ✭ 307 (-3.15%)
Mutual labels:  json
Laravel5 Jsonapi
Laravel 5 JSON API Transformer Package
Stars: ✭ 313 (-1.26%)
Mutual labels:  json
Gosercomp
⚡️ Golang Serializer Benchmark Comparison
Stars: ✭ 300 (-5.36%)
Mutual labels:  json
Sapient
Secure API Toolkit
Stars: ✭ 308 (-2.84%)
Mutual labels:  json
Redsea
A lightweight RDS to JSON decoder
Stars: ✭ 311 (-1.89%)
Mutual labels:  json
Toapi
Every web site provides APIs.
Stars: ✭ 3,209 (+912.3%)
Mutual labels:  json
Node Json Db
A simple "database" that use JSON file for Node.JS.
Stars: ✭ 314 (-0.95%)
Mutual labels:  json
Ultrajson
Ultra fast JSON decoder and encoder written in C with Python bindings
Stars: ✭ 3,504 (+1005.36%)
Mutual labels:  json
Imbo
Imbo is an image "server" that can be used to add/get/delete images using a RESTful interface.
Stars: ✭ 312 (-1.58%)
Mutual labels:  json
Serialize.linq
C# library to serialize LINQ expressions
Stars: ✭ 304 (-4.1%)
Mutual labels:  json
Bebop
An extremely simple, fast, efficient, cross-platform serialization format
Stars: ✭ 305 (-3.79%)
Mutual labels:  json
Xxl Tool
a series of tools that make Java development more efficient.(Java工具类库XXL-TOOL)
Stars: ✭ 311 (-1.89%)
Mutual labels:  json
Commandtrayhost
A command line program monitor systray for Windows
Stars: ✭ 303 (-4.42%)
Mutual labels:  json
Lsp Mode
Emacs client/library for the Language Server Protocol
Stars: ✭ 3,691 (+1064.35%)
Mutual labels:  json
Ikigajson
A high performance JSON library in Swift
Stars: ✭ 302 (-4.73%)
Mutual labels:  json
Fractalistic
A framework agnostic, developer friendly wrapper around Fractal
Stars: ✭ 309 (-2.52%)
Mutual labels:  json
Mongo Sql
An extensible SQL generation library for JavaScript with a focus on introspectibility
Stars: ✭ 314 (-0.95%)
Mutual labels:  json
Ovine
Build entirety admin system ui blazing fast with json. (Looking for a front-end partner~ Please Contact me : )
Stars: ✭ 308 (-2.84%)
Mutual labels:  json
Django Ajax
Fast and easy AJAX libraries for django applications. Contains ajax decorator, ajax middleware, shortcuts and more.
Stars: ✭ 312 (-1.58%)
Mutual labels:  json

swift-extras-json

Swift 5.1 github-actions codecov

This package provides a json encoder and decoder in Swift (without the use of Foundation or any other dependency). The implementation is RFC8259 compliant. It offers a significant performance improvement compared to the Foundation implementation on Linux.

If you like the idea of using Swift without any dependencies, you might also like our reimplementation of Base64 in Swift: swift-extras-base64

Goals

  • [x] does not use Foundation at all
  • [x] does not use unsafe Swift syntax
  • [x] no external dependencies, only the Swift stdlib required
  • [x] faster than Foundation implementation

Currently not supported

Alternatives

Usage

Add swift-extras-json as dependency to your Package.swift:

  dependencies: [
    .package(url: "https://github.com/swift-extras/swift-extras-json.git", .upToNextMajor(from: "0.6.0")),
  ],

Add ExtrasJSON to the target you want to use it in.

  targets: [
    .target(name: "MyFancyTarget", dependencies: [
      .product(name: "ExtrasJSON", package: "swift-extras-json"),
    ])
  ]

Use it as you would use the Foundation encoder and decoder.

import ExtrasJSON

let bytesArray  = try XJSONEncoder().encode(myEncodable)
let myDecodable = try XJSONDecoder().decode(MyDecodable.self, from: bytes)

Use with SwiftNIO ByteBuffer

For maximal performance create an [UInt8] from your ByteBuffer, even though buffer.readableBytesView would technically work as well.

let result = try XJSONDecoder().decode(
  [SampleStructure].self,
  from: buffer.readBytes(length: buffer.readableBytes)!)
let bytes = try XJSONEncoder().encode(encodable)
var buffer = byteBufferAllocator.buffer(capacity: bytes.count)
buffer.writeBytes(bytes)

Use with Vapor 4

Increase the performance of your Vapor 4 API by using swift-extras-json instead of the default Foundation implementation. First you'll need to implement the conformance to Vapor's ContentEncoder and ContentDecoder as described in the Vapor docs.

import Vapor
import ExtrasJSON

extension XJSONEncoder: ContentEncoder {
  public func encode<E: Encodable>(
    _ encodable: E,
    to body: inout ByteBuffer,
    headers: inout HTTPHeaders) throws
  {
    headers.contentType = .json
    let bytes = try self.encode(encodable)
    // the buffer's storage is resized in case its capacity is not sufficient
    body.writeBytes(bytes)
  }
}

extension XJSONDecoder: ContentDecoder {
  public func decode<D: Decodable>(
    _ decodable: D.Type,
    from body: ByteBuffer,
    headers: HTTPHeaders) throws -> D
  {
    guard headers.contentType == .json || headers.contentType == .jsonAPI else {
      throw Abort(.unsupportedMediaType)
    }
    var body = body
    return try self.decode(D.self, from: body.readBytes(length: body.readableBytes)!)
  }
}

Next, register the encoder and decoder for use in Vapor:

let decoder = XJSONDecoder()
ContentConfiguration.global.use(decoder: decoder, for: .json)

let encoder = XJSONEncoder()
ContentConfiguration.global.use(encoder: encoder, for: .json)

Performance

All tests have been run on a 2019 MacBook Pro (16" – 2,4 GHz 8-Core Intel Core i9). You can run the tests yourself by cloning this repo and

# change dir to perf tests
$ cd PerfTests

# compile and run in release mode - IMPORTANT ‼️
$ swift run -c release

Encoding

macOS Swift 5.1 macOS Swift 5.2 Linux Swift 5.1 Linux Swift 5.2
Foundation 2.61s 2.62s 13.03s 12.52s
ExtrasJSON 1.23s 1.25s 1.13s 1.05s
Speedup ~2x ~2x ~10x ~10x

Decoding

macOS Swift 5.1 macOS Swift 5.2 Linux Swift 5.1 Linux Swift 5.2
Foundation 2.72s 3.04s 10.27s 10.65s
ExtrasJSON 1.70s 1.72s 1.39s 1.16s
Speedup ~1.5x ~1.5x ~7x ~8x

Workarounds

What about Date and Data?

Date and Data are particular cases for encoding and decoding. They do have default implementations that are kind off special:

  • Date will be encoded as a float

    Example: 2020-03-17 16:36:58 +0000 will be encoded as 606155818.503831

  • Data will be encoded as a numeric array.

    Example: 0, 1, 2, 3, 255 will be encoded as: [0, 1, 2, 3, 255]

Yes, that is the default implementation. Only Apple knows why it is not ISO 8601 and Base64. 🙃 Since I don't want to link against Foundation, it is not possible to implement default encoding and decoding strategies for Date and Data like the Foundation implementation does. That's why, if you want to use another encoding/decoding strategy than the default, you need to overwrite encode(to: Encoder) and init(from: Decoder).

This could look like this:

struct MyEvent: Decodable {

  let eventTime: Date
  
  enum CodingKeys: String, CodingKey {
    case eventTime
  }

  init(from decoder: Decoder) {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let dateString = try container.decode(String.self, forKey: .eventTime)
    guard let timestamp = MyEvent.dateFormatter.date(from: dateString) else {
      let dateFormat = String(describing: MyEvent.dateFormatter.dateFormat)
      throw DecodingError.dataCorruptedError(forKey: .eventTime, in: container, debugDescription:
        "Expected date to be in format `\(dateFormat)`, but `\(dateString) does not fulfill format`")
    }
    self.eventTime = timestamp
  }
  
  private static let dateFormatter: DateFormatter = MyEvent.createDateFormatter()
  private static func createDateFormatter() -> DateFormatter {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
    formatter.timeZone   = TimeZone(secondsFromGMT: 0)
    formatter.locale     = Locale(identifier: "en_US_POSIX")
    return formatter
  }
}

You can find more information about encoding and decoding custom types in Apple's documentation.

Of course you can use @propertyWrappers to make this more elegant:

import Foundation

@propertyWrapper
struct DateStringCoding: Decodable {
  var wrappedValue: Date
  
  init(wrappedValue: Date) {
    self.wrappedValue = wrappedValue
  }

  init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    let dateString = try container.decode(String.self)
    guard let date = Self.dateFormatter.date(from: dateString) else {
      let dateFormat = String(describing: Self.dateFormatter.dateFormat)
      throw DecodingError.dataCorruptedError(in: container, debugDescription:
            "Expected date to be in format `\(dateFormat)`, but `\(dateString) does not fulfill format`")
    }
    self.wrappedValue = date
  }

  private static let dateFormatter: DateFormatter = Self.createDateFormatter()
  private static func createDateFormatter() -> DateFormatter {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
    formatter.timeZone   = TimeZone(secondsFromGMT: 0)
    formatter.locale     = Locale(identifier: "en_US_POSIX")
    return formatter
  }
}

struct MyEvent: Decodable {
  @DateStringCoding
  var eventTime: Date
}

Checkout a full example in the test file DateCodingTests.

UTF-16 and UTF-32

If your input is UTF-16 or UTF-32 encoded, you can easily convert it to UTF-8:

let utf16 = UInt16[]() // your utf-16 encoded data
let utf8  = Array(String(decoding: utf16, as: Unicode.UTF16.self).utf8)
let utf32 = UInt32[]() // your utf-32 encoded data
let utf8  = Array(String(decoding: utf32, as: Unicode.UTF32.self).utf8)

Contributing

Please feel welcome and encouraged to contribute to swift-extras-json. This is a very young endeavour and help is always welcome.

If you've found a bug, have a suggestion, or need help getting started, please open an Issue or a PR. If you use this package, I'd be grateful for sharing your experience.

Focus areas for the time being:

  • ensuring safe use of nested containers while encoding and decoding
  • supporting camelCase and snakeCase aka KeyEncodingStrategy

Credits

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].