Code Monkey home page Code Monkey logo

plank's Introduction

Plank logo

Plank

Plank is a command-line tool for generating robust immutable models from JSON Schemas. It will save you time writing boilerplate and eliminate model errors as your application scales in complexity.

We currently support the following languages:

Schema-defined

Models are defined in JSON, a well-defined, extensible and language-independent specification.

Immutable Classes

Model classes are generated to be immutable. Each class provides a “Builder” class to handle mutation.

Type safe

Based on the type information specified in the schema definition, each class provides type validation and null reference checks to ensure model integrity.

Installation

MacOS

$ brew install plank

Linux

Plank supports building in Ubuntu Linux via Docker. The Dockerfile in the repository will fetch the most recent release of plank and build dependencies including the Swift snapshot.

$ docker build -t plank .

Build from source

Plank is built using the Swift Package Manager. Although you’ll be able to build Plank using swift build directly, for distribution of the binary we’d recommend using the commands in the Makefile since they will pass the necessary flags to bundle the Swift runtime.

$ make archive

Getting Started

Keep reading to learn about Plank’s features or get your try it yourself with the tutorial.

Defining a schema

Plank takes a schema file as an input. Schemas are based on JSON, a well-defined, extensible and language-independent specification. Defining schemas in JSON allowed us to avoid writing unnecessary parser code and opened up the possibility of generating code from the same type system used on the server.

{
    "id": "pin.json",
    "title": "pin",
    "description" : "Schema definition of a Pin",
    "$schema": "http://json-schema.org/schema#",
    "type": "object",
    "properties": {
        "id": { "type": "string" },
        "link": { "type": "string", "format": "uri"}
    }
}

You’ll notice we specify the name of the model and a list of its properties. Note that link specifies an additional format attribute which tells Plank to use a more concrete type like NSURL or NSDate.

Generating a model

Generate a schema file (pin.json) using Plank using the format plank [options] file1 file2 file3 ...

$ plank pin.json

This will generate files (Pin.h, Pin.m) in the current directory:

$ ls
pin.json Pin.h Pin.m

Learn more about usage on the installation page.

Generated Output

Plank will generate the necessary header and implementation file for your schema definition. Here is the output of the Pin.h header.

@interface Pin
@property (nonatomic, copy, readonly) NSString *identifier;
@property (nonatomic, strong, readonly) NSURL *link;
@end

All properties are readonly, as this makes the class immutable. Populating instances of your model with values is performed by builder classes.

Mutations through builders

Model mutations are performed through a builder class. The builder class is a separate type which contains readwrite properties and a build method that will create a new object. Here is the header of the builder that Plank generated for the Pin model:

@interface PinBuilder
@property (nonatomic, copy, readwrite) NSString *identifier;
@property (nonatomic, strong, readwrite) NSURL *link;
- (Pin *)build;
@end

@interface Pin (BuilderAdditions)
- (instancetype)initWithBuilder:(PinBuilder *)builder;
@end

JSON parsing

Plank will create an initializer method called initWithModelDictionary: that handles parsing NSDictionary objects that conform to your schema. The generated implementation will perform validation checks based on the schema’s types, avoiding the dreaded [NSNull null] showing up in your objects:

@interface Pin (JSON)
- (instancetype)initWithModelDictionary:(NSDictionary *)dictionary;
@end

Serialization

All Plank generated models implement methods to conform to NSSecureCoding, providing native serialization support out of the box.

+ (BOOL)supportsSecureCoding {
    return YES;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (!(self = [super init])) { return self; }
    _identifier = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"identifier"];
    _link = [aDecoder decodeObjectOfClass:[NSURL class] forKey:@"link"];
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.identifier forKey:@"identifier"];
    [aCoder encodeObject:self.link forKey:@"link"];
}

With this implementation objects are serialized using NSKeyedArchiver:

// Create a pin from a server response
Pin *pin = [[Pin alloc] initWithModelDictionary:/* server response */];
// Cache the pin
[NSKeyedArchiver archiveRootObject:pin toFile:/* cached pin path */];
// App goes on, terminates, restarts...
// Load the cached object
Pin *cachedPin = [NSKeyedUnarchiver unarchiveObjectWithFile:/* cached pin path */];

Model merging and partial data

Plank models support merging and partial object materialization through a conventional “last writer wins” approach. By calling the mergeWithModel: on a generated model with a newer instance, the properties set on the newer instance will be preserved. To know which properties are set, the information is tracked internally within the model during initialization or through any mutation methods. In this example, an identifier property is used as a primary key for object equality:

static inline id valueOrNil(NSDictionary *dict, NSString *key);
struct PinSetProperties {
  unsigned int Identifier:1;
  unsigned int Link:1;
};
- (instancetype)initWithModelDictionary:(NSDictionary *)modelDictionary {
  [modelDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *  _Nonnull key, id obj, BOOL *stop){
    if ([key isEqualToString:@"identifier"]) {
        // ... Set self.identifier
        self->_pinSetProperties.Identifier = 1;
    }
    if ([key isEqualToString:@"link"]) {
        // ... Set self.link
        self->_pinSetProperties.Link = 1;
    }
  }];
  // Post notification
  [[NSNotificationCenter defaultCenter] postNotificationName:@"kPlankModelDidInitializeNotification" object:self];
}

With immutable models, you have to think through how data flows through your application and how to keep a consistent state. At the end of every model class initializer, there’s a notification posted with the updated model. This is useful for integrating with your data consistency framework. With this you can track whenever a model has updated and can use internal tracking to merge the new model.

@implementation Pin(ModelMerge)
- (instancetype)mergeWithModel:(Pin *)modelObject {
    PinBuilder *builder = [[PinBuilder alloc] initWithModelObject:self];
    if (modelObject.pinSetProperties.Identifier) {
        builder.identifier = modelObject.identifier;
    }
    if (modelObject.pinSetProperties.Link) {
        builder.link = modelObject.link;
    }
    return [builder build];
}
@end

Algebraic data types

Plank provides support for specifying multiple model types in a single property, commonly referred to as an Algebraic Data Type (ADT). In this example, the Pin schema receives an attribution property that will be either a User, Board, or Interest. Assume we have separate schemas defined in files user.json, board.json and interest.json, respectively.

{
    "title": "pin",
    "properties": {
        "identifier": { "type": "string" },
        "link": { "type": "string", "format": "uri"},
        "attribution": {
          "oneOf": [
            { "$ref": "user.json" },
            { "$ref": "board.json" },
            { "$ref": "interest.json" }
          ]
        }
    }
}

Plank takes this definition of attribution and creates the necessary boilerplate code to handle each possibility. It also provides additional type-safety by generating a new class to represent your ADT.

@interface PinAttribution : NSObject<NSCopying, NSSecureCoding>
+ (instancetype)objectWithUser:(User *)user;
+ (instancetype)objectWithBoard:(Board *)board;
+ (instancetype)objectWithInterest:(Interest *)interest;
- (void)matchUser:(void (^)(User * pin))userMatchHandler
          orBoard:(void (^)(Board * board))boardMatchHandler
       orInterest:(void (^)(Interest * interest))interestMatchHandler;
@end

Note there’s a “match” function declared in the header. This is how you extract the true underlying value of your ADT instance. This approach guarantees at compile-time you have explicitly handled every possible case preventing bugs and reducing the needs to use runtime reflection which hinders performance. The example below shows how you’d use this match function.

PinAttribution *attribution;
[attribution matchUser:^(User *user) {
   // Do something with "user"
} orBoard:^(Board *board) {
   // Do something with "board"
} orInterest:^(Interest *interest) {
   // Do something with "interest"
}];

Contributing

Pull requests for bug fixes and features welcomed.

License

Copyright 2019 Pinterest, Inc.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

plank's People

Contributors

adlai-holler avatar andrewapperley avatar ay8s avatar bkase avatar bolsinga avatar garrettmoon avatar joprice avatar jparise avatar levi avatar maicki avatar nguyenhuy avatar rahul-malik avatar ricoyao avatar rmls avatar shashachu avatar shto avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

plank's Issues

GraphQL Schema

I think we should supporting GraphQL’s schema representation in Plank. Four big reasons:

  1. GraphQL provides richer type support (enums, unions, interfaces).
  2. GraphQL supports documentation elements that could be included in the generated code.
  3. GraphQL directives provide a mechanism for marking fields as @deprecated, etc.
  4. It allows for an easy transition from static schema files to “live” introspected schema queries.

Dictionary Representation throws warnings for strict selector matching

We get the below error because it is present in Foundation APIs

error: multiple methods named 'dictionaryRepresentation' found [-Werror,-Wstrict-selector-match]
514                 [result0 addObject:[obj0 dictionaryRepresentation]];
515                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
516 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator11.0.sdk/System/Librar    y/Frameworks/Foundation.framework/Headers/NSMapTable.h:68:1: note: using
517 - (NSDictionary<KeyType, ObjectType> *)dictionaryRepresentation;  // create a dictionary of contents
518 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
519 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator11.0.sdk/System/Librar y/Frameworks/Foundation.framework/Headers/NSUserDefaults.h:146:1: note: also found
520 - (NSDictionary<NSString *, id> *)dictionaryRepresentation;
521 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
522 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator11.0.sdk/System/Librar    y/Frameworks/Foundation.framework/Headers/NSUbiquitousKeyValueStore.h:48:58: note: also found
523 @property (readonly, copy) NSDictionary<NSString *, id> *dictionaryRepresentation;
.....

Ability to name Objective-C property differently than its JSON key

Say I'm interfacing with an API that has an img_url key. If I'm understanding everything correctly, plank would incorporate that as imgUrl. It'd be nice to be able to specify it as imageURL to be more inline with Objective-C convention (I don't have server-side control over this API, so I can't change it there).

Or is there a goal to keep it absolutely 1:1 with the server-side spec?

Model value type in Schema.Map / Schema.Array as a separate enum

From @bkase comment on #3 .

@@ -83,10 +83,15 @@ struct ObjCRootsRenderer {
switch schema {
case .Array(itemType: .none):
return "NSArray *"

  •    case .Array(itemType: .some(let itemType)) where itemType.isObjCPrimitiveType:
    

This is probably not worth changing, but -- if it's possible to represent something as a pure pattern match without guards, it's faster because then the switch can compile into a jump table (I don't know if Swift takes advantage of this optimization though).

The way to do that in this case would be to change the way we parse the JSON schema for the Array and Map cases and change itemType to something like:

enum CollectionElement {
case Unknown
case ObjcClass(Schema)
case Primitive(name: String)
}
instead of what is something like Schema?

And then you can do a deeper pattern match on those cases to avoid the where clause

Automate releases

Releases should go out weekly assuming there have been changes and all CI measures have passed

iOS reserved property name

I had issues with a few variable names that are reserved, I know that plank handle a few of them (id, description), but not all. I had issues with "class" and "continue".

Is there anything that I can do to avoid this problem?

Synchronise models through different view controllers

Is it possible to modify the same model in different instances of different views?

For example, I have an user model in the profile view and in the home view. If I change the model in the profile view it should also change in home screen.

This gif shows what I'm trying to achieve using Facebook like button state as example.
ezgif com-resize

Error to upgrade to 1.1 on High Sierra

This generates the error: https://github.com/pinterest/plank/blob/master/Makefile#L25

brew upgrade pinterest/tap/plank
==> Upgrading 1 outdated package, with result:
pinterest/tap/plank 1.1
==> Upgrading pinterest/tap/plank
==> Cloning https://github.com/pinterest/plank.git
Updating /Users/Leo-KiddoLabs/Library/Caches/Homebrew/plank--git
==> Checking out tag v1.1
==> unset CC; make archive
Last 15 lines from /Users/Leo-KiddoLabs/Library/Logs/Homebrew/plank/01.unset:
2017-09-25 14:16:47 -0300

unset CC; make archive

xcrun swift build -c release -Xswiftc -static-stdlib
error: manifest parse error(s):
sandbox-exec: sandbox_apply_container: Operation not permitted
make: *** [archive] Error 1

Do not report this issue to Homebrew/brew or Homebrew/core!


Error: You are using macOS 10.13.
We do not provide support for this pre-release version.
You may encounter build failures or other breakages.
Please create pull-requests instead of filing issues.

Error: You are using macOS 10.13.
We do not provide support for this pre-release version.
You may encounter build failures or other breakages.
Please create pull-requests instead of filing issues.

captura de tela 2017-09-25 as 14 19 15

Improve slightly unintuitive behavior of enum "description" field

My intuition was to use the field "description" on an enum value to describe the purpose of the value. However, "description" is used to generate the enum name. It seems like it might make more sense to decouple name and description.

Setting a description like this:

    "enum": [
       { "default" : "unknown", "description" : "Default value. This is not a value that the API will return."},

Generates this output which doesn't compile:

    typedef NS_ENUM(NSInteger, PIPinSafetyStatusRedirectStatusType) {
        PIPinSafetyStatusRedirectStatusTypeDefault Value. This Is Not A Value That The Api Will Return. /* unknown */,

Setup CI

  • Decide between Travis, Buildkite, etc
  • Build and Test should be run
  • Add Dangerfile support

Generated Builder's property setter need implement `copy`, to align attribute of `copy` at @property.

Thank you for this cool lib and immutable models concept.

The generated PinBuilder is as following.

@interface PinBuilder : NSObject
@property (nullable, nonatomic, copy, readwrite) NSString * identifier;

but the PinBuilder setter implementation didn't reflect the copy attribute at property.

- (void)setIdentifier:(NSString *)identifier
{
    _identifier = identifier;
    _pinDirtyProperties.PinDirtyPropertyIdentifier = 1;
}

Example schema

{
    "id": "pin.json",
    "title": "pin",
    "description" : "Schema definition of a Pin",
    "$schema": "http://json-schema.org/schema#",
    "type": "object",
    "properties": {
        "id": { "type": "string" },
        "link": { "type": "string", "format": "uri"}
    }
}

This is similar to #71

Add support for merging strategies

Currently we assume that overwrite is the write strategy for merging maps, lists, etc. Based on the application this assumption can be incorrect and the desired interaction is likely a merge of the data types instead.

This likely means augmenting how we declare properties to allow users to specify this and updating the merge with model code.

This might not be available on all property types so we should clearly document when and where this option can be used.

Flow Type Support

We are planning to add Flow type support to plank and and here are some initial thoughts around it.

The initial idea was to provide a more extended JS integration with immutable models backed by Immutable.js. A partial implementation was already on a side branch too, but as Immutable.js is pretty hefty in size as well as would introduce a certain convention to use Immutable.js up front, we decided to take a step back for the first pass and start with adding Flow support to plank.

Pull Request

A pull request that implements this issue as currently described is ready: #57

Example

For a first example what the following PDT definition of an extensive representation of a Pin type is provided as well as the created Flow type definition below.

PDT

{
    "id": "pin.json",
    "title": "pin",
    "description" : "Schema definition of Pinterest Pin",
    "$schema": "http://json-schema.org/schema#",
    "type": "object",
    "properties": {
		"id" : { "type": "string" },
		"link" : {
			"type": "string",
			"format": "uri"
		},
		"url" : {
			"type": "string",
			"format": "uri"
		},
		"creator": {
			"type": "object",
			"additionalProperties": { "$ref": "user.json" }
		},
		"board": { "$ref": "board.json" },
		"created_at" : {
			"type": "string",
			"format": "date-time"
		},
		"note" : { "type": "string" },
		"color" : { "type": "string" },
		"counts": {
			"type": "object",
			"additionalProperties": { "type": "integer" }
		},
		"media": {
			"type": "object",
			"additionalProperties": { "type": "string" }
		},
		"attribution": {
			"type": "object",
			"additionalProperties": { "type": "string" }
		},
		"description" : { "type": "string" },
		"image": { "$ref": "image.json" }
	},
    "required": []
}

Flow

import type { PlankDate, PlankURI } from "./runtime.flow.js";
import type BoardType from "./BoardType.js";
import type ImageType from "./ImageType.js";
import type UserType from "./UserType.js";

export type PinType = $Shape<{|
    +note?: string | null,
    +media?: { [string]: string } | null,
    +counts?: { [string]: number } /* Integer */ | null,
    +descriptionText?: string | null,
    +creator?: { [string]: UserType } | null,
    +attribution?: { [string]: string } | null,
    +board?: BoardType | null,
    +color?: string | null,
    +link?: PlankDate | null,
    +identifier?: string | null,
    +image?: ImageType | null,
    +createdAt?: PlankDate | null,
    +url?: PlankDate | null,
|}> & {
    id: string
};

Some notes

Let's pick out some interesting pieces from the example above and provide bit more details.

Type alias

For every plank type an exported Flow type alias with the name TitleType will be created.

Property variance (read-only)

Currently all properties are defined as covariant (read-only), declared by the plus symbol in front of the property name.

Optional properties

As it's currently not possible to know for sure, that properties are included within the API response we declare the properties as optional. Furthermore, if the property is included in the API response we don't know for sure if a valid value or null was received. Therefore the type definition of a property is always declared as optional.

Primitive type properties

For types like integer or strings, equivalent primitive types like number and string are used.

Object as maps properties

For object types that act like a map, like thecounts property from above, we use a special kind of property, called an "indexer property".

Format type properties

For specific format types, we are providing pre-defined types which are defined in a specific runtime file. In case of the date and uri type the representation is just a string for now:

...
export type PlankDate = string;
export type PlankURI = string;
...

Reference properties

If references to other types are defined within the PDT, the referenced type will be imported and the property will be annotated with the reference type.

Enum and ADT properties

We also have support for enums and ADTs, which are not present in the example above. Examples for both of them would look like the following:

ADTs

PDT
...
"attribution": {
  "oneOf": [
    { "$ref": "image.json" },
    { "$ref": "board.json" }
  ]
},
...
Flow
...
export type PinAttributionType = ImageType | BoardType;
...

export type PinType = $Shape<{|
  +attribution?: PinAttributionType | null,
  ...
|}> ...

Enums

PDT
...
"status" : {
  "type": "string",
  "enum": [
      { "default" : "unknown", "description" : "unknown" },
      { "default" : "new", "description" : "new" },
      { "default" : "accepted", "description" : "accepted" },
      { "default" : "denied", "description" : "denied" },
      { "default" : "pending_approval", "description" : "pending_approval" },
      { "default" : "contact_request_not_approved", "description": "contact_request_not_approved" }
  ],
  "default" : "unknown"
},
"availability" : {
  "type": "integer",
  "enum": [
      { "default" : 1, "description" : "in_stock" },
      { "default" : 2, "description" : "out_of_stock" },
      { "default" : 3, "description" : "preorder" },
      { "default" : 4, "description" : "unavailable" }
  ]
},
...
Flow
export type PinStatusType =
    | "unknown"
    | "new"
    | "accepted"
    | "denied"
    | "pending_approval"
    | "contact_request_not_approved";

export type PinAvailabilityType =
    | 1 /* in_stock */
    | 2 /* out_of_stock */
    | 3 /* preorder */
    | 4; /* unavailable */
...
export type PinType = $Shape<{|
  +status?: PinStatusType | null,
  +availability?: PinAvailabilityType | null,
  ...
|}> ...

Feedback welcome

As we would like to provide the most optimal integration that would suit most of the needs, with this issue we would like to start gathering feedback from the community about our current approach and were we should heading to.

cc @rahul-malik @bkase @chrislloyd @bradencanderson

Fix compilation issues in `dictionaryObjectRepresentation`

This method was added but has some schema combinations that actually have preventing it from compiling. The scenario I found that errored was a property of type Map<String,Array<String>>

We should also add a integration test case that is exhaustive of all schema types to prevent this from happening

Problems updating to 1.3 - Homebrew

I don't know if its plank fault or homebrew but I can't update to version 1.3

14:19 $ brew install plank
Updating Homebrew...
Error: plank 1.2 is already installed
To upgrade to 1.3, run brew upgrade plank
✘-1 ~
14:24 $ brew upgrade plank
Error: pinterest/tap/plank 1.2 already installed

Support for id Properties

I'd like to use plank for generating models for use inside an app i.e. no JSON parsing, and it'd be great to be able to specify @property id myThing and @property UIColor *myColor.

Is anyone else interesting in this capability?

Support validation enum of properties.

If I have a schema with the following properties:

{
    "action_type": {
        "type": "integer",
        "enum": [
            { "default": 1, "description": "foobar" }
        ]
    }
}

And a model is attempting to be generated with the following dictionary:

{
    "action_type": 2
}

(i.e. one where the value that is attempting to be generated is not contained within the values the properties define)

I would expect something to be logged and for the model to not be generated.

Bridging Integration Tests

Now that #67 landed we should be able to bridge between Objc and Flow. We should add integration tests that test this bridging and if we add more languages in the future extend this tests.

Plank generating conflicting enum name

In the Buffer API for profiles we have both a service and a service_type. When we add these to our scheme as String Enums we only get BFRProfileServiceType outputted as all enums get the suffix 'Type'.

From our side I think we'd be happy with BFRProfileService as the name and have 'Type' dropped which would resolve the conflict.

func enumTypeName(propertyName: String, className: String) -> String {

Support null default values in enumerations

Currently we define a description and default value for each entry in the string enum specification. The entries default value should support "null" in order to allow the caller to specify the overall default enum value.

Collaborate with quicktype?

Hi! I have a similar project and am currently working on Objective-C support. Check it out: https://github.com/quicktype/quicktype

We have automated tests, JSON inference in addition to Schema, support for GraphQL queries, an Xcode extension in the App Store, a cross-platform CLI, a web app, and support for 8 output languages.

I would love to find a way to collaborate on at least Objective-C and Swift if you're interested. We use the same license as you, but are implemented in TypeScript.

Add a commandline argument to dump cache state (atomically to files) and read cache state

EDIT: A simpler solution would be to investigate fixes for the slowness that caused us to want a cache in the first place (i.e. reduce unnecessary writes)


Right now Pinterest's Bazel setup for plank makes two targets. One for headers and one for sources generated from plank.

We do this because invoking plank with many schemas at once speeds up the generation of the sources by an order of magnitude (due to the way we cache and reuse schemas we parse).

Since we treat all pieces as one target, we have to recompile every single generated model when one of them changes (this is the slow part).

If there were some way to dump cache state to a file and read it from a file, then we could invoke plank in several build targets with several artifacts that are hashed and cached separately.

It's important that this "shared cache file" (maybe it's an mmap-ed hashtable) be thread safe since several plank invocations could run concurrently.

This change would save around 30s per incremental build on a plank change

Problem with parsing String to Date

Hi, I tried parsing a string with a data to a NSDate object but it didn't work, my property always ended being nil. I believe its probably a mistake on my part, could you provide an example of it?

I did it as suggested below.
Date Parsing: Due to the variance of possible date formats, NSDate or DateTime objects are created using an instance of NSValueTransformer. It is up to the host application to register an instance of NSValueTransformer for the key kPINModelDateValueTransformerKey.

Grouping of multiple models for ADT

Let's say I have a set of N models, and they all have optional arrays that all can contain the same types of models. Right now I have to duplicate all of the oneOf declarations in each schema, and duplicate the matching code for each of the N models.

I wish there was a way to group multiple models for ADT, then we can also keep all the logic for serializing/matching these models in one place to remove the duped code.

Support for Copying/MutableCopying

It would be nice for Builders and objects both to conform to NSCopying and NSMutableCopying. -copy would return the corresponding model type, and -mutableCopy would return the corresponding Builder type. Could allow for a ton of flexibility!

Assigning value to an iVar with a `copy`

Hi!

Just going over the tutorial, and had a question. In the initWithModelDictionary: method which created, if a property is defined as copy, doesn’t the copy method should be called on the variable when initialized?

For example, in the tutorial, the user object has a firstName property, which is defined as copy; but the relevant line in the aforementioned method is self->_username = value;. Doesn’t it need to be self->_username = [value copy];?

Unify ADT and Model class structs via a protocol

The Model and ADT classes need to share a lot of logic for things like hash and isEqual. Rather than duplicating this code we should be able to share a lot of the logic using protocols and protocol extensions.

Here is the plan:

  • Create a protocol to represent the basic inputs and render method
  • Migrate existing extensions on ObjCRootsRender to extend off the protocol itself.

Serialisation to JSON

Is there possibility to serialise generated object to JSON? We can do initWithDictionary, but what about to have something like dictionary method, which will return dictionary to be send as JSON via REST?

Add tests for generated models

We should add tests for our generated models to make changes way more safer and see immediately if we break something.

We should look into adding the tests to the Example/ folder and we could swift test for that project.

I ran into the issue in #67 as I wanted to be sure that the model dictionary is the same as calling dictionaryRepresentation on it. The tests could look like something like that:

NSDictionary *imageModelDictionary = @{
    @"height" : @(12),
    @"width" : @(11),
    @"url" : @"http://google.com"
};
Image *image = [[Image alloc] initWithModelDictionary:imageModelDictionary];
XCTAssert([imageModelDictionary isEqualToDictionary:[image dictionaryRepresentation]], @"Should be the same");


NSDictionary *userModelDictionary = @{
    @"id" : @(123),
    @"first_name" : @"Michael",
    @"last_name" : @"Schneider",
    @"image" : imageModelDictionary,
};
User *user = [[User alloc] initWithModelDictionary:userModelDictionary];
XCTAssert([userModelDictionary isEqualToDictionary:[user dictionaryRepresentation]], @"Should be the same");
var imageModelDictionary: [AnyHashable: Any] = ["height": (12), "width": (11), "url": "http://google.com"]
var image = Image(modelDictionary: imageModelDictionary)
XCTAssert(imageModelDictionary.isEqual(to: image.dictionaryRepresentation), "Image dictionary representation should be the same as the model dictionary.")
var userModelDictionary: [AnyHashable: Any] = ["id": (123), "first_name": "Michael", "last_name": "Schneider", "image": imageModelDictionary]
var user = User(modelDictionary: userModelDictionary)
XCTAssert(userModelDictionary.isEqual(to: user.dictionaryRepresentation), "User dictionary representation should be the same as the model dictionary")

Update Plank to Swift 3.1

There are a number of minor errors that need to be resolved for compatibility but since 3.1 is GA we should migrate to the latest version and update our .swiftenv configuration

Doubt about mutating models

In my current application I need to mutate some of my models eventually, but I'm not entirely sure if I'm doing it correctly, two examples:

Remove a single object from an array inside my object:

self.object = [self.object copyWithBlock:^(THFeedBuilder * _Nonnull builder) {
                    NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:self.object.array];
                    [mutableArray removeObject:item];
                    builder.array = mutableArray;
                }];

Change a bool value from a complex model:

self.object = [self.object copyWithBlock:^(THPostBuilder * _Nonnull builder) {
            builder.object2 = [builder.object2 copyWithBlock:^(THPostpictureobjectBuilder * _Nonnull builder) {
                builder.object3 = [builder.object3 copyWithBlock:^(THPostpictureBuilder * _Nonnull builder) {
                    builder.bool = YES;
                }];
            }];
        }];

Also I'm doing self.object = [self.object copy... instead of creating a new object because I need to modify a class property.

default property doesn't get renamed causing error

We have a boolean called default from one of our API responses, this doesn't seem to get renamed in the same way as identifier or description_text does.

Confirmed that default is included in objectiveCReservedWords.

Not sure if I'm missing a trick to rename etc.

Generate validation logic for Dictionary responses

We recently had a crash where the type information validation could have prevented a serious high volume crash. while this shouldn't happen in practice it might be valuable to validate in-line in initWithDictionary or having a separate validation method entirely.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.