• Stars
    star
    612
  • Rank 73,287 (Top 2 %)
  • Language
    Swift
  • License
    MIT License
  • Created over 8 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Elevate is a JSON parsing framework that leverages Swift to make parsing simple, reliable and composable.

Elevate

Build Status CocoaPods Compatible Carthage Compatible Platform

Elevate is a JSON parsing framework that leverages Swift to make parsing simple, reliable and composable.

Elevate should no longer be used for new feature development. We recommend using the Codable protocol provided by Apple in the Foundation framework in its place. We will continue to support and update Elevate for the foreseeable future.

Features

  • Validation of full JSON payload
  • Parse complex JSON into strongly typed objects
  • Support for optional and required values
  • Convenient and flexible protocols to define object parsing
  • Large object graphs can be parsed into their component objects
  • Error aggregation across entire object graph

Requirements

  • iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+
  • Xcode 10.2+
  • Swift 5.0+

Communication

  • Need help? Open an issue.
  • Have a feature request? Open an issue.
  • Find a bug? Open an issue.
  • Want to contribute? Fork the repo and submit a pull request.

Installation

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:

[sudo] gem install cocoapods

CocoaPods 1.3+ is required.

To integrate Elevate into your Xcode project using CocoaPods, specify it in your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '11.0'
use_frameworks!

pod 'Elevate', '~> 3.0'

Carthage

Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.

You can install Carthage with Homebrew using the following command:

brew update
brew install carthage

To integrate Elevate into your Xcode project using Carthage, specify it in your Cartfile:

github "Nike-Inc/Elevate" ~> 3.0

To build Elevate on iOS only, use the following Carthage command:

carthage update --platform iOS

Usage

Elevate aims to make JSON parsing and validation simple, yet robust. This is achieved through a set of protocols and classes that can be utilized to create Decodable and Decoder classes. By using Elevate's parsing infrastructure, you'll be able to easily parse JSON data into strongly typed model objects or simple dictionaries by specifying each property key path and its associated type. Elevate will validate that the keys exist (if they're not optional) and that they are of the correct type. Validation errors will be aggregated as the JSON data is parsed. If an error is encountered, a ParserError will be thrown.

Elevate also supports encoding model objects back into JSON objects through the light-weight Encodable protocol. Convenience extensions have been added to collection types to make it easy to encode nested objects in a single pass.

Parsing JSON with Elevate

After you have made your model objects Decodable or implemented a Decoder for them, parsing with Elevate is as simple as:

let avatar: Avatar = try Elevate.decodeObject(from: data, atKeyPath: "response.avatar")

Pass an empty string into atKeyPath if your object or array is at the root level.

Creating Decodables

In the previous example Avatar implements the Decodable protocol. By implementing the Decodable protocol on an object, it can be used by Elevate to parse avatars from JSON data as a top-level object, a sub-object, or even an array of avatar objects.

public protocol Decodable {
    init(json: Any) throws
}

The json: Any will typically be a [String: Any] instance that was created from the JSONSerialization APIs. Use the Elevate Parser.parseEntity method to define the structure of the JSON data to be validated and perform the parsing.

struct Person {
    let identifier: String
    let name: String
    let nickname: String?
    let birthDate: Date
    let isMember: Bool?
    let addresses: [Address]
}

extension Person: Elevate.Decodable {
    fileprivate struct KeyPath {
        static let id = "identifier"
        static let name = "name"
        static let nickname = "nickname"
        static let birthDate = "birthDate"
        static let isMember = "isMember"
        static let addresses = "addresses"
    }

    init(json: Any) throws {
        let dateDecoder = DateDecoder(dateFormatString: "yyyy-MM-dd")

        let entity = try Parser.parseEntity(json: json) { schema in
            schema.addProperty(keyPath: KeyPath.id, type: .int)
            schema.addProperty(keyPath: KeyPath.name, type: .string)
            schema.addProperty(keyPath: KeyPath.nickname, type: .string, optional: true)
            schema.addProperty(keyPath: KeyPath.birthDate, type: .string, decoder: dateDecoder)
            schema.addProperty(keyPath: KeyPath.isMember, type: .bool, optional: true)
            schema.addProperty(keyPath: KeyPath.addresses, type: .array, decodableType: Address.self)
        }

        self.identifier = entity <-! KeyPath.id
        self.name = entity <-! KeyPath.name
        self.nickname = entity <-? KeyPath.nickname
        self.birthDate = entity <-! KeyPath.birthDate
        self.isMember = entity <-? KeyPath.isMember
        self.addresses = entity <--! KeyPath.addresses
    }
}

Implementing the Decodable protocol in this way allows you to create fully intialized structs that can contain non-optional constants from JSON data.

Some other things worth noting in this example:

  1. The Decodable protocol conformance was implemented as an extension on the struct. This allows the struct to keep its automatic memberwise initializer.
  2. Standard primitive types are supported as well as URL, Array, and Dictionary types. See ParserPropertyProtocol definition for the full list.
  3. Elevate facilitates passing a parsed property into a Decoder for further manipulation. See the birthDate property in the example above. The DateDecoder is a standard Decoder provided by Elevate to make date parsing hassle free.
  4. A Decoder or Decodable type can be provided to a property of type .Array to parse each item in the array to that type. This also works with the .Dictionary type to parse a nested JSON object.
  5. The parser guarantees that properties will be of the specified type. Therefore, it is safe to use the custom operators to automatically extract the Any value from the entity dictionary and cast it to the return type.

Property Extraction Operators

Elevate contains four property extraction operators to make it easy to extract values out of the entity dictionary and cast the Any value to the appropriate type.

  • <-! - Extracts the value from the entity dictionary for the specified key. This operator should only be used on non-optional properties.
  • <-? - Extracts the optional value from the entity dictionary for the specified key. This operator should only be used on optional properties.
  • <--! - Extracts the array from the entity dictionary for the specified key as the specified array type. This operator should only be used on non-optional array properties.
  • <--? - Extracts the array from the entity dictionary for the specified key as the specified optional array type.

Creating Encodables

Extending a model object to conform to the Encodable protocol is less involved than making it Decodable. Since your object is already strongly typed, it only needs to be converted into a JSON friendly Any object. Building on the previous Person type, let's make it conform to the Encodable protocol.

extension Person: Elevate.Encodable {
    var json: Any {
        var json: [String: Any] = [
            KeyPath.id: identifier,
            KeyPath.name: name,
            KeyPath.birthDate: birthDate,
            KeyPath.addresses: addresses.json
        ]

        if let nickname = nickname { json[KeyPath.nickname] = nickname }
        if let isMember = isMember { json[KeyPath.isMember] = isMember }

        return json
    }
}

As you can see in the example, converting the Person into a JSON dictionary is straightforward. It's also easy to convert the array of Address objects into JSON by calling the json property on the array. This works because Address also conforms to Encodable. The collection type extensions on Array, Set and Dictionary make it easy to convert a complex objects with multiple layers of Encodable objects into a JSON objects.


Advanced Usage

Decoders

In most cases implementing a Decodable model object is all that is needed to parse JSON using Elevate. There are some instances though where you will need more flexibility in the way that the JSON is parsed. This is where the Decoder protocol comes in.

public protocol Decoder {
    func decode(_ object: Any) throws -> Any
}

A Decoder is generally implemented as a separate object that returns instances of the desired model object. This is useful when you have multiple JSON mappings for a single model object, or if you are aggregating data across multiple JSON payloads. For example, if there are two separate services that return JSON for Avatar objects that have a slightly different property structure, a Decoder could be created for each mapping to handle them individually.

The input type and output types are intentionally vague to allow for flexibility. A Decoder can return any type you want -- a strongly typed model object, a dictionary, etc. It can even dynamically return different types at runtime if needed.

Using Multiple Decoders

class AvatarDecoder: Elevate.Decoder {
    func decode(_ object: Any) throws -> Any {
        let urlKeyPath = "url"
        let widthKeyPath = "width"
        let heightKeyPath = "height"

        let entity = try Parser.parseEntity(json: object) { schema in
            schema.addProperty(keyPath: urlKeyPath, type: .url)
            schema.addProperty(keyPath: widthKeyPath, type: .int)
            schema.addProperty(keyPath: heightKeyPath, type: .int)
        }

        return Avatar(
            URL: entity <-! urlKeyPath,
            width: entity <-! widthKeyPath,
            height: entity <-! heightKeyPath
        )
    }
}
class AlternateAvatarDecoder: Elevate.Decoder {
    func decode(_ object: Any) throws -> Any {
        let locationKeyPath = "location"
        let wKeyPath = "w"
        let hKeyPath = "h"

        let entity = try Parser.parseEntity(json: object) { schema in
            schema.addProperty(keyPath: locationKeyPath, type: .url)
            schema.addProperty(keyPath: wKeyPath, type: .int)
            schema.addProperty(keyPath: hKeyPath, type: .int)
        }

        return Avatar(
            URL: entity <-! locationKeyPath,
            width: entity <-! wKeyPath,
            height: entity <-! hKeyPath
        )
    }
}

Then to use the two different Decoder objects with the Parser:

let avatar1: Avatar = try Elevate.decodeObject(
    from: data1, 
    atKeyPath: "response.avatar", 
    with: AvatarDecoder()
)

let avatar2: Avatar = try Elevate.decodeObject(
    from: data2, 
    atKeyPath: "alternative.response.avatar", 
    with: AlternateAvatarDecoder()
)

Each Decoder is designed to handle a different JSON structure for creating an Avatar. Each uses the key paths specific to the JSON data it's dealing with, then maps those back to the properties on the Avatar object. This is a very simple example to demonstration purposes. There are MANY more complex examples that could be handled in a similar manner via the Decoder protocol.

Decoders as Property Value Transformers

A second use for the Decoder protocol is to allow for the value of a property to be further manipulated. The most common example is a date string. Here is how the DateDecoder implements the Decoder protocol:

public func decode(_ object: Any) throws -> Any {
    if let string = object as? String {
        return try dateFromString(string, withFormatter:self.dateFormatter)
    } else {
        let description = "DateParser object to parse was not a String."
        throw ParserError.Validation(failureReason: description)
    }
}

And here is how it's used to parse a JSON date string:

let dateDecoder = DateDecoder(dateFormatString: "yyyy-MM-dd 'at' HH:mm")

let entity = try Parser.parseEntity(data: data) { schema in
    schema.addProperty(keyPath: "dateString", type: .string, decoder: dateDecoder)
}

You are free to create any decoders that you like and use them with your properties during parsing. Some other uses would be to create a StringToBoolDecoder or StringToFloatDecoder that parses a Bool or Float from a JSON string value. The DateDecoder and StringToIntDecoder are already included in Elevate for your convenience.


Creators

More Repositories

1

Willow

Willow is a powerful, yet lightweight logging library written in Swift.
Swift
1,334
star
2

gimme-aws-creds

A CLI that utilizes Okta IdP via SAML to acquire temporary AWS credentials
Python
902
star
3

koheesio

Python framework for building efficient data pipelines. It promotes modularity and collaboration, enabling the creation of complex pipelines from simple, reusable components.
Python
595
star
4

burnside

Fast and Reliable E2E Web Testing with only Javascript
JavaScript
381
star
5

wingtips

Wingtips is a distributed tracing solution for Java based on the Google Dapper paper.
Java
326
star
6

hal

hal provides an AWS Lambda Custom Runtime environment for your Haskell applications.
Haskell
235
star
7

brickflow

Pythonic Programming Framework to orchestrate jobs in Databricks Workflow
Python
187
star
8

spark-expectations

A Python Library to support running data quality rules while the spark job is running⚑
Python
161
star
9

SQift

Powerful Swift wrapper for SQLite
Swift
141
star
10

riposte

Riposte is a Netty-based microservice framework for rapid development of production-ready HTTP APIs.
Java
122
star
11

timeseries-generator

A library to generate synthetic time series data by easy-to-use factors and generator
Python
122
star
12

bartlett

A simple Jenkins command line client to serve your needs.
Haskell
81
star
13

cerberus-doc-site

Secure Property Store for Cloud Applications
CSS
81
star
14

aws-greengrass-core-sdk-rust

Provides an idiomatic Rust wrapper around the AWS Greengrass Core C SDK to more easily enable Greengrass native lambda functions in Rust.
Rust
71
star
15

cerberus

The Cerberus micro-service, a secure property store for cloud applications. It includes a REST API, authentication and encryption features, as well as a self-service web UI for users.
Java
62
star
16

referee

Referee is a UI for using Spinnaker Kayenta as a standalone service.
TypeScript
59
star
17

moirai

Libraries that can be used to determine if a feature should be exposed to a user.
Java
53
star
18

riposte-microservice-template

An example template for quickly creating a new Riposte microservice project.
Java
51
star
19

harbormaster

Harbormaster is a webhook handler for the Kubernetes API.
Go
42
star
20

fastbreak

Fastbreak is a simple Java 8 native circuit breaker supporting async future, blocking, and callback/manual modes.
Java
40
star
21

signal_analog

A troposphere-inspired library for programmatic, declarative definition and management of SignalFx Charts, Dashboards, and Detectors.
Python
39
star
22

backstopper

Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 7 and up.
Java
38
star
23

react-virtualized-item-grid

React component for efficiently rendering a large, scrollable list of items in a series of wrapping rows
JavaScript
38
star
24

knockoff-factory

A library for generating fake data and populating database tables.
Python
34
star
25

pterradactyl

Pterradactyl is a library developed to abstract Terraform configuration from the Terraform environment setup.
Python
32
star
26

lambda-logger-node

A middleware logger that implements the MDC logging pattern for use in AWS NodeJS Lambdas.
TypeScript
29
star
27

lambda-router

JavaScript
23
star
28

bokor

Bokor is a simple, Record and Playback Mock Server written in Node.js, utilized for Service Virtualization.
JavaScript
23
star
29

piggyback

This tool allows you to tunnel SSH (using ProxyCommand) via HTTPS (with Squid Proxy). It is a python implementation of corkscrew, but over https (TLS) instead of http (plaintext).
Python
17
star
30

cerberus-node-client

Node client for interacting with a Cerberus backend. It can be used in Amazon EC2 instances and Amazon Lambdas.
JavaScript
16
star
31

cerberus-java-client

Java Client for Cerberus
Java
14
star
32

cerberus-lifecycle-cli

Command Line Interface for managing a Cerberus environment in AWS
Java
14
star
33

cerberus-python-client

Python Client for Cerberus
Python
13
star
34

cerberus-management-dashboard

A single page react app that is the self service web UI for administration of Safe Deposit Boxes, access control, and data.
HTML
13
star
35

tdd-training-cube

Papercraft cube used as training aid for Outside-In Test Driven Development
11
star
36

cerberus-serverless-components

A collection of AWS Serverless components for Cerberus
Java
11
star
37

cerberus-go-client

A Golang client for interacting with Cerberus, a secure property store for cloud applications.
Go
11
star
38

gradle-localstack

Gradle plugin for working with mock AWS endpoints using LocalStack.
Java
11
star
39

aws-thin-dynamo-node

A small, fast re-implementation of the AWS Dynamo DocumentClient
JavaScript
10
star
40

cerberus-archaius-client

An Archaius property provider implementation backed by Cerberus.
Java
9
star
41

epc-standards

Implementation of decoding GS1 EPC tags
Java
9
star
42

lambda-zipper

Zip up your node lambda code and production dependencies without pruning node_modules
JavaScript
9
star
43

java-vault-client

This is a java based Vault client library for communicating with Vault via HTTP.
Java
8
star
44

cerberus-cli

A CLI for the Cerberus API.
Go
8
star
45

cerberus-integration-tests

Groovy
8
star
46

cerberus-gateway-puppet-module

Puppet Module for installing Nginx and config downloader scripts
Python
8
star
47

Fleam

Scala
7
star
48

cerberus-consul-puppet-module

A Puppet module for installing Hashicorp's Consul as a service with customized start up scripts for Cerberus.
HTML
7
star
49

bluegreen-manager

Java
6
star
50

aws-thin-s3-node

A super-thin AWS S3 client
JavaScript
5
star
51

homebrew-nike

Homebrew formulas provided by Nike, Inc.
Ruby
5
star
52

sagerender

A library for configuring SageMaker pipelines using hierarchical configuration pattern.
Python
5
star
53

dynamo-arc

TypeScript
5
star
54

metrics-new-relic-insights

Reporter to send Dropwizard Metrics to New Relic Insights.
Java
5
star
55

cerberus-vault-puppet-module

A Puppet module for installing Hashicorp's Vault as a service with customized start up scripts for Cerberus.
HTML
5
star
56

cerberus-spring-boot-client

Spring Boot client for interacting with a Cerberus backend.
Java
4
star
57

aws-thin-ses-node

A super-thin AWS Simple Email Service client
JavaScript
3
star
58

dabber

Dabber is a Node CLI tool and AWS Lambda that helps you work with Dynamo.
JavaScript
3
star
59

actions-cerberus-secrets

Read secrets from Cerberus and make it as environment variables in GitHub Actions job so that it can be used in CICD process.
TypeScript
3
star
60

nike-inc.github.io

HTML
3
star
61

phiera

Python
2
star
62

Fawcett

A collection of Monocle lenses for navigating Amazon's API models.
Scala
2
star
63

cerberus-ruby-client

Ruby Client for Cerberus
Ruby
2
star
64

aws-scale

AWS Scaling Made Simple
JavaScript
2
star
65

gimme-a-cli

Gimme a CLI is a Java library for creating quick and easy command line interfaces (CLIs) using JCommander and Spring dependency injection.
Java
2
star
66

gradle-localdynamodb-plugin

XSLT
1
star
67

dynamo-butter

JavaScript
1
star
68

redwiggler

The composting worm. Composts your contract specification and tests and confirms that the contract specification is being followed.
Scala
1
star
69

gimme-a-cli-starter-project

Clone and modify this project to quickly create your own CLI based on the Gimme a CLI library.
Java
1
star