Skip to content

Conversation

@yigit
Copy link

@yigit yigit commented Nov 18, 2025

This PR implements ability to reference application classes from the schema.

A property of type object can now reference an existing class in the codebase that will not be generated by plank but should still conform to the expected protocols.

example usage:

{
    "id": "decorated.json",
    "title": "decorated",
    "description" : "Schema definition of a decorated type",
    "$schema": "http://json-schema.org/schema#",
    "type": "object",
    "properties": {
        "name" : { "type": "string" },
        "externalType": {
            "type": "object",
            "x-external": true,
            "x-typename": "MyCustomClass"
        }
    }
}

When plank compiler finds x-external:true, it will mark the SchemaObjectRoot as external, which will prevent it from trying to generate that model object. An x-typename is required, otherwise, it will fail code generation.

On Java, it will just use the class as if it exists. Unless the class is not in the same package, it needs to be imported using the existing java_decorations feature.

java decorations file:

{
  "imports": [
    "com.pinterest.models.custom.MyCustomClass"
  ]
}

On ObjC, it will just use the class as if it exists. I've implemented ObjC decorations support, similar to Java, which allows specifying swift package imports that will be imported from the generated .m file.

objc decorations file:

{
    "swiftPackageImports": ["MyCustomPackage"]
}

This will generate:

// Decorated.m
@import MyCustomPackage;

In ObjC header, we also need to declare a forward reference for it, which is automatically added if there is a referenced external type. (each of these types are added to the ObjCIR.Root enumaration)

// Decorated.h
@class MyCustomClass;

Lists and Maps
I've not implemented support for external classes and list and map types. It requires a bit more logic when generating gson, hence skipped it for now. We still detect it and fail w/ an explicit error, in case someone tries to use it.

Examples Update
I've added decorated examples to demonstrate both x-external and decorations support. Added a test to the ObjC code to validate serialization-deserialization for the type with external data.

Other relevant but misc changes:

  • The current bazel version does not support arm. I've updated the bazel version, removed the bazel binary in favor of using bazelisk.
  • Re-generated the examples. The changes in examples (for existing cases) are not from this PR, seems like we forgot to regenerate them in a previos class.
    • Similarly, there is a failing test for polymorphics, that is also failing before my changes. I've just disabled it for now.

iOS External Class Requirements
External classes should implement the following protocols:

* NSObject
* NSSecureCoding

External classes should implement the following functions:

// initialize the object with the given NSDictionary
@objc public required init(modelDictionary: NSDictionary)
@objc(modelObjectWithDictionary:) public class func modelObject(with dictionary: NSDictionary) -> Self?
@objc(modelObjectWithDictionary:error:) public class func modelObject(with dictionary: NSDictionary) throws -> Self

// convert the object into NSDictionary
@objc(dictionaryObjectRepresentation) public func dictionaryObjectRepresentation() -> NSMutableDictionary

// merge the object with another copy of it
@objc(mergeWithModel:initType:) public func merge(with model: <ExternalType>, initType: Int) -> <ExternalType>

Android external class requirements
Custom android classes should register a GSON type adapter when deserializing.

MyExternalClassTypeAdapterFactory : com.google.gson.TypeAdapterFactory

They should also implement:
equals & hashcode

@yigit yigit marked this pull request as ready for review November 18, 2025 00:37
"type": "object",
"properties": {
"name" : { "type": "string" },
"externalType": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider renaming to "myCustomClass"

case let .object(schemaRoot):
return [schemaRoot.className(with: self.params)]
if schemaRoot.external {
return []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add comment to indicate that we expect developer to use an import decoration

Copy link
Contributor

@RicoYao RicoYao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good for Android changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants