Skip to content

Commit

Permalink
fixes #134 - throwing DuplicateObjectPropertyException on duplicate j…
Browse files Browse the repository at this point in the history
…son keys
  • Loading branch information
erosb committed Dec 15, 2024
1 parent 085350a commit b7054b1
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 9 deletions.
6 changes: 6 additions & 0 deletions src/main/kotlin/com/github/erosb/jsonsKema/JsonParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,12 @@ class JsonParser private constructor(
walker.skipWhitespaces().consume(":").skipWhitespaces()
val propValue = parseValue()
nestingPath.removeLast()
if (properties.keys.contains(propName)) {
throw DuplicateObjectPropertyException(
properties.keys.find { it.value == propName.value }!!,
propName
)
}
properties.put(propName, propValue)
if (walker.curr() == ',') {
commaCharFound = true
Expand Down
7 changes: 6 additions & 1 deletion src/main/kotlin/com/github/erosb/jsonsKema/JsonValue.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import java.math.BigDecimal
import java.net.URI
import java.util.stream.Collectors.joining

data class JsonParseException(override val message: String, val location: TextLocation) : RuntimeException()
open class JsonParseException(override val message: String, val location: TextLocation) : RuntimeException()

class DuplicateObjectPropertyException(
firstOccurrence: JsonString,
secondOccurrence: JsonString
): JsonParseException("property \"${firstOccurrence.value}\" found at multiple locations in the same object, ${firstOccurrence.location} and ${secondOccurrence.location}", secondOccurrence.location)

data class JsonTypingException(
val expectedType: String,
Expand Down
60 changes: 52 additions & 8 deletions src/test/kotlin/com/github/erosb/jsonsKema/JsonParserTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -105,39 +105,44 @@ class JsonParserTest {
val exception = assertThrows(JsonParseException::class.java) {
JsonParser("nil")()
}
assertEquals(JsonParseException("Unexpected character found: i", TextLocation(1, 2, DEFAULT_BASE_URI)), exception)
assertEquals("Unexpected character found: i", exception.message)
assertEquals(TextLocation(1, 2, DEFAULT_BASE_URI), exception.location)
}

@Test
fun `EOF reached while reading token`() {
val exception = assertThrows(JsonParseException::class.java) {
JsonParser("nu")()
}
assertEquals(JsonParseException("Unexpected EOF", TextLocation(1, 3, DEFAULT_BASE_URI)), exception)
assertEquals("Unexpected EOF", exception.message)
assertEquals(TextLocation(1, 3, DEFAULT_BASE_URI), exception.location)
}

@Test
fun `empty input`() {
val exception = assertThrows(JsonParseException::class.java) {
JsonParser("")()
}
assertEquals(JsonParseException("Unexpected EOF", TextLocation(1, 1, DEFAULT_BASE_URI)), exception)
assertEquals("Unexpected EOF", exception.message)
assertEquals(TextLocation(1, 1, DEFAULT_BASE_URI), exception.location)
}

@Test
fun `missing comma character in object`() {
val exception = assertThrows(JsonParseException::class.java) {
JsonParser("{\"key1\":\"value1\", \"key2\":\"value2\" \"key3\":\"value3\"}\n")()
}
assertEquals(JsonParseException("Unexpected character found: \". Expected ',', '}'", SourceLocation(1, 35, pointer())), exception)
assertEquals("Unexpected character found: \". Expected ',', '}'", exception.message)
assertEquals(SourceLocation(1, 35, pointer()), exception.location)
}

@Test
fun `missing comma character in array`() {
val exception = assertThrows(JsonParseException::class.java) {
JsonParser("[1, 2 3]")()
}
assertEquals(JsonParseException("Unexpected character found: 3. Expected ',', ']'", SourceLocation(1, 7, pointer())), exception)
assertEquals("Unexpected character found: 3. Expected ',', ']'", exception.message)
assertEquals(SourceLocation(1, 7, pointer()), exception.location)
}

@Test
Expand Down Expand Up @@ -168,15 +173,17 @@ class JsonParserTest {
val exception = assertThrows(JsonParseException::class.java) {
JsonParser("\"p\\u022suffix\"")()
}
assertEquals(JsonParseException("invalid unicode sequence: 022s", TextLocation(1, 4, DEFAULT_BASE_URI)), exception)
assertEquals("invalid unicode sequence: 022s", exception.message)
assertEquals(TextLocation(1, 4, DEFAULT_BASE_URI), exception.location)
}

@Test
fun `invalid unicode escape - not enough hex chars`() {
val exception = assertThrows(JsonParseException::class.java) {
JsonParser("\"p\\u022")()
}
assertEquals(JsonParseException("Unexpected EOF", TextLocation(1, 8, DEFAULT_BASE_URI)), exception)
assertEquals("Unexpected EOF", exception.message)
assertEquals(TextLocation(1, 8, DEFAULT_BASE_URI), exception.location)
}

@Test
Expand All @@ -191,7 +198,8 @@ class JsonParserTest {
val exception = assertThrows(JsonParseException::class.java) {
JsonParser("\r\n \"")()
}
assertEquals(JsonParseException("Unexpected EOF", SourceLocation(2, 4, pointer())), exception)
assertEquals("Unexpected EOF", exception.message)
assertEquals(SourceLocation(2, 4, pointer()), exception.location)
}

@Test
Expand Down Expand Up @@ -378,4 +386,40 @@ class JsonParserTest {
.ignoringFieldsOfTypes(SourceLocation::class.java)
.isEqualTo(expected)
}

@Test
fun `duplicate key causes exception`() {
assertThrows(JsonParseException::class.java) {
JsonParser(
"""
{
"a": 2,
"b": 2,
"a": 3
}
""".trimIndent()
)()
}
}

@Test
fun `duplicate key check works only on same nesting level`() {
JsonParser(
"""
{
"a": {
"a": 2,
"b": [ "a" ]
},
"b": 2,
"c": [
"a",
{"a": false},
"a",
[ "a" ]
]
}
""".trimIndent()
)()
}
}

0 comments on commit b7054b1

Please sign in to comment.