text: //
  //  OBSMTLJSONAdapter.h
  //  Mantle
  //
  //  Created by Justin Spahr-Summers on 2013-02-12.
  //  Copyright (c) 2013 GitHub. All rights reserved.
  //
  
  #import <Foundation/Foundation.h>
  
  @protocol OBSMTLModel;
  @protocol OBSMTLTransformerErrorHandling;
  
  /// A OBSMTLModel object that supports being parsed from and serialized to JSON.
  @protocol OBSMTLJSONSerializing <OBSMTLModel>
  @required
  
  /// Specifies how to map property keys to different key paths in JSON.
  ///
  /// Subclasses overriding this method should combine their values with those of
  /// `super`.
  ///
  /// Values in the dictionary can either be key paths in the JSON representation
  /// of the receiver or an array of such key paths. If an array is used, the
  /// deserialized value will be a dictionary containing all of the keys in the
  /// array.
  ///
  /// Any keys omitted will not participate in JSON serialization.
  ///
  /// Examples
  ///
  ///     + (NSDictionary *)JSONKeyPathsByPropertyKey {
  ///         return @{
  ///             @"name": @"POI.name",
  ///             @"point": @[ @"latitude", @"longitude" ],
  ///             @"starred": @"starred"
  ///         };
  ///     }
  ///
  /// This will map the `starred` property to `JSONDictionary[@"starred"]`, `name`
  /// to `JSONDictionary[@"POI"][@"name"]` and `point` to a dictionary equivalent
  /// to:
  ///
  ///     @{
  ///         @"latitude": JSONDictionary[@"latitude"],
  ///         @"longitude": JSONDictionary[@"longitude"]
  ///     }
  ///
  /// Returns a dictionary mapping property keys to one or multiple JSON key paths
  /// (as strings or arrays of strings).
  + (NSDictionary *)JSONKeyPathsByPropertyKey;
  
  @optional
  
  /// Specifies how to convert a JSON value to the given property key. If
  /// reversible, the transformer will also be used to convert the property value
  /// back to JSON.
  ///
  /// If the receiver implements a `+<key>JSONTransformer` method, OBSMTLJSONAdapter
  /// will use the result of that method instead.
  ///
  /// Returns a value transformer, or nil if no transformation should be performed.
  + (NSValueTransformer *)JSONTransformerForKey:(NSString *)key;
  
  /// Overridden to parse the receiver as a different class, based on information
  /// in the provided dictionary.
  ///
  /// This is mostly useful for class clusters, where the abstract base class would
  /// be passed into -[OBSMTLJSONAdapter initWithJSONDictionary:modelClass:], but
  /// a subclass should be instantiated instead.
  ///
  /// JSONDictionary - The JSON dictionary that will be parsed.
  ///
  /// Returns the class that should be parsed (which may be the receiver), or nil
  /// to abort parsing (e.g., if the data is invalid).
  + (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary;
  
  @end
  
  /// The domain for errors originating from OBSMTLJSONAdapter.
  extern NSString  *const OBSMTLJSONAdapterErrorDomain;
  
  /// +classForParsingJSONDictionary: returned nil for the given dictionary.
  extern const NSInteger OBSMTLJSONAdapterErrorNoClassFound;
  
  /// The provided JSONDictionary is not valid.
  extern const NSInteger OBSMTLJSONAdapterErrorInvalidJSONDictionary;
  
  /// The model's implementation of +JSONKeyPathsByPropertyKey included a key which
  /// does not actually exist in +propertyKeys.
  extern const NSInteger OBSMTLJSONAdapterErrorInvalidJSONMapping;
  
  /// An exception was thrown and caught.
  extern const NSInteger OBSMTLJSONAdapterErrorExceptionThrown;
  
  /// Associated with the NSException that was caught.
  extern NSString  *const OBSMTLJSONAdapterThrownExceptionErrorKey;
  
  /// Converts a OBSMTLModel object to and from a JSON dictionary.
  @interface OBSMTLJSONAdapter : NSObject
  
  /// Attempts to parse a JSON dictionary into a model object.
  ///
  /// modelClass     - The OBSMTLModel subclass to attempt to parse from the JSON.
  ///                  This class must conform to <OBSMTLJSONSerializing>. This
  ///                  argument must not be nil.
  /// JSONDictionary - A dictionary representing JSON data. This should match the
  ///                  format returned by NSJSONSerialization. If this argument is
  ///                  nil, the method returns nil.
  /// error          - If not NULL, this may be set to an error that occurs during
  ///                  parsing or initializing an instance of `modelClass`.
  ///
  /// Returns an instance of `modelClass` upon success, or nil if a parsing error
  /// occurred.
  + (id)modelOfClass:(Class)modelClass fromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error;
  
  /// Attempts to parse an array of JSON dictionary objects into a model objects
  /// of a specific class.
  ///
  /// modelClass - The OBSMTLModel subclass to attempt to parse from the JSON. This
  ///              class must conform to <OBSMTLJSONSerializing>. This argument must
  ///              not be nil.
  /// JSONArray  - A array of dictionaries representing JSON data. This should
  ///              match the format returned by NSJSONSerialization. If this
  ///              argument is nil, the method returns nil.
  /// error      - If not NULL, this may be set to an error that occurs during
  ///              parsing or initializing an any of the instances of
  ///              `modelClass`.
  ///
  /// Returns an array of `modelClass` instances upon success, or nil if a parsing
  /// error occurred.
  + (NSArray *)modelsOfClass:(Class)modelClass fromJSONArray:(NSArray *)JSONArray error:(NSError **)error;
  
  /// Converts a model into a JSON representation.
  ///
  /// model - The model to use for JSON serialization. This argument must not be
  ///         nil.
  /// error - If not NULL, this may be set to an error that occurs during
  ///         serializing.
  ///
  /// Returns a JSON dictionary, or nil if a serialization error occurred.
  + (NSDictionary *)JSONDictionaryFromModel:(id<OBSMTLJSONSerializing>)model error:(NSError **)error;
  
  /// Converts a array of models into a JSON representation.
  ///
  /// models - The array of models to use for JSON serialization. This argument
  ///          must not be nil.
  /// error  - If not NULL, this may be set to an error that occurs during
  ///          serializing.
  ///
  /// Returns a JSON array, or nil if a serialization error occurred for any
  /// model.
  + (NSArray *)JSONArrayFromModels:(NSArray *)models error:(NSError **)error;
  
  /// Initializes the receiver with a given model class.
  ///
  /// modelClass - The OBSMTLModel subclass to attempt to parse from the JSON and
  ///              back. This class must conform to <OBSMTLJSONSerializing>. This
  ///              argument must not be nil.
  ///
  /// Returns an initialized adapter.
  - (id)initWithModelClass:(Class)modelClass;
  
  /// Deserializes a model from a JSON dictionary.
  ///
  /// The adapter will call -validate: on the model and consider it an error if the
  /// validation fails.
  ///
  /// JSONDictionary - A dictionary representing JSON data. This should match the
  ///                  format returned by NSJSONSerialization. This argument must
  ///                  not be nil.
  /// error          - If not NULL, this may be set to an error that occurs during
  ///                  deserializing or validation.
  ///
  /// Returns a model object, or nil if a deserialization error occurred or the
  /// model did not validate successfully.
  - (id)modelFromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error;
  
  /// Serializes a model into JSON.
  ///
  /// model - The model to use for JSON serialization. This argument must not be
  ///         nil.
  /// error - If not NULL, this may be set to an error that occurs during
  ///         serializing.
  ///
  /// Returns a model object, or nil if a serialization error occurred.
  - (NSDictionary *)JSONDictionaryFromModel:(id<OBSMTLJSONSerializing>)model error:(NSError **)error;
  
  /// Filters the property keys used to serialize a given model.
  ///
  /// propertyKeys - The property keys for which `model` provides a mapping.
  /// model        - The model being serialized.
  ///
  /// Subclasses may override this method to determine which property keys should
  /// be used when serializing `model`. For instance, this method can be used to
  /// create more efficient updates of server-side resources.
  ///
  /// The default implementation simply returns `propertyKeys`.
  ///
  /// Returns a subset of propertyKeys that should be serialized for a given
  /// model.
  - (NSSet *)serializablePropertyKeys:(NSSet *)propertyKeys forModel:(id<OBSMTLJSONSerializing>)model;
  
  /// An optional value transformer that should be used for properties of the given
  /// class.
  ///
  /// A value transformer returned by the model's +JSONTransformerForKey: method
  /// is given precedence over the one returned by this method.
  ///
  /// The default implementation invokes `+<class>JSONTransformer` on the
  /// receiver if it's implemented. It supports NSURL conversion through
  /// +NSURLJSONTransformer.
  ///
  /// modelClass - The class of the property to serialize. This property must not be
  ///              nil.
  ///
  /// Returns a value transformer or nil if no transformation should be used.
  + (NSValueTransformer *)transformerForModelPropertiesOfClass:(Class)modelClass;
  
  /// A value transformer that should be used for a properties of the given
  /// primitive type.
  ///
  /// If `objCType` matches @encode(id), the value transformer returned by
  /// +transformerForModelPropertiesOfClass: is used instead.
  ///
  /// The default implementation transforms properties that match @encode(BOOL)
  /// using the OBSMTLBooleanValueTransformerName transformer.
  ///
  /// objCType - The type encoding for the value of this property. This is the type
  ///            as it would be returned by the @encode() directive.
  ///
  /// Returns a value transformer or nil if no transformation should be used.
  + (NSValueTransformer *)transformerForModelPropertiesOfObjCType:(const char *)objCType;
  
  @end
  
  @interface OBSMTLJSONAdapter (ValueTransformers)
  
  /// Creates a reversible transformer to convert a JSON dictionary into a OBSMTLModel
  /// object, and vice-versa.
  ///
  /// modelClass - The OBSMTLModel subclass to attempt to parse from the JSON. This
  ///              class must conform to <OBSMTLJSONSerializing>. This argument must
  ///              not be nil.
  ///
  /// Returns a reversible transformer which uses the class of the receiver for
  /// transforming values back and forth.
  + (NSValueTransformer<OBSMTLTransformerErrorHandling> *)dictionaryTransformerWithModelClass:(Class)modelClass;
  
  /// Creates a reversible transformer to convert an array of JSON dictionaries
  /// into an array of OBSMTLModel objects, and vice-versa.
  ///
  /// modelClass - The OBSMTLModel subclass to attempt to parse from each JSON
  ///              dictionary. This class must conform to <OBSMTLJSONSerializing>.
  ///              This argument must not be nil.
  ///
  /// Returns a reversible transformer which uses the class of the receiver for
  /// transforming array elements back and forth.
  + (NSValueTransformer<OBSMTLTransformerErrorHandling> *)arrayTransformerWithModelClass:(Class)modelClass;
  
  /// This value transformer is used by OBSMTLJSONAdapter to automatically convert
  /// NSURL properties to JSON strings and vice versa.
  + (NSValueTransformer *)NSURLJSONTransformer;
  
  /// This value transformer is used by OBSMTLJSONAdapter to automatically convert
  /// NSUUID properties to JSON strings and vice versa.
  + (NSValueTransformer *)NSUUIDJSONTransformer;
  
  @end
  
  @class OBSMTLModel;
  
  @interface OBSMTLJSONAdapter (Deprecated)
  
  @property (nonatomic, strong, readonly) id<OBSMTLJSONSerializing> model __attribute__((unavailable("Replaced by -modelFromJSONDictionary:error:")));
  
  + (NSArray *)JSONArrayFromModels:(NSArray *)models __attribute__((deprecated("Replaced by +JSONArrayFromModels:error:"))) NS_SWIFT_UNAVAILABLE("Replaced by +JSONArrayFromModels:error:");
  
  + (NSDictionary *)JSONDictionaryFromModel:(OBSMTLModel<OBSMTLJSONSerializing> *)model __attribute__((deprecated("Replaced by +JSONDictionaryFromModel:error:"))) NS_SWIFT_UNAVAILABLE("Replaced by +JSONDictionaryFromModel:error:");
  
  - (NSDictionary *)JSONDictionary __attribute__((unavailable("Replaced by -JSONDictionaryFromModel:error:"))) NS_SWIFT_UNAVAILABLE("Replaced by -JSONDictionaryFromModel:error:");
  - (NSString *)JSONKeyPathForPropertyKey:(NSString *)key __attribute__((unavailable("Replaced by -serializablePropertyKeys:forModel:")));
  - (id)initWithJSONDictionary:(NSDictionary *)JSONDictionary modelClass:(Class)modelClass error:(NSError **)error __attribute__((unavailable("Replaced by -initWithModelClass:")));
  - (id)initWithModel:(id<OBSMTLJSONSerializing>)model __attribute__((unavailable("Replaced by -initWithModelClass:"))) NS_SWIFT_UNAVAILABLE("Replaced by -initWithModelClass:");
  - (NSDictionary *)serializeToJSONDictionary:(NSError **)error __attribute__((unavailable("Replaced by -JSONDictionaryFromModel:error:"))) NS_SWIFT_UNAVAILABLE("Replaced by -JSONDictionaryFromModel:error:");
  
  @end
  