Property Schemas

Each entry of a document type's properties is a property schema: JSON Schema (draft 2020-12), limited to the keywords in this chapter, plus three Platform keywords that say how a value is stored (position, byteArray and contentMediaType). The schema is checked when the contract is registered, and every created or replaced document is validated against it. Platform keywords with more to them, such as maxBytes, refersTo, distinctFrom, encryptedFor, generatedFrom, requiredSince and a typed array's items, have chapters of their own.

KeywordApplies toOn update
typeevery propertyFixed
positionevery propertyFixed
minLength, maxLength, pattern, formatstringsLoosened or removed only
minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOfintegers and numbersLoosened or removed only, keeping an integer's width; multipleOf fixed
enum, constanyenum may gain values; const may be removed
byteArray, contentMediaTypebyte arraysFixed
minItems, maxItems, uniqueItems, containsarraysLoosened or removed only; contains fixed
properties, required, additionalProperties, minProperties, maxProperties, dependentRequiredobjectsMembers may be added; the rest fixed, except dependentRequired may lose entries
$refanyFixed
$id, $comment, description, examplesanyFree ($id may only be added)

Each section gives the error a contract update gets for breaking its rules. Most are refused with IncompatibleDocumentTypeSchemaError (10246), from the comparison of the old and new schemas; a change to how a value is stored is refused with DocumentTypeUpdateError (40212).

How property schemas are checked

When a contract is registered or updated:

  • The schema is checked against the document meta-schema. A keyword the meta-schema does not allow where it is written, or a value of the wrong shape, is refused with JsonSchemaError (10101).
  • The parser reads each property's type and bounds, and refuses what the meta-schema cannot express (InvalidContractStructure, 10231).
  • The schema is compiled for validating documents. A pattern that is not a valid regular expression, or a format the validator does not know, is refused here (JsonSchemaError, 10101).

When a document is created or replaced:

  1. Every string and byte array value is at most 5120 bytes, whatever the schema allows (DocumentFieldMaxSizeExceededError, 10417). From protocol version 14 a value nested more than 256 levels deep is refused as well (ValueError, 10103).
  2. The document's properties are validated against the schema. Each failure is reported as a JsonSchemaError (10101) naming the keyword and the property.
  3. Platform's own checks, which JSON Schema cannot express, come next: maxBytes and propertyConstraints among them.

A transfer, a price update and a purchase carry no property values, so they are not validated against the schema again.

The schema also decides how each value is stored, since a stored document holds no property names or type tags:

PropertyStored as
integer1, 2, 4 or 8 bytes, chosen by its bounds (see Numbers)
number8 bytes, a 64-bit floating point number
boolean1 byte
stringa length prefix, then the UTF-8 bytes
byte array with minItems equal to maxItemsthe bytes, with no prefix
any other byte arraya length prefix, then the bytes
identifier32 bytes
objecta length prefix, then its members
typed arrayan element count, then the elements (see Typed Arrays)

An optional property adds one byte in front that says whether it is present. See Document Serialization for the exact encoding.

type

WhereEvery property; also the elements of a typed array
ValueOne of "string", "integer", "number", "boolean", "object", "array"
Sinceprotocol version 1
On updateFixed (IncompatibleDocumentTypeSchemaError, 10246)
ErrorsJsonSchemaError (10101): a document value of another type

type is a single name. A list of types, and "null", are refused at registration: every stored value needs one known encoding.

An array is one of two things:

  • a byte array, with byteArray: true: a string of bytes, such as a hash or an identifier;
  • from protocol version 14, a typed array, with an items schema: a list of values of one scalar type. See Typed Arrays.

An array that is neither is refused.

position

WhereEvery property, at every level. Not on the elements of a typed array.
ValueAn integer, 0 or more
Sinceprotocol version 1
On updateFixed (10246)
ErrorsMissingPositionsInDocumentTypePropertiesError (10411) at registration

A document is stored with its values one after another and no property names. position is the property's place in that sequence.

  • The top-level properties of a document type must use the positions 0, 1, 2 and so on, with no gap and no number used twice (10411). A top-level property without a position is refused.
  • The members of an object need a position too. Number them from 0 within the object; only the top level is checked for gaps.
  • A property that takes its schema from a $ref writes its position next to the $ref.
  • A property added by a contract update takes the next free position. An existing position can never change, or stored documents would be read in the wrong order.

Strings

KeywordsminLength, maxLength, pattern, format
WhereProperties of type string, and the string elements of a typed array
ValueminLength, maxLength: an integer, 0 or more, counting characters. pattern: a regular expression. format: the name of a JSON Schema format, such as "uri"
Sinceprotocol version 1
On updatemaxLength may be raised or removed, and minLength lowered or removed. pattern and format may be removed. None of them may be added, and no other change is allowed (10246).
ErrorsJsonSchemaError (10101)
"username": {
  "type": "string",
  "minLength": 3,
  "maxLength": 63,
  "pattern": "^[a-zA-Z0-9_]+$",
  "position": 0
}

A username is 3 to 63 letters, digits or underscores.

  • minLength and maxLength count characters, and a character takes 1 to 4 bytes in UTF-8. To cap the stored size, add maxBytes. Whatever maxLength says, no single string may exceed 5120 bytes (10417).
  • pattern is written in the syntax of Rust's regex crate, which has no lookaround and no backreferences. A pattern that does not compile is refused at registration (10101).
  • format is checked on every document. The validator knows date-time, date, time, email, idn-email, hostname, ipv4, ipv6, uri and regex. Any other format, uuid and uri-reference included, is refused at registration (10101).
  • A string with pattern or format must declare a maxLength of at most 50000, so that matching stays cheap.
  • A string used in an index needs a maxLength of at most 63 (InvalidIndexedPropertyConstraintError, 10205). See Indexes.

Numbers

Keywordsminimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf
WhereProperties of type integer or number, and such elements of a typed array
ValueA number. On an integer element of a typed array, minimum and maximum are integers.
Sinceprotocol version 1
On updatemaximum and exclusiveMaximum may be raised or removed, and minimum and exclusiveMinimum lowered or removed, unless that changes how an integer is stored (DocumentTypeUpdateError, 40212). None may be added, and multipleOf is fixed (10246).
ErrorsJsonSchemaError (10101)
"rating": { "type": "integer", "minimum": 1, "maximum": 5, "position": 2 }

A rating is a whole number from 1 to 5, and is stored in one byte.

A number is always stored in 8 bytes. An integer is stored in the smallest width its minimum and maximum allow, when the contract's config has sizedIntegerTypes on (the default; see Contract-Level Keys and config):

minimummaximumStored as
0 or moreup to 2551 byte, unsigned
0 or moreup to 655352 bytes, unsigned
0 or moreup to 42949672954 bytes, unsigned
0 or morehigher8 bytes, unsigned
below 0both bounds within -128 to 1271 byte, signed
below 0both bounds within -32768 to 327672 bytes, signed
below 0both bounds within -2147483648 to 21474836474 bytes, signed
below 0otherwise8 bytes, signed

With only a minimum, the integer takes 8 bytes, unsigned when the minimum is 0 or more. With only a maximum, it takes the unsigned width the maximum gives, so an integer that may be negative needs a minimum too. With neither, an enum of integers picks the width from its smallest and largest members; without one, the integer takes 8 bytes, signed. exclusiveMinimum and exclusiveMaximum do not affect the width. With sizedIntegerTypes off, every integer takes 8 bytes, signed.

Stored documents and index entries hold each integer at its width, so a contract update may not change the width or the sign. Raising maximum past the width, lowering minimum below 0, removing a bound, or adding an enum value outside the width is refused with DocumentTypeUpdateError (40212); so is turning sizedIntegerTypes on when it would change an existing integer's width. A change that keeps the width is accepted.

enum and const

WhereAny property. enum also on the string, integer, number and boolean elements of a typed array; const never on elements.
Valueenum: an array of one or more values, none repeated. const: one value.
Sinceprotocol version 1
On updateenum may gain values but not lose one; the keyword may be removed, not added. const may be removed, not added or changed (10246). An enum value that changes an integer's width is refused (DocumentTypeUpdateError, 40212).
ErrorsJsonSchemaError (10101)
"status": { "type": "string", "enum": ["open", "closed", "archived"], "position": 3 }

enum lists the values a property may take and const the one value it must take. Both only narrow what documents may hold, so an update may widen them (more enum values, or no keyword at all) but never narrow them, which could leave stored documents invalid. On an integer without bounds, enum also sets the stored width (see Numbers).

Byte arrays and identifiers

KeywordsbyteArray, contentMediaType
WhereProperties of type array, and the array elements of a typed array
ValuebyteArray: true, the only value. contentMediaType: "application/x.dash.dpp.identifier" makes the byte array an identifier.
Sinceprotocol version 1
On updateFixed (10246). The length bounds follow the rules of Arrays, but a byte array may not switch between a fixed and a variable length, or change its fixed length (DocumentTypeUpdateError, 40212).
ErrorsJsonSchemaError (10101)
"authorId": {
  "type": "array",
  "byteArray": true,
  "minItems": 32,
  "maxItems": 32,
  "contentMediaType": "application/x.dash.dpp.identifier",
  "position": 1
}

authorId is an identifier: the 32-byte id of an identity, a document, a contract or a token.

  • byteArray: true makes an array a string of bytes. Its minItems and maxItems count bytes. When the two are equal the bytes are stored as they are; otherwise they carry a length prefix.
  • contentMediaType: "application/x.dash.dpp.identifier" makes a byte array an identifier. It must come with byteArray: true, minItems: 32 and maxItems: 32, and it may not carry uniqueItems. An identifier is shown in base58, and it is the kind of property distinctFrom and most refersTo targets are declared on.
  • A byte array used in an index needs a maxItems of at most 255 (InvalidIndexedPropertyConstraintError, 10205).
  • On a typed array, contentMediaType belongs on the items, not on the array.

Arrays

KeywordsminItems, maxItems, uniqueItems, contains
WhereProperties of type array. minItems and maxItems also on byte array elements of a typed array.
ValueminItems, maxItems: an integer, 0 or more. uniqueItems: a boolean. contains: a schema.
Sinceprotocol version 1
On updatemaxItems may be raised or removed and minItems lowered or removed (10246), within the byte array rule above (40212); a typed array keeps its maxItems. uniqueItems may be removed or set to false, not added (10246). contains is fixed (10246).
ErrorsJsonSchemaError (10101)
  • minItems and maxItems count bytes on a byte array and elements on a typed array. A typed array must declare maxItems, at most 1024.
  • uniqueItems: true on a typed array refuses a document that repeats an element. On a plain byte array it refuses a repeated byte. It is refused on an identifier, and on the elements of a typed array.
  • contains is the JSON Schema keyword: at least one element must match the schema it holds.

Objects

Keywordsproperties, required, additionalProperties, minProperties, maxProperties, dependentRequired
WhereProperties of type object
ValueThe same as at the top of a document type: see Document Shape
Sinceprotocol version 1
On updateMembers may be added, never removed; required and additionalProperties are fixed; dependentRequired may lose entries, not gain them (10246). minProperties and maxProperties are fixed (10246).
ErrorsJsonSchemaError (10101)
"records": {
  "type": "object",
  "properties": {
    "identity": {
      "type": "array",
      "byteArray": true,
      "minItems": 32,
      "maxItems": 32,
      "contentMediaType": "application/x.dash.dpp.identifier",
      "position": 0
    }
  },
  "minProperties": 1,
  "additionalProperties": false,
  "position": 5
}

An object groups members under one property, as DPNS groups a name's records.

  • An object declares properties, 1 to 100 members each with a position, and additionalProperties: false, unless it takes its schema from a $ref.
  • Its required names the members every value of the object must hold. It cannot change after the type exists, so a member added by an update is optional.
  • An object is stored with its members inline. An object cannot be indexed, but a member can: an index names it by its dotted path, such as records.identity.

$ref

WhereAny property, except the elements of a typed array
Value"#/$defs/<name>": a definition in the contract's schemaDefs
Sinceprotocol version 1
On updateFixed (10246)
ErrorsInvalidJsonSchemaRefError (10207) at registration

$ref lets several document types share one schema, written once in the contract's schemaDefs:

"schemaDefs": {
  "address": {
    "type": "object",
    "properties": {
      "street": { "type": "string", "maxLength": 100, "position": 0 },
      "city": { "type": "string", "maxLength": 50, "position": 1 }
    },
    "required": ["street", "city"],
    "additionalProperties": false
  }
}

A property of any document type of the contract then reads "shippingAddress": { "$ref": "#/$defs/address", "position": 2 }.

  • Only local references, starting with #, are allowed. The platform places schemaDefs under $defs in every document type (see $schema and $defs).
  • A reference that does not resolve, or that leads back to itself, is refused (10207).
  • The property keeps its own position; the definition supplies everything else.
  • The $ref itself cannot change on update. The definition it points at can, under the rules of the keywords it holds (IncompatibleDataContractSchemaError, 10213).

Annotations

Keywords$id, $comment, description, examples
WhereAny property. $comment and description also on the elements of a typed array.
Value$id: a string starting with #. $comment, description: a string. examples: an array of values.
Sinceprotocol version 1
On update$comment, description and examples: free. $id: may be added, not removed or changed (10246).
Errorsnone

Notes for people and tools reading the contract. They do not change what a document may hold.

See also