Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions source/JSONSchema-Core-Tests/JSONSchemaTests.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,31 @@ JSONSchemaTests >> testSchemaReadArrayMultipleItems [
self assert: object foo third equals: (DateAndTime year: 2019 month: 10 day: 9 hour: 13 minute: 15 second: 8 offset: 1 hour).
]

{ #category : 'tests' }
JSONSchemaTests >> testSchemaReadArrayPropertyThatIsNull [
"A schema saying a property is an array does not forbid the actual
value being JSON null - readString: must not crash trying to parse
null as a list."
| schema object |
schema := {
#foo -> (JSONSchemaArray new items: JSONSchema string).
} asJSONSchema.
object := schema readString: '{"foo":null}'.
self assert: object foo equals: nil
]

{ #category : 'tests' }
JSONSchemaTests >> testSchemaReadArrayPropertyThatIsWrongType [
"A literal true/false where an array was expected is invalid JSON for
this schema and should raise a clear type error, not an obscure parser
error from trying to read it as a list."
| schema |
schema := {
#foo -> (JSONSchemaArray new items: JSONSchema string).
} asJSONSchema.
self should: [ schema readString: '{"foo":true}' ] raise: JSONTypeError
]

{ #category : 'tests' }
JSONSchemaTests >> testSchemaReadNested [

Expand All @@ -75,6 +100,17 @@ JSONSchemaTests >> testSchemaReadNested [

]

{ #category : 'tests' }
JSONSchemaTests >> testSchemaReadNestedObjectPropertyThatIsNull [
"Same as above for an object-typed property."
| schema object |
schema := {
#bar -> { #one -> JSONSchema string }
} asJSONSchema.
object := schema readString: '{"bar":null}'.
self assert: object bar equals: nil
]

{ #category : 'tests' }
JSONSchemaTests >> testSchemaWriteArray [

Expand Down
18 changes: 18 additions & 0 deletions source/JSONSchema-Core/JSONSchema.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,24 @@ JSONSchema >> neoJsonOn: jsonWriter [

]

{ #category : 'private' }
JSONSchema >> peekNullThenReadUsing: aReader ifNotNull: aBlock [
"Both object- and array-typed schemas hand off straight to a structural
parse (parseMapKeysDo:/parseListDo:), which expects { or [ and has no
concept of null - but any OpenAPI/JSON-Schema property can legitimately
be null regardless of its declared type. Peek for a null/true/false
literal first (parseConstantDo: does not consume the stream if there is
no match) before committing to the structural read."
| matched value |
matched := false.
value := nil.
aReader parseConstantDo: [ :v | matched := true. value := v ].
(matched and: [ value isNil ]) ifTrue: [ ^ nil ].
matched ifTrue: [
^ JSONTypeError signal: self class typeName, ' cannot be ', value printString ].
^ aBlock value
]

{ #category : 'instance creation' }
JSONSchema >> readFrom: aStream [
^ self readUsing: (NeoJSONReader on: aStream)
Expand Down
9 changes: 5 additions & 4 deletions source/JSONSchema-Core/JSONSchemaArray.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,16 @@ JSONSchemaArray >> read: aString object: object [

{ #category : 'private' }
JSONSchemaArray >> readUsing: aReader [
| index |
index := 0.
^ Array streamContents: [:stream |
^ self peekNullThenReadUsing: aReader ifNotNull: [
| index |
index := 0.
Array streamContents: [:stream |
aReader parseListDo: [
stream nextPut: (items isCollection
ifTrue: [
index := index + 1.
(items at: index) readUsing: aReader ]
ifFalse: [ items readUsing: aReader ])]]
ifFalse: [ items readUsing: aReader ])]] ]
]

{ #category : 'accessing' }
Expand Down
44 changes: 20 additions & 24 deletions source/JSONSchema-Core/JSONSchemaObject.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -149,27 +149,24 @@ JSONSchemaObject >> read: string object: object [
]

{ #category : 'as yet unclassified' }
JSONSchemaObject >> readKey: key reader: reader [
properties ifNotNil: [
properties
at: key
ifPresent: [ :propertySchema |
"schema for property is existing using it to read the value"
^ propertySchema readUsing: reader ] ].

(additionalProperties isNil | (additionalProperties = true))
ifTrue: [
"if additionProperties is absent or true any additional property
is allowed"
^ reader parseValue ].

(additionalProperties = false)
ifTrue: [
"addtional properties are disallowed"
JSONTypeError signal: 'additional properties are not allowed' ].

"if additionalProperty is present and not a boolean it is a
schema defining the type of the addtional properties"
JSONSchemaObject >> readKey: key reader: reader [
properties ifNotNil: [
properties
at: key
ifPresent: [ :propertySchema |
"schema for property is existing using it to read the value"
^ propertySchema readUsing: reader ] ].
(additionalProperties isNil | (additionalProperties = true))
ifTrue: [
"if additionProperties is absent or true any additional property
is allowed"
^ reader parseValue ].
(additionalProperties = false)
ifTrue: [
"addtional properties are disallowed"
JSONTypeError signal: 'additional properties are not allowed' ].
"if additionalProperty is present and not a boolean it is a
schema defining the type of the addtional properties"
^ additionalProperties readUsing: reader
]

Expand All @@ -194,9 +191,8 @@ JSONSchemaObject >> readString: aString [

{ #category : 'private' }
JSONSchemaObject >> readUsing: reader [
^ self
readUsing: reader
object: self newInstance
^ self peekNullThenReadUsing: reader ifNotNull: [
self readUsing: reader object: self newInstance ]
]

{ #category : 'private' }
Expand Down
Loading