JSONOneOf

Specify a record that needs to be serialized or deserialized.

Syntax

JSONOneOf is an optional attribute.

Usage

You can set JSONOneOf on a record, elements of a record, or a user-defined type to specify elements that need to be serialized or deserialized. For example, if you have complex data or data that is flexible in how it may be represented, then you can define the data with a number of schemas and JSONOneOf to validate against exactly one of the schemas at runtime.

There are considerations for working with primitive types in schemas to avoid ambiguity in the oneOf validation. When defining schemas for JSONOneOf, keep in mind what happens with the conversion of BDL types to JSON types. For more information on conversion mapping for Genero BDL data types to JSON, go to OpenAPI types mapping: BDL.

The deserialization error -15807 is returned at runtime if there are more than one of the following types in the schema:

  • STRING
  • STRING format: DATE/DATETIME
  • BOOLEAN
  • INTEGER
  • NUMBER

In this example there is an INTEGER and a DECIMAL, but the JSON schema will convert DECIMAL to NUMBER; therefore, this schema is allowed.

PUBLIC TYPE integerNumber RECORD ATTRIBUTE(JSONOneOf)
    _selector SMALLINT ATTRIBUTE(JSONSelector),
    i INTEGER,
    fl DECIMAL
END RECORD
However, in this next example, the type will cause the deserialization runtime error because there are two string types:
PUBLIC TYPE primType RECORD ATTRIBUTE(JSONOneOf)
    _selector SMALLINT ATTRIBUTE(JSONSelector),
    a STRING,
    b STRING
END RECORD
The type in the next example will also cause the deserialization runtime error because the JSON schema will convert all the Genero BDL data types, INTEGER, TINYINT, SMALLINT, and BIGINT, to INTEGER.
PUBLIC TYPE primNumberType RECORD ATTRIBUTE(JSONOneOf)
    _selector SMALLINT ATTRIBUTE(JSONSelector),
    i INTEGER,
    ty TINYINT,
    si SMALLINT,
    i2 BIGINT
END RECORD
In this example, the DATETIME YEAR TO SECOND is converted to a STRING with a specific format in JSON (format: date-time); therefore, this type is allowed. However, if both STRING values are valid for JSONOneOf, the priority is given to the DATETIME YEAR TO SECOND value. A special rule is implemented to resolve this conflict. For details, go to Special rules
PUBLIC TYPE stringDatetime RECORD ATTRIBUTE(JSONOneOf)
    _selector SMALLINT ATTRIBUTE(JSONSelector),
    a STRING,
    da DATETIME YEAR TO SECOND
END RECORD

Special rules

In oneOf validation , special rules have to be applied to resolve potential conflicts involving the STRING/DATE/DATETIME and INTEGER/NUMBER types. This occurs when the value sent is valid for both types and the JSON schema cannot resolve oneOf. To resolve this conflict, a priority selection applies in the following cases:
  • If there is a valid STRING and a DATE/DATETIME, the DATE/DATETIME will have priority.
  • If there is a valid INTEGER and a NUMBER, the INTEGER will have priority.
To understand how the priority of DATE/DATETIME over STRING is applied with different values, study the examples shown in Table 1, Table 2, and Table 3.

To understand how the priority of INTEGER over NUMBER is applied with different values, study the examples shown in Table 4, Table 5, and Table 6

Before applying the priority rule (shown in the Priority column), deserialization for the value is checked first, and this is shown in the column Deserialization. The Result column shows the resolution applied.

Table 1. Priority datetime/string with value "2022-10-31T09:00:00.594Z"
Type Deserialization Priority Result
DATETIME YES YES DATETIME
STRING YES NO
Table 2. Priority date/string with value "2022-10-31T09:00:00.594Z"
Type Deserialization Priority Result
DATE NO YES STRING
STRING YES NO
Table 3. Priority datetime/string with value "foo"
Type Deserialization Priority Result
DATETIME NO YES STRING
STRING YES NO
Table 4. Priority integer/number with value: 23
Type Deserialization Priority Result
INTEGER YES YES INTEGER
NUMBER YES NO
Table 5. Priority integer/number with value: 23.0
Type Deserialization Priority Result
INTEGER YES YES INTEGER
NUMBER YES NO
Table 6. Priority integer/number with value: 23.4
Type Deserialization Priority Result
INTEGER NO YES NUMBER
NUMBER YES NO

The JSONOneOf attribute represents the oneOf keyword in the JSON schema property of the Swagger and OpenAPI specification.

The JSONOneOf attribute must be used in combination with the JSONSelector attribute.

Example 1: using JSONOneOf

In this example, the JSONOneOf attribute is set on the input record of the CreateAccount function. At a request to the service, you can choose which member of the createAccountType record – id or name – to use by setting the member of the input record with the JSONSelector attribute, _selector in our example, to the index value of the record member.

For example, in a request from your client application, you might have a statement like this: LET in._SELECTOR=2.

In the CreateAccount function, the CASE statement tests for the _selector value.

IMPORT COM

PUBLIC TYPE accountType RECORD
    id INTEGER,
    name STRING,
    date DATETIME YEAR TO SECOND,
    age INTEGER,
    gender BOOLEAN
END RECORD

PRIVATE DEFINE accounts DYNAMIC ARRAY OF accountType

PUBLIC DEFINE accountError RECORD ATTRIBUTE(WSError = 'account service error')
    id INTEGER,
    msg STRING
END RECORD

TYPE createAccountType RECORD ATTRIBUTE(JSONOneOf)
    _selector INTEGER ATTRIBUTE(JSONSelector),
    id INTEGER,
    name STRING
END RECORD

FUNCTION CreateAccount(
    in createAccountType)
    ATTRIBUTE(WSPost, WSPath = "/createaccount")
    RETURNS(createAccountType)
    DEFINE out createAccountType
    DEFINE idx INTEGER
    LET out = in

    CASE
        WHEN in._selector = 1
            LET idx = accounts.search("id", in.id)
            IF idx > 0 THEN
                # raise RESTError with accountError
            ELSE
              CALL accounts.appendElement()
              LET accounts[accounts.getLength()].id = in.id
            END IF
        WHEN in._selector = 2
            LET idx = accounts.search("name", in.name)
            # ... function code ... 
    END CASE

    RETURN out
END FUNCTION

In the OpenAPI documentation, the GWS engine exposes the oneOf property in the schema for the record elements defined with JSONOneOf. In the following sample schema, a request to the operation to create an account can choose to send the record for the id or name.

The output shown is from the Firefox® browser, which converts JSON to human readable format. The output may vary depending on your browser.
Figure: Sample JSON schema with oneOf property

Image from the OpenAPI document showing the "one of'"JSON schema property

Example 2: using JSONOneOf with JSONRequired

In this example, the use of the JSONRequired attribute in addition to the JSONOneOf attribute is important to validate the schemas. In the complexType record, there are three sub-records. Each sub-record also has a member set with the JSONRequired attribute. The effect of this required member is that it allows the schemas to be validated.

In JSON schema, there is a default to allowing additional properties – properties that do not correspond to any property name – which has the effect of allowing schemas to be validated with any properties. Therefore, in our example (with JSONOneOf alone) no schema could be validated, since they would all be valid. For an example using JSONOneOf and JSONAdditionalProperties instead of JSONRequired, go to JSONAdditionalProperties.

With JSONRequired in the validation process, the JSON schema checks that the required property has been received; otherwise, the schema will be invalid and will be eliminated. In the end, according to the oneOf requirement, there must remain only one valid schema in all the possible schemas.

For more information on properties and additional properties, go to the JSON Schema (external link) object page and search for additional properties.

# In this example, a property with attribute JSONRequired is mandatory 
# Otherwise, all schemas will be valid and oneOf can only accept one valid schema 
TYPE complexType RECORD ATTRIBUTE(JSONOneOf)     
        _selector INTEGER ATTRIBUTE(JSONSelector),
    accountById RECORD
        id INTEGER ATTRIBUTE(JSONRequired),
        birthday DATE,
        gender BOOLEAN
    END RECORD,
    accountByname RECORD
        name STRING ATTRIBUTE(JSONRequired),
        birthday DATE,
        gender BOOLEAN
    END RECORD,
    accountBydate RECORD
        date DATETIME YEAR TO SECOND ATTRIBUTE(JSONRequired),
        birthday DATE,
        gender BOOLEAN
    END RECORD
END RECORD


FUNCTION echoComplexType(
    in complexType)
    ATTRIBUTE(WSPost, WSPath = "/echoComplexType")
    RETURNS(complexType)
    DEFINE out complexType
    
    LET out = in
    DISPLAY out._selector

    CASE
        WHEN out._selector == 2
            DISPLAY "This is the schema 1 with id required property: ",
                out.accountById.id

        WHEN out._selector == 3
            DISPLAY "This is the schema 2 with STRING value: ",
                out.accountByName.name

        WHEN out._selector == 4
            DISPLAY "This is the schema 3 with DATE value: ",
                out.accountBydate.date
    END CASE

    RETURN out
END FUNCTION

In the OpenAPI documentation, the GWS creates the JSON schema with oneOf and required properties.

The output shown is from the Firefox browser, which converts JSON to human readable format. The output may vary depending on your browser.
Figure: Sample JSON schema with oneOf and required properties

Image from the OpenAPI document showing the "one of'" and required JSON schema properties