<file_sep># User Guide - `json-kotlin`

## Contents

- [Introduction](#introduction)
- [Type Mapping](#type-mapping)
- [Configuration](#configuration)
- [Serialize Kotlin object to String](#serialize-kotlin-object-to-string)
- [Deserialize String to Kotlin object](#deserialize-string-to-kotlin-object)
- [Serialize Kotlin object to `JSONValue`](#serialize-kotlin-object-to-jsonvalue)
- [Deserialize `JSONValue` to Kotlin object](#deserialize-jsonvalue-to-kotlin-object)
- [Exceptions](#exceptions)
- [Further Examples](#further-examples)


## Introduction

`json-kotlin` is a reflection-based library that converts Kotlin objects to and from their JSON representations.

It builds on an earlier Java library [`jsonutil`](https://github.com/pwall567/jsonutil) that parses JSON text into an
internal structure of `JSONValue` nodes, and converts those nodes back into text.
`json-kotlin` can serialize an arbitrary Kotlin (or Java) object into the `JSONValue` node structure ready for output,
and it can deserialize a node structure into an object of a specified (or implied) Kotlin type.

Most users of the library will not need to know about this two-stage process (string to `JSONValue` followed by
`JSONValue` to object, and vice versa) &mdash; first-time users will probably want to start with serializing Kotlin
objects to string and deserializing strings to Kotlin.
More complex uses are described in the sections covering serializing and deserializing to and from `JSONValue`.

As a native Kotlin library, `json-kotlin` will respect the "nullability" of any object being deserialized;
that is to say, it will not deserialize `null` into a `String?`.
It also has built-in support for Kotlin utility classes such as `Sequence` and `Pair`.

## Type mapping

### Custom Serialization and Deserialization

For all classes, including standard classes, the serialization and deserialization processes will first look for a
custom serialization or deserialization function.
That means that custom serializations and deserializations may be specified for any class, even classes with obvious
default representations (like `UUID` or even `Boolean`).

For more information see [Custom Serialization and Deserialization](CUSTOM.md).

### Standard Types

#### Kotlin

`json-kotlin` has built-in support for the following standard Kotlin types:

| Type          | JSON Representation  |
| ------------- | -------------------- |
| String        | string               |
| StringBuilder | string               |
| CharSequence  | string               |
| Char          | string (of length 1) |
| CharArray     | string               |
| Int           | number               |
| Long          | number               |
| Short         | number               |
| Byte          | number               |
| Double        | number               |
| Float         | number               |
| Boolean       | boolean              |
| Array         | array                |
| IntArray      | array                |
| LongArray     | array                |
| ShortArray    | array                |
| ByteArray     | array                |
| DoubleArray   | array                |
| FloatArray    | array                |
| BooleanArray  | array                |
| Collection    | array                |
| Iterable      | array                |
| Iterator      | array                |
| List          | array                |
| ArrayList     | array                |
| LinkedList    | array                |
| Set           | array                |
| HashSet       | array                |
| LinkedHashSet | array                |
| Sequence      | array                |
| Map           | object               |
| HashMapMap    | object               |
| LinkedHashMap | object               |
| Pair          | array (of length 2)  |
| Triple        | array (of length 3)  |
| Enum          | string (using name)  |

#### Java

The library also has built-in support for the following standard Java types:

| Type                           | JSON Representation                                   |
| ------------------------------ | ----------------------------------------------------- |
| java.lang.StringBuffer         | string                                                |
| java.math.BigInteger           | number ([optionally string](#bigintegerstring))       |
| java.math.BigDecimal           | number ([optionally string](#bigdecimalstring))       |
| java.util.Enumeration          | array                                                 |
| java.util.Bitset               | array of bit indices                                  |
| java.util.UUID                 | string                                                |
| java.util.Date                 | string (as yyyy-mm-ddThh:mm:ss.sssZ)                  |
| java.util.Calendar             | string (as yyyy-mm-ddThh:mm:ss.sss&#xB1;hh:mm)        |
| java.sql.Date                  | string (as yyyy-mm-dd)                                |
| java.sql.Time                  | string (as hh:mm:ss)                                  |
| java.sql.Timestamp             | string (as yyyy-mm-dd hh:mm:ss.sss)                   |
| java.time.Instant              | string (as yyyy-mm-ddThh:mm:ss.sssZ)                  |
| java.time.LocalDate            | string (as yyyy-mm-dd)                                |
| java.time.LocalTime            | string (as hh:mm:ss.sss)                              |
| java.time.LocalDateTime        | string (as yyyy-mm-ddThh:mm:ss.sss)                   |
| java.time.OffsetTime           | string (as hh:mm:ss.sss&#xB1;hh:mm)                   |
| java.time.OffsetDateTime       | string (as yyyy-mm-ddThh:mm:ss.sss&#xB1;hh:mm)        |
| java.time.ZonedDateTime        | string (as yyyy-mm-ddThh:mm:ss.sss&#xB1;hh:mm\[name]) |
| java.time.Year                 | string (as yyyy)                                      |
| java.time.YearMonth            | string (as yyyy-mm)                                   |
| java.time.MonthDay             | string (as --mm-dd)                                   |
| java.time.Duration             | string (e.g. PT2M)                                    |
| java.time.Period               | string (e.g. P3M)                                     |
| java.net.URI                   | string                                                |
| java.net.URL                   | string                                                |
| java.util.stream.Stream        | array                                                 |
| java.util.stream.IntStream     | array                                                 |
| java.util.stream.LongStream    | array                                                 |
| java.util.stream.DoubleStream  | array                                                 |

### Other Types

#### Serialization

Objects of other types are serialized as objects, using the public properties of the object as properties of the JSON
object.
In this case, as well as for the collection types, the serialization functions call themselves recursively, to the depth
required to represent all levels of the object.

#### Deserialization

Deserialization of classes other than those listed above is possibly the most complex aspect of the `json-kotlin`
library.

If the JSON is a string and the target class has a constructor that takes a single string parameter, that constructor is
invoked and the resulting object returned (this is the mechanism used internally for classes such as `StringBuilder` and
`URL`, but it may also be employed for user-defined classes).

Otherwise, the JSON must be an object, and the deserialization functions will construct an object of the target class,
following this pseudo-code:
```text
create a shortlist of potential constructors
for each constructor in the target class:
    for each parameter in the constructor (there may be no parameters):
        if there is no property in the JSON object with the same name, and the parameter has no default value,
                    and the parameter is not nullable:
            reject the constructor
    if the constructor has not been rejected add it to the shortlist
if there is no constructor in the shortlist:
    FAILURE - can't construct an object of the target type from the supplied JSON
if there is more than one constructor in the shortlist:
    select the constructor that uses the greatest number of properties from the JSON object
for each parameter in the selected constructor (there may be none in the case of a no-arg constructor):
    if the parameter has a matching property in the JSON:
        invoke the deserialization functions using the target type from the parameter and the property from the JSON
    if the parameter has no matching property in the JSON, and has no default value, but is nullable:
        set the parameter value to null
invoke the constructor using the parameter values derived as above
for each property in the JSON that has not been consumed by the constructor (there may be no such properties):
    locate a property in the instantiated object with the same name
    if no such property exists (and allowExtra not specified):
        FAILURE - can't construct an object of the target type from the supplied JSON
    if a property exists and has a setter (it is a var property):
        invoke the deserialization functions using the target type from the property and the property from the JSON
        invoke the setter function with the resulting value
    if a property exists but has no setter (it is a val property):
        invoke the deserialization functions using the target type from the property and the property from the JSON
        invoke the getter function
        if the value from the getter does not match the value from the deserialization of the JSON property:
            FAILURE - can't construct an object of the target type from the supplied JSON
return the instantiated object
```

The above steps allow for a wide variety of target classes, but in the common case of a Kotlin data class with a single
constructor and no additional properties, the steps reduce to:
```text
for each parameter in the constructor:
    if the parameter has a matching property in the JSON:
        invoke the deserialization functions using the target type from the parameter and the property from the JSON
    if the parameter has no matching property in the JSON, and has no default value, but is nullable:
        set the parameter value to null
    if the parameter has no matching property in the JSON, the parameter has no default value,
                and the parameter is not nullable:
        FAILURE - can't construct an object of the target type from the supplied JSON
invoke the constructor using the parameter values derived as above
if there are properties in the JSON that have not been consumed by the constructor (and allowExtra not specified):
    FAILURE - can't construct an object of the target type from the supplied JSON
return the instantiated object
```

### `Any?`

When deserialising into the target type `Any?` (either as the initial target class or as a nested class in a recursive
invocation), `json-kotlin` will return the following types:

| Input JSON                                           | Result Class        |
| ---------------------------------------------------- | ------------------- |
| string                                               | `String`            |
| number (that will fit in an `Int`)                   | `Int`               |
| number (longer than `Int`, but will fit in a `Long`) | `Long`              |
| number (other than the above)                        | `BigDecimal`        |
| boolean                                              | `Boolean`           |
| array                                                | `List<Any?>`        |
| object                                               | `Map<String, Any?>` |

When the return class is `List` or `Map`, the type parameter is `Any?` but the actual type will itself be one of the
above.

### Objects

A Kotlin `object` may be the target of a deserialization operation (serialization requires no special processing).
The object instance will be returned, and all parameters in the JSON will be processed in the same way as additional
properties (those not used as constructor parameters) in the instantiation of an object.

### Sealed Classes

The library will handle [Kotlin sealed classes](https://kotlinlang.org/docs/reference/sealed-classes.html), by adding a
discriminator property to the JSON object to allow the deserialization to select the correct derived class.
The Kotlin documentation on sealed classes uses the following example:
```kotlin
    sealed class Expr
    data class Const(val number: Double) : Expr()
    data class Sum(val e1: Expr, val e2: Expr) : Expr()
    object NotANumber : Expr()
```

A `Const` instance from the example will serialize as:
```json
{"class":"Const","number":1.234}
```
and the `NotANumber` object will serialize as:
```json
{"class":"NotANumber"}
```
The discriminator property name (default "class") [may be chosen as required](#sealedclassdiscriminator).


## Configuration

Operation of the `json-kotlin` library may be parameterised by the use of the `JSONConfig` class.
This form of configuration (as opposed to constructing and tailoring a converter object which then performs the
serialization and deserialization) has a number of advantages:
- simple uses of the library (the majority) may be expressed more concisely
- the pattern allows for the use of extension functions (e.g. `string.parseJSON(config)`)

Most of the serialization and deserialization functions take a `JSONConfig` as a parameter, and use
`JSONConfig.defaultConfig` as the default if the parameter is omitted.

To create a custom `JSONConfig`:
```kotlin
    val config = JSONConfig().apply {
        // make changes to default settings as required
    }
```

### `includeNulls`

The default behaviour is to omit `null` fields when serializing an object.
To specify that all fields are to be output, even if `null`, set the `includeNulls` variable to `true`:
```kotlin
    config.includeNulls = true
```

### `allowExtra`

When deserializing a JSON object into an instance of a class, `json-kotlin` will try to find a constructor parameter or
an instance variable for each property in the object, and will raise an exception if no matching parameter or property
exists.
To allow non-matching properties to be ignored, the `allowExtra` variable may be set to `true`:
```kotlin
    config.allowExtra = true
```

### `bigDecimalString`

By default `BigDecimal` objects will be serialized to and deserialized from JSON numbers.
To use strings, the `bigDecimalString` variable may be set to `true`:
```kotlin
    config.bigDecimalString = true
```

### `bigIntegerString`

By default `BigInteger` objects will be serialized to and deserialized from JSON numbers.
To use strings, the `bigIntegerString` variable may be set to `true`:
```kotlin
    config.bigIntegerString = true
```

### `sealedClassDiscriminator`

The default name for the discriminator property added to sealed classes is `class`.
This may be changed using the `sealedClassDiscriminator` variable:
```kotlin
    config.sealedClassDiscriminator = "?" // note that it does not need to be an alphanumeric name
```

### `fromJSON`

Function to add a custom deserialization lambda.
See [Custom Serialization and Deserialization](CUSTOM.md#fromjson-lambda-in-the-jsonconfig).

### `toJSON`

Function to add a custom serialization lambda.
See [Custom Serialization and Deserialization](CUSTOM.md#tojson-lambda-in-the-jsonconfig).

### `defaultConfig`

The `defaultConfig` object that will be used when no `JSONConfig` is specified may be modified just like any other
`JSONConfig`.
To make a configuration option apply to all uses of the library in an application, you can set the value in the
default object and it will take effect for all functions where a `JSONConfig` is not specified explicitly, for example:
```kotlin
    JSONConfig.defaultConfig.allowExtra = true
```
If you have made such changes to the `defaultConfig`, you can still override them by specifying a "clean" `JSONConfig`
in the function call:
```kotlin
    val person: Person? = json.parseJSON(JSONConfig())
```

### Annotations

Many of the above options may applied to an individual property or to a class by the use of annotations.
See the [json-kotlin-annotations](https://github.com/pwall567/json-kotlin-annotations) project for more details.


## Serialize Kotlin object to String

### Extension Functions

The simplest way to convert any object to JSON is to use the `stringifyJSON()` extension function on the object itself
(the receiver for the function is `Any?`, so it may even be applied to `null`):
```kotlin
    val obj = listOf("ABC", "DEF")
    println(obj.stringifyJSON())
```
This will output:
```json
["ABC","DEF"]
```
If required, a `JSONConfig` may be provided as a parameter to the function.

Alternatively, if you have an `Appendable` (for example, a `StringBuilder` or a `Writer`), you can serialize an object
directly using the `appendJSON()` extension function on the `Appendable`:
```kotlin
    File("path.to.file").writer().use {
        it.appendJSON(objectToBeSerialized)
    }
```
This avoids allocating a `StringBuilder` to hold the serialized data before it is output.
An optional `JSONConfig` may be supplied as a second parameter to the function.

### The `JSONStringify` object

For those who prefer to call a function with the object to be serialized as a parameter, the `JSONStringify` object has
a function `stringify` for this purpose (the word "stringify" is borrowed from the JavaScript implementation of JSON):
```kotlin
    val person = Person(surname = "Smith", firstName = "Bill")
    println(JSONStringify.stringify(person))
```
If required, a `JSONConfig` may be supplied as a second parameter to the function.

### The `JSONAuto` object

The `JSONAuto` object has a number of functions to help with parsing, but it also has a `stringify` function, which
operates identically to the `JSONStringify.stringify()` function above.


## Deserialize String to Kotlin object

Deserialization in all its forms requires the target type to be specified in some way.
In many cases the target type may be determined through Kotlin type inference, but there are also functions that allow
the target type to be specified explicitly as a `KType`, a `KClass` or even a Java `Class` or `Type`.

All of these functions will return `null` if the JSON string is `"null"`, and they all take a `JSONConfig` as an
optional final (or only) parameter.

The examples in this section assume the input is a JSON string as follows:
```kotlin
    val json = """{"surname":"Smith","firstName":"Bill"}"""
```

### Extension Functions

The extension function `parseJSON()` may be applied to a `CharSequence` (this is an interface that is implemented by
`String`, `StringBuilder` and other classes, so the extension function may be applied to any of those classes).
There are several forms of the function, allowing the target type to be specified in different ways:

#### Implied type

The simplest form of the function is the one that uses the implied type:
```kotlin
    val person: Person? = json.parseJSON()
```
Note that the result type must be specified as nullable.

Of course, if a null value would represent an error, you can use something like:
```kotlin
    val person: Person = json.parseJSON() ?: throw NullPointerException("JSON should not be null")
```

#### Kotlin reified type

The same function may be invoked using the target type as type parameter:
```kotlin
    val person = json.parseJSON<Person>()
```
Even though the type parameter is specified as the non-nullable type, the result type will be nullable
(in this case, the type of `person` will be `Person?`).

#### Explicit `KClass`

The class may be specified as a parameter:
```kotlin
    val person = json.parseJSON(Person::class)
```

#### Explicit `KType`

The type may be specified as a parameter (note that in this case, the type parameter does not convey enough information
to determine the result type at compile time, so an `as` construction is required):
```kotlin
    val person = json.parseJSON(Person::class.starProjectedType) as Person
```

### The `JSONAuto` object

The `JSONAuto` object has a number of parsing functions, allowing the input to be supplied as a `String` (actually a
`CharSequence` - see above), a `Reader`, an `InputStream` or a `File`.
For each form of input, the target type may be specified implicitly or as a `KClass`, `KType` or a Java `Type` (which
includes `Class`).

The function signatures are listed here; for more detail see the KDoc (your IDE should show this when you enter the
function name):
- `fun parse(resultType: KType, str: CharSequence, config: JSONConfig = JSONConfig.defaultConfig): Any?`
- `fun <T: Any> parse(resultClass: KClass<T>, str: CharSequence, config: JSONConfig = JSONConfig.defaultConfig): T?`
- `inline fun <reified T: Any> parse(str: CharSequence, config: JSONConfig = JSONConfig.defaultConfig): T?`
- `fun parse(javaType: Type, str: CharSequence, config: JSONConfig = JSONConfig.defaultConfig): Any?`
- `fun parse(resultType: KType, reader: Reader, config: JSONConfig = JSONConfig.defaultConfig): Any?`
- `fun <T: Any> parse(resultClass: KClass<T>, reader: Reader, config: JSONConfig = JSONConfig.defaultConfig): T?`
- `inline fun <reified T: Any> parse(reader: Reader, config: JSONConfig = JSONConfig.defaultConfig): T?`
- `fun parse(javaType: Type, reader: Reader, config: JSONConfig = JSONConfig.defaultConfig): Any?`
- `fun parse(resultType: KType, inputStream: InputStream, config: JSONConfig = JSONConfig.defaultConfig): Any?`
- `fun <T: Any> parse(resultClass: KClass<T>, inputStream: InputStream, config: JSONConfig = JSONConfig.defaultConfig): T?`
- `inline fun <reified T: Any> parse(inputStream: InputStream, config: JSONConfig = JSONConfig.defaultConfig): T?`
- `fun parse(javaType: Type, inputStream: InputStream, config: JSONConfig = JSONConfig.defaultConfig): Any?`
- `fun parse(resultType: KType, file: File, config: JSONConfig = JSONConfig.defaultConfig): Any?`
- `fun <T: Any> parse(resultClass: KClass<T>, file: File, config: JSONConfig = JSONConfig.defaultConfig): T?`
- `inline fun <reified T: Any> parse(file: File, config: JSONConfig = JSONConfig.defaultConfig): T?`
- `fun parse(javaType: Type, file: File, config: JSONConfig = JSONConfig.defaultConfig): Any?`

Note that the functions that take a `File` or an `InputStream` do not include provision for specifying a character set;
if a character set other than the default (UTF-8) is required the functions taking a `Reader` may be used, for example:
```kotlin
    val json = JSONAuto.parse<List<Person>>(File("path.to.json").reader(Charsets.ISO_8859_1))
```


## Serialize Kotlin object to `JSONValue`

### The `JSONSerializer` object

The `JSONSerializer` object is intended principally for use internally within the `json-kotlin` library itself
(hence the not very user-friendly name).
It has a single public function `serialize()`, which takes the object to be serialized and returns a `JSONValue`
(or `null` if the input is `null`):
```kotlin
    val value: JSONValue? = JSONSerializer.serialize(anyObject)
```
As always, a `JSONConfig` may be specified as an optional final parameter.


## Deserialize `JSONValue` to Kotlin object

As with the deserialization of `String` to a Kotlin object, the functions will return `null` if the JSON string is
`"null"`, and they all take a `JSONConfig` as an optional final (or only) parameter.

The examples in this section assume the input is a `JSONValue` resulting from the following `parse()` operation
(see [`jsonutil`](https://github.com/pwall567/jsonutil) for more details):
```kotlin
    val jsonValue = JSON.parse("""{"surname":"Smith","firstName":"Bill"}""")
```

### Extension Functions

There are several forms of the `deserialize()` extension function which may be applied to a `JSONValue` (actually a
`JSONValue?`, so the receiver may be null).

#### Implied type

As with deserialization from string, the simplest form of the function is the one that uses the implied type:
```kotlin
    val person: Person? = jsonValue.deserialize()
```

#### Kotlin reified type

The same function may be called with the target type as an explicit type parameter:
```kotlin
    val person = jsonValue.deserialize<Person>()
```

#### Explicit `KClass`

The class may be specified as a parameter:
```kotlin
    val person = jsonValue.deserialize(Person::class)
```

#### Explicit `KType`

The type may be specified as a parameter (see the note about the `as` clause in the description of the string function):
```kotlin
    val person = jsonValue.deserialize(Person::class.starProjectedType) as Person
```

### The `JSONDeserializer` object

Like `JSONSerializer`, the `JSONDeserializer` object is intended principally for internal use within `json-kotlin`.
And as with all the other forms of deserialization, there are several options for specifying the target type.
The `JSONDeserializer` object also has some additional functions to help with common cases.

#### Implied type

Again, the simplest form is the one that uses the implied type:
```kotlin
    val person: Person? = JSONDeserializer.deserialize(jsonValue)
```

#### Kotlin reified type

And again, the same function may be called with the target type as an explicit type parameter:
```kotlin
    val person = JSONDeserializer.deserialize<Person>(jsonValue)
```

#### Explicit `KClass`

The class may be specified as a parameter:
```kotlin
    val person = JSONDeserializer.deserialize(Person::class, jsonValue)
```

#### Explicit `KType`

The type may be specified as a parameter (again, see the note about the `as` clause in the description of the string
function):
```kotlin
    val person = JSONDeserializer.deserialize(Person::class.starProjectedType, jsonValue) as Person
```

#### Java `Type`

The Java type may also be specified as a parameter (Java `Class` is a subclass of `Type`, but like `KType`, `Type` does
not convey sufficient information to determine the result type so `as` is required):
```kotlin
    val person = JSONDeserializer.deserialize(Person::class.java, jsonValue) as Person
```

#### `deserializeNonNull`

The `deserializeNonNull` function deserializes a `JSONValue` to specified `KClass`, throwing an exception if the result
would be `null`:
```kotlin
    val person = JSONDeserializer.deserializeNonNull(Person::class, jsonValue)
```
Only the form of the function taking a `KClass` is provided; in the case of a `KType` the nullability of the target is
specified in the `KType` itself.

#### `deserializeAny`

This is a shortcut form of the `deserialize` function when supplied with a type parameter of `Any?`.
See [here](#any) for the effect of specifying a target type of `Any?`.


## Exceptions

Most errors in serialization or deserialization cause a `JSONKotlinException` to be thrown.
The exception has the following available properties:

| Name      | Type          | Description                                                                        |
| --------- | ------------- | ---------------------------------------------------------------------------------- |
| `text`    | `String`      | The specific text for the exception, not including pointer or `cause`              |
| `pointer` | `JSONPointer` | A [JSON Pointer](https://tools.ietf.org/html/rfc6901) to the location of the issue |
| `message` | `String`      | The conventional exception message, including the pointer                          |
| `cause`   | `Throwable`   | The original cause of the exception                                                |


## Further Examples

### Conditional Deserialization

A common requirement is to deserialize an input object into one of a number of child classes in a hierarchy, depending
on a value in the JSON itself.
(Kotlin sealed classes provide one mechanism for this, but their use is not always appropriate.)

An easy way to accomplish this is to deserialize the input JSON into a `JSONValue` structure, and then examine values
directly in that structure to determine the specific target type.

For example:
```kotlin
    val json = JSON.parseObject(inputString) ?: throw NullPointerException("JSON must not be null")
    val event = when (json["type"].toString()) {
        // the toString() above is necessary because json["type"] returns a JSONValue
        "open" -> JSONDeserializer.deserialize<OpenEvent>(json)
        "close" -> JSONDeserializer.deserialize<CloseEvent>(json)
        else -> throw JSONException("Unknown event type")
    }
```

### Working with Spring

Many users will wish to use `json-kotlin` in conjunction with the
[Spring Framework](https://spring.io/projects/spring-framework).
An example `Service` class to provide default JSON serialization and deserialization for Spring applications is shown in
the [Spring and `json-kotlin`](SPRING.md) guide.

2021-01-10
<file_sep>/*
 * @(#) JSONJavaTest.java
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

public class JSONJavaTest {

    private static final String json = "{\"field1\":123,\"field2\":\"abcdef\"}";
    private static final String complexJson =
            "[{\"field1\":123,\"field2\":\"abcdef\"},{\"field1\":987,\"field2\":\"hello\"}]";

    @Test
    public void testJSONJavaStringify() {
        JavaClass1 javaClass1 = new JavaClass1(123, "abcdef");
        assertEquals(json, JSONJava.stringify(javaClass1));
    }

    @Test
    public void testJSONJavaStringifyOfKotlinClass() {
        Dummy1 dummy1 = new Dummy1("abcdefghi", 9876543);
        String expected = "{\"field1\":\"abcdefghi\",\"field2\":9876543}";
        assertEquals(expected, JSONJava.stringify(dummy1));
    }

    @Test
    public void testJSONJavaParseUsingClass() {
        JavaClass1 javaClass1 = new JavaClass1(123, "abcdef");
        assertEquals(javaClass1, JSONJava.parse(JavaClass1.class, json));
    }

    @Test
    public void testJSONJavaParseOfKotlinClass() {
        String jsonDummy1 = "{\"field1\":\"abcdefghi\",\"field2\":9876543}";
        Dummy1 expected = new Dummy1("abcdefghi", 9876543);
        assertEquals(expected, JSONJava.parse(Dummy1.class, jsonDummy1));
    }

    @Test
    public void testJSONJavaParseUsingType() throws Exception {
        List<JavaClass1> javaClass1List = new ArrayList<>();
        javaClass1List.add(new JavaClass1(123, "abcdef"));
        javaClass1List.add(new JavaClass1(987, "hello"));
        Type type = JavaClass2.class.getField("field1").getGenericType();
        assertEquals(javaClass1List, JSONJava.parse(type, complexJson));
    }

    @Test
    public void testAddToJSONMapping() {
        JSONConfig config = new JSONConfig();
        JSONJava.addToJSONMapping(config, Dummy1.class, new Dummy1ToJSON());
        JSONObject expected = JSONObject.create().putValue("a", "xyz").putValue("b", 888);
        assertEquals(expected, JSONSerializer.INSTANCE.serialize(new Dummy1("xyz", 888), config));
    }

    @Test
    public void testAddFromJSONMapping() {
        JSONConfig config = new JSONConfig();
        JSONJava.addFromJSONMapping(config, Dummy1.class, new Dummy1FromJSON());
        Dummy1 expected = new Dummy1("xyz", 888);
        JSONObject json = JSONObject.create().putValue("a", "xyz").putValue("b", 888);
        assertEquals(expected, JSONDeserializer.INSTANCE.deserialize(Dummy1.class, json, config));
    }

    public static class Dummy1ToJSON implements JSONJava.ToJSONMapping<Dummy1> {

        @Override
        public JSONValue invoke(Dummy1 obj) {
            return obj == null ? null :
                    JSONObject.create().putValue("a", obj.getField1()).putValue("b", obj.getField2());
        }

    }

    public static class Dummy1FromJSON implements JSONJava.FromJSONMapping<Dummy1> {

        @Override
        public Dummy1 invoke(JSONValue json) {
            if (json == null)
                return null;
            if (!(json instanceof JSONObject))
                fail();
            JSONObject jsonObject = (JSONObject)json;
            return new Dummy1(jsonObject.getString("a"), jsonObject.getInt("b"));
        }

    }

}
<file_sep>/*
 * @(#) JSONKotlinExceptionTest.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2021 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.test.Test
import kotlin.test.assertFailsWith
import kotlin.test.expect

import net.pwall.json.JSONKotlinException.Companion.fail
import net.pwall.json.pointer.JSONPointer

class JSONKotlinExceptionTest {

    @Test fun `should create simple exception`() {
        val e = JSONKotlinException("Test message")
        expect("Test message") { e.text }
        expect("Test message") { e.message }
        expect(null) { e.pointer }
        expect(null) { e.cause }
    }

    @Test fun `should create exception with root pointer`() {
        val e = JSONKotlinException("Test message", JSONPointer.root)
        expect("Test message") { e.text }
        expect("Test message") { e.message }
        expect(JSONPointer.root) { e.pointer }
        expect(null) { e.cause }
    }

    @Test fun `should create exception with pointer`() {
        val e = JSONKotlinException("Test message", JSONPointer.root.child(0).child("ace"))
        expect("Test message") { e.text }
        expect("Test message at /0/ace") { e.message }
        expect(JSONPointer("/0/ace")) { e.pointer }
        expect(null) { e.cause }
    }

    @Test fun `should create exception with cause`() {
        val nested = JSONException("Nested")
        val e = JSONKotlinException("Test message", nested)
        expect("Test message") { e.text }
        expect("Test message") { e.message }
        expect(null) { e.pointer }
        expect(nested) { e.cause }
    }

    @Test fun `should create exception with pointer and cause`() {
        val nested = JSONException("Nested")
        val e = JSONKotlinException("Test message", JSONPointer.root.child(0).child("ace"), nested)
        expect("Test message") { e.text }
        expect("Test message at /0/ace") { e.message }
        expect(JSONPointer("/0/ace")) { e.pointer }
        expect(nested) { e.cause }
    }

    @Test fun `should throw simple exception`() {
        val e = assertFailsWith<JSONKotlinException> { fail("Test message") }
        expect("Test message") { e.text }
        expect("Test message") { e.message }
        expect(null) { e.pointer }
        expect(null) { e.cause }
    }

    @Test fun `should throw exception with root pointer`() {
        val e = assertFailsWith<JSONKotlinException> { fail("Test message", JSONPointer.root) }
        expect("Test message") { e.text }
        expect("Test message") { e.message }
        expect(JSONPointer.root) { e.pointer }
        expect(null) { e.cause }
    }

    @Test fun `should throw exception with pointer`() {
        val e = assertFailsWith<JSONKotlinException> { fail("Test message", JSONPointer.root.child(0).child("ace")) }
        expect("Test message") { e.text }
        expect("Test message at /0/ace") { e.message }
        expect(JSONPointer("/0/ace")) { e.pointer }
        expect(null) { e.cause }
    }

    @Test fun `should throw exception with cause`() {
        val nested = JSONException("Nested")
        val e = assertFailsWith<JSONKotlinException> { fail("Test message", nested) }
        expect("Test message") { e.text }
        expect("Test message") { e.message }
        expect(null) { e.pointer }
        expect(nested) { e.cause }
    }

    @Test fun `should throw exception with pointer and cause`() {
        val nested = JSONException("Nested")
        val e = assertFailsWith<JSONKotlinException> {
            fail("Test message", JSONPointer.root.child(0).child("ace"), nested)
        }
        expect("Test message") { e.text }
        expect("Test message at /0/ace") { e.message }
        expect(JSONPointer("/0/ace")) { e.pointer }
        expect(nested) { e.cause }
    }

}
<file_sep>/*
 * @(#) JSONTypeRef.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2020 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import java.lang.reflect.ParameterizedType

/**
 * Implementation of the [TypeReference](https://gafter.blogspot.com/2006/12/super-type-tokens.html) pattern.
 *
 * @author  <NAME> (with acknowledgements to the author of the above-referenced article)
 * @param   T       the target type
 */
open class JSONTypeRef<T>(nullable: Boolean = false) {

    /** The type reference */
    val refType = (this::class.java.genericSuperclass as ParameterizedType).actualTypeArguments[0].toKType(nullable)

    companion object {

        /**
         * Create a [JSONTypeRef] for the target type.
         *
         * @param   T       the target type
         * @return          the [JSONTypeRef]
         */
        inline fun <reified T> create(nullable: Boolean = false): JSONTypeRef<T> = object : JSONTypeRef<T>(nullable) {}

    }

}
<file_sep>/*
 * @(#) JSONKotlinException.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2021 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import net.pwall.json.pointer.JSONPointer

/**
 * Exception class for errors in serialization and deserialization.
 *
 * @author  <NAME>
 */
class JSONKotlinException(val text: String, val pointer: JSONPointer? = null) : JSONException(text) {

    constructor(text: String, pointer: JSONPointer, nested: Exception) : this(text, pointer) {
        initCause(nested)
    }

    constructor(text: String, nested: Exception) : this(text) {
        initCause(nested)
    }

    override val message: String
        get() = when (pointer) {
            null,
            JSONPointer.root -> text
            else -> "$text at $pointer"
        }

    companion object {

        // the name "fail" may clash with "kotlin.test.fail", so these functions are marked "internal"

        internal fun fail(text: String, pointer: JSONPointer? = null): Nothing {
            throw JSONKotlinException(text, pointer)
        }

        internal fun fail(text: String, pointer: JSONPointer, nested: Exception): Nothing {
            throw JSONKotlinException(text, pointer, nested)
        }

        internal fun fail(text: String, nested: Exception): Nothing {
            throw JSONKotlinException(text, nested)
        }

    }

}
<file_sep>/*
 * @(#) JSONFun.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019, 2020, 2021 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.KTypeProjection
import kotlin.reflect.full.createType
import kotlin.reflect.full.starProjectedType

import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.WildcardType
import java.math.BigDecimal
import java.math.BigInteger

import net.pwall.json.JSONKotlinException.Companion.fail

/** Type alias for `JSONInt` to make it closer to Kotlin standard class names. */
typealias JSONInt = JSONInteger

/** Type alias to simplify the definition of `fromJSON` mapping functions. */
typealias FromJSONMapping = (JSONValue?) -> Any?

/** Type alias to simplify the definition of `toJSON` mapping functions. */
typealias ToJSONMapping = (Any?) -> JSONValue?

/**
 * More Kotlin-like conversion function name.
 *
 * @return  the value as a [BigInteger]
 */
fun JSONNumberValue.toBigInteger(): BigInteger = bigIntegerValue()

/**
 * More Kotlin-like conversion function name.
 *
 * @return  the value as a [BigDecimal]
 */
fun JSONNumberValue.toBigDecimal(): BigDecimal = bigDecimalValue()

/**
 * Convert a [CharSequence] ([String], [StringBuilder] etc.) to a [JSONValue].
 *
 * @receiver    the [CharSequence] to be converted
 * @return      the [JSONValue]
 */
fun CharSequence.asJSONValue(): JSONValue = JSONString(this)

/**
 * Convert a [Char] to a [JSONValue].
 *
 * @receiver    the [Char] to be converted
 * @return      the [JSONValue]
 */
fun Char.asJSONValue(): JSONValue = JSONString(StringBuilder().append(this))

/**
 * Convert an [Int] to a [JSONValue].
 *
 * @receiver    the [Int] to be converted
 * @return      the [JSONValue]
 */
fun Int.asJSONValue(): JSONValue = JSONInt.valueOf(this)

/**
 * Convert a [Long] to a [JSONValue].
 *
 * @receiver    the [Long] to be converted
 * @return      the [JSONValue]
 */
fun Long.asJSONValue(): JSONValue = JSONLong.valueOf(this)

/**
 * Convert a [Short] to a [JSONValue].
 *
 * @receiver    the [Short] to be converted
 * @return      the [JSONValue]
 */
fun Short.asJSONValue(): JSONValue = JSONInt.valueOf(this.toInt())

/**
 * Convert a [Byte] to a [JSONValue].
 *
 * @receiver    the [Byte] to be converted
 * @return      the [JSONValue]
 */
fun Byte.asJSONValue(): JSONValue = JSONInt.valueOf(this.toInt())

/**
 * Convert a [Float] to a [JSONValue].
 *
 * @receiver    the [Float] to be converted
 * @return      the [JSONValue]
 */
fun Float.asJSONValue(): JSONValue = JSONFloat.valueOf(this)

/**
 * Convert a [Double] to a [JSONValue].
 *
 * @receiver    the [Double] to be converted
 * @return      the [JSONValue]
 */
fun Double.asJSONValue(): JSONValue = JSONDouble.valueOf(this)

/**
 * Convert a [Boolean] to a [JSONValue].
 *
 * @receiver    the [Boolean] to be converted
 * @return      the [JSONValue]
 */
fun Boolean.asJSONValue(): JSONValue = JSONBoolean.valueOf(this)

/**
 * Convert any object to a [JSONValue].
 *
 * @receiver        the object to be converted
 * @param   config  an optional [JSONConfig] to customise the conversion
 * @return          the [JSONValue]
 */
fun Any?.asJSONValue(config: JSONConfig = JSONConfig.defaultConfig): JSONValue? = JSONSerializer.serialize(this, config)

/**
 * Create a [Pair] of [String] and [JSONValue]? for use with [makeJSON].
 *
 * @receiver    the [String]
 * @param   str a [CharSequence] ([String], [StringBuilder] etc.)
 * @return      the [Pair]
 */
infix fun String.isJSON(str: CharSequence?): Pair<String, JSONValue?> = this to str?.asJSONValue()

/**
 * Create a [Pair] of [String] and [JSONValue]? for use with [makeJSON].
 *
 * @receiver    the [String]
 * @param   ch  a [Char]
 * @return      the [Pair]
 */
infix fun String.isJSON(ch: Char): Pair<String, JSONValue?> = this to ch.asJSONValue()

/**
 * Create a [Pair] of [String] and [JSONValue]? for use with [makeJSON].
 *
 * @receiver    the [String]
 * @param   i   an [Int]
 * @return      the [Pair]
 */
infix fun String.isJSON(i: Int): Pair<String, JSONValue?> = this to i.asJSONValue()

/**
 * Create a [Pair] of [String] and [JSONValue]? for use with [makeJSON].
 *
 * @receiver    the [String]
 * @param   i   a [Long]
 * @return      the [Pair]
 */
infix fun String.isJSON(i: Long): Pair<String, JSONValue?> = this to i.asJSONValue()

/**
 * Create a [Pair] of [String] and [JSONValue]? for use with [makeJSON].
 *
 * @receiver    the [String]
 * @param   s   a [Short]
 * @return      the [Pair]
 */
infix fun String.isJSON(s: Short): Pair<String, JSONValue?> = this to s.asJSONValue()

/**
 * Create a [Pair] of [String] and [JSONValue]? for use with [makeJSON].
 *
 * @receiver    the [String]
 * @param   b   a [Byte]
 * @return      the [Pair]
 */
infix fun String.isJSON(b: Byte): Pair<String, JSONValue?> = this to b.asJSONValue()

/**
 * Create a [Pair] of [String] and [JSONValue]? for use with [makeJSON].
 *
 * @receiver    the [String]
 * @param   f   a [Float]
 * @return      the [Pair]
 */
infix fun String.isJSON(f: Float): Pair<String, JSONValue?> = this to f.asJSONValue()

/**
 * Create a [Pair] of [String] and [JSONValue]? for use with [makeJSON].
 *
 * @receiver    the [String]
 * @param   d   a [Double]
 * @return      the [Pair]
 */
infix fun String.isJSON(d: Double): Pair<String, JSONValue?> = this to d.asJSONValue()

/**
 * Create a [Pair] of [String] and [JSONValue]? for use with [makeJSON].
 *
 * @receiver    the [String]
 * @param   b   a [Boolean]
 * @return      the [Pair]
 */
infix fun String.isJSON(b: Boolean): Pair<String, JSONValue?> = this to b.asJSONValue()

/**
 * Create a [JSONObject] from a list of [Pair]s of [String]s and [JSONValue]s.
 *
 * @param   pairs   the list of pairs
 * @return          the [JSONObject]
 */
fun makeJSON(vararg pairs: Pair<String, JSONValue?>) = JSONObject().apply { putAll(pairs) }

/**
 * Deserialize JSON from string ([CharSequence]) to a specified [KType].
 *
 * @receiver            the JSON in string form
 * @param   resultType  the target type
 * @param   config      an optional [JSONConfig] to customise the conversion
 * @return              the converted object
 */
fun CharSequence.parseJSON(resultType: KType, config: JSONConfig = JSONConfig.defaultConfig): Any? =
        JSONAuto.parse(resultType, this, config)

/**
 * Deserialize JSON from string ([CharSequence]) to a specified [KClass].
 *
 * @receiver            the JSON in string form
 * @param   resultClass the target class
 * @param   config      an optional [JSONConfig] to customise the conversion
 * @param   T           the target class
 * @return              the converted object
 */
fun <T: Any> CharSequence.parseJSON(resultClass: KClass<T>, config: JSONConfig = JSONConfig.defaultConfig): T? =
        JSONAuto.parse(resultClass, this, config)

/**
 * Deserialize JSON from string ([CharSequence]) to a the inferred [KClass].
 *
 * @receiver        the JSON in string form
 * @param   config  an optional [JSONConfig] to customise the conversion
 * @param   T       the target class
 * @return          the converted object
 */
inline fun <reified T: Any> CharSequence.parseJSON(config: JSONConfig = JSONConfig.defaultConfig): T? =
        JSONAuto.parse(this, config)

/**
 * Stringify any object to JSON.
 *
 * @receiver        the object to be converted to JSON (`null` will be converted to `"null"`).
 * @param   config  an optional [JSONConfig] to customise the conversion
 * @return          the JSON string
 */
fun Any?.stringifyJSON(config: JSONConfig = JSONConfig.defaultConfig): String = JSONStringify.stringify(this, config)

/**
 * Helper method to create a [KType] for a parameterised type, for use as the target type of a deserialization.
 *
 * @param   mainClass       the parameterised class
 * @param   paramClasses    the parameter classes
 * @param   nullable        `true` if the [KType] is to be nullable
 * @return                  the [KType]
 */
fun targetKType(mainClass: KClass<*>, vararg paramClasses: KClass<*>, nullable: Boolean = false): KType =
        mainClass.createType(paramClasses.map { KTypeProjection.covariant(it.starProjectedType) }, nullable)

/**
 * Helper method to create a [KType] for a parameterised type, for use as the target type of a deserialization.
 *
 * @param   mainClass       the parameterised class
 * @param   paramTypes      the parameter types
 * @param   nullable        `true` if the [KType] is to be nullable
 * @return                  the [KType]
 */
fun targetKType(mainClass: KClass<*>, vararg paramTypes: KType, nullable: Boolean = false): KType =
        mainClass.createType(paramTypes.map { KTypeProjection.covariant(it) }, nullable)

/**
 * Convert a Java [Type] to a Kotlin [KType].  This allows Java [Type]s to be used as the target type of a
 * deserialization operation.
 *
 * @receiver    the Java [Type] to be converted
 * @param       nullable    `true` if the [KType] is to be nullable
 * @return      the resulting Kotlin [KType]
 * @throws      JSONKotlinException if the [Type] can not be converted
 */
fun Type.toKType(nullable: Boolean = false): KType = when (this) {
    is Class<*> -> this.kotlin.createType(nullable = nullable)
    is ParameterizedType -> (this.rawType as Class<*>).kotlin.createType(this.actualTypeArguments.map {
        when (it) {
            is WildcardType ->
                if (it.lowerBounds?.firstOrNull() == null)
                    KTypeProjection.covariant(it.upperBounds[0].toKType(true))
                else
                    KTypeProjection.contravariant(it.lowerBounds[0].toKType(true))
            else -> KTypeProjection.invariant(it.toKType(true))
        } }, nullable)
    else -> fail("Can't handle type: $this")
}

/**
 * Deserialize a [JSONValue] to the nominated [KType].
 *
 * @receiver    the [JSONValue] (or `null`)
 * @param       resultType  the target [KType]
 * @param       config      an optional [JSONConfig] to customise the conversion
 * @throws      JSONKotlinException if the value can not be converted
 */
fun JSONValue?.deserialize(resultType: KType, config: JSONConfig = JSONConfig.defaultConfig): Any? =
        JSONDeserializer.deserialize(resultType, this, config)

/**
 * Deserialize a [JSONValue] to the nominated [KClass].
 *
 * @receiver    the [JSONValue] (or `null`)
 * @param       resultClass the target [KClass]
 * @param       config      an optional [JSONConfig] to customise the conversion
 * @param       T           the target class
 * @throws      JSONKotlinException if the value can not be converted
 */
fun <T: Any> JSONValue?.deserialize(resultClass: KClass<T>, config: JSONConfig = JSONConfig.defaultConfig): T? =
        JSONDeserializer.deserialize(resultClass, this, config)

/**
 * Deserialize a [JSONValue] to the inferred class.
 *
 * @receiver    the [JSONValue] (or `null`)
 * @param       config      an optional [JSONConfig] to customise the conversion
 * @param       T           the target class
 * @throws      JSONKotlinException if the value can not be converted
 */
inline fun <reified T: Any> JSONValue?.deserialize(config: JSONConfig = JSONConfig.defaultConfig): T? =
        JSONDeserializer.deserialize(this, config)

/**
 * Deserialize a [JSONValue] to the nominated Java [Type].
 *
 * @receiver    the [JSONValue] (or `null`)
 * @param       javaType    the target [Type]
 * @param       config      an optional [JSONConfig] to customise the conversion
 * @throws      JSONKotlinException if the value can not be converted
 */
fun JSONValue?.deserialize(javaType: Type, config: JSONConfig = JSONConfig.defaultConfig): Any? =
        JSONDeserializer.deserialize(javaType, this, config)
<file_sep>/*
 * @(#) JSONSerializerTest.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019, 2020, 2021 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.math.abs
import kotlin.test.assertFailsWith
import kotlin.test.assertNull
import kotlin.test.assertSame
import kotlin.test.assertTrue
import kotlin.test.expect
import kotlin.test.Test

import java.math.BigDecimal
import java.math.BigInteger
import java.net.URI
import java.net.URL
import java.time.Duration
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.MonthDay
import java.time.OffsetDateTime
import java.time.OffsetTime
import java.time.Period
import java.time.Year
import java.time.YearMonth
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.ZonedDateTime
import java.util.BitSet
import java.util.Calendar
import java.util.TimeZone
import java.util.UUID
import java.util.stream.IntStream
import java.util.stream.Stream

class JSONSerializerTest {

    @Test fun `null should return null`() {
        assertNull(JSONSerializer.serialize(null))
    }

    @Test fun `JSONValue should be returned as-is`() {
        val json = JSONInt(12345)
        assertSame(json, JSONSerializer.serialize(json))
    }

    @Test fun `String should return JSONString`() {
        val str = "Hello JSON!"
        expect(JSONString(str)) { JSONSerializer.serialize(str) }
    }

    @Test fun `StringBuilder should return JSONString`() {
        val str = "Hello JSON!"
        val sb = StringBuilder(str)
        expect(JSONString(str)) { JSONSerializer.serialize(sb) }
    }

    @Test fun `Char should return JSONString`() {
        val char = 'Q'
        expect(JSONString("Q")) { JSONSerializer.serialize(char) }
    }

    @Test fun `Int should return JSONInt`() {
        val i = 123456
        val actual = JSONSerializer.serialize(i)
        assertTrue(actual is JSONInt)
        assertTrue(intEquals(i, actual.value))
        // Note - these assertions are complicated because JSONInt.equals() returns true
        // for any comparison with another numeric JSON types where the values are equal
    }

    @Test fun `Int (negative) should return JSONInt`() {
        val i = -8888
        val actual = JSONSerializer.serialize(i)
        assertTrue(actual is JSONInt)
        assertTrue(intEquals(i, actual.value))
    }

    @Test fun `Long should return JSONLong`() {
        val i = 12345678901234
        val actual = JSONSerializer.serialize(i)
        assertTrue(actual is JSONLong)
        assertTrue(longEquals(i, actual.value))
    }

    @Test fun `Long (negative) should return JSONLong`() {
        val i = -987654321987654321
        val actual = JSONSerializer.serialize(i)
        assertTrue(actual is JSONLong)
        assertTrue(longEquals(i, actual.value))
    }

    @Test fun `Short should return JSONInt`() {
        val i: Short = 1234
        val actual = JSONSerializer.serialize(i)
        assertTrue(actual is JSONInt)
        assertTrue(intEquals(i.toInt(), actual.value))
    }

    @Test fun `Byte should return JSONInt`() {
        val i: Byte = 123
        val actual = JSONSerializer.serialize(i)
        assertTrue(actual is JSONInt)
        assertTrue(intEquals(i.toInt(), actual.value))
    }

    @Test fun `Float should return JSONFloat`() {
        val f = 0.1234F
        val actual = JSONSerializer.serialize(f)
        assertTrue(actual is JSONFloat)
        assertTrue(floatEquals(f, actual.value))
    }

    @Test fun `Float (negative) should return JSONFloat`() {
        val f = -88.987F
        val actual = JSONSerializer.serialize(f)
        assertTrue(actual is JSONFloat)
        assertTrue(floatEquals(f, actual.value))
    }

    @Test fun `Double should return JSONDouble`() {
        val d = 987.654321
        val actual = JSONSerializer.serialize(d)
        assertTrue(actual is JSONDouble)
        assertTrue(doubleEquals(d, actual.value))
    }

    @Test fun `Double (exponent notation) should return JSONDouble`() {
        val d = 1e40
        val actual = JSONSerializer.serialize(d)
        assertTrue(actual is JSONDouble)
        assertTrue(doubleEquals(d, actual.value))
    }

    @Test fun `Boolean should return JSONBoolean`() {
        val t = true
        assertSame(JSONBoolean.TRUE, JSONSerializer.serialize(t))
        val f = false
        assertSame(JSONBoolean.FALSE, JSONSerializer.serialize(f))
    }

    @Test fun `CharArray should return JSONString`() {
        val ca = charArrayOf('H', 'e', 'l', 'l', 'o', '!')
        expect(JSONString("Hello!")) { JSONSerializer.serialize(ca) }
    }

    @Test fun `Array of Char should return JSONString`() {
        val ca = arrayOf('H', 'e', 'l', 'l', 'o', '!')
        expect(JSONString("Hello!")) { JSONSerializer.serialize(ca) }
    }

    @Test fun `Array of Int should return JSONArray`() {
        val array = arrayOf(123, 2345, 0, 999)
        val expected = JSONArray().apply {
            addValue(123)
            addValue(2345)
            addValue(0)
            addValue(999)
        }
        expect(expected) { JSONSerializer.serialize(array) }
    }

    @Test fun `Array of Int nullable should return JSONArray`() {
        val array = arrayOf(123, null, 0, 999)
        val expected = JSONArray().apply {
            addValue(123)
            addNull()
            addValue(0)
            addValue(999)
        }
        expect(expected) { JSONSerializer.serialize(array) }
    }

    @Test fun `Array of String should return JSONArray`() {
        val array = arrayOf("Hello", "Kotlin")
        val expected = JSONArray().apply {
            addValue("Hello")
            addValue("Kotlin")
        }
        expect(expected) { JSONSerializer.serialize(array) }
    }

    @Test fun `Iterator of String should return JSONArray`() {
        val iterator = listOf("Hello", "Kotlin").iterator()
        val expected = JSONArray().apply {
            addValue("Hello")
            addValue("Kotlin")
        }
        expect(expected) { JSONSerializer.serialize(iterator) }
    }

    @Test fun `Enumeration of String should return JSONArray`() {
        val list = listOf("Hello", "Kotlin")
        val expected = JSONArray().apply {
            addValue("Hello")
            addValue("Kotlin")
        }
        expect(expected) { JSONSerializer.serialize(ListEnum(list)) }
    }

    @Test fun `List of String should return JSONArray`() {
        val list = listOf("Hello", "Kotlin")
        val expected = JSONArray().apply {
            addValue("Hello")
            addValue("Kotlin")
        }
        expect(expected) { JSONSerializer.serialize(list) }
    }

    @Test fun `Sequence of String should return JSONArray`() {
        val seq = listOf("Hello", "Kotlin").asSequence()
        val expected = JSONArray().apply {
            addValue("Hello")
            addValue("Kotlin")
        }
        expect(expected) { JSONSerializer.serialize(seq) }
    }

    @Test fun `Map of String to Int should return JSONObject`() {
        val map = mapOf("abc" to 1, "def" to 4, "ghi" to 999)
        val expected = JSONObject().apply {
            putValue("abc", 1)
            putValue("def", 4)
            putValue("ghi", 999)
        }
        expect(expected) { JSONSerializer.serialize(map) }
    }

    @Test fun `Map of String to String nullable should return JSONObject`() {
        val map = mapOf("abc" to "hello", "def" to null, "ghi" to "goodbye")
        val expected = JSONObject().apply {
            putValue("abc", "hello")
            putNull("def")
            putValue("ghi", "goodbye")
        }
        expect(expected) { JSONSerializer.serialize(map) }
    }

    @Test fun `Class with toJSON() should serialize as JSONObject`() {
        val obj = DummyFromJSON(23)
        val expected = JSONObject().apply {
            putValue("dec", "23")
            putValue("hex", "17")
        }
        expect(expected) { JSONSerializer.serialize(obj) }
    }

    @Test fun `Enum should serialize as JSONString`() {
        val eee = DummyEnum.GAMMA
        expect(JSONString("GAMMA")) { JSONSerializer.serialize(eee) }
    }

    @Test fun `Calendar should return JSONString`() {
        val cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")).apply {
            set(Calendar.YEAR, 2019)
            set(Calendar.MONTH, 3)
            set(Calendar.DAY_OF_MONTH, 25)
            set(Calendar.HOUR_OF_DAY, 18)
            set(Calendar.MINUTE, 52)
            set(Calendar.SECOND, 47)
            set(Calendar.MILLISECOND, 123)
            set(Calendar.ZONE_OFFSET, 10 * 60 * 60 * 1000)
        }
        expect(JSONString("2019-04-25T18:52:47.123+10:00")) { JSONSerializer.serialize(cal) }
    }

    @Test fun `Date should return JSONString`() {
        val cal = Calendar.getInstance().apply {
            set(Calendar.YEAR, 2019)
            set(Calendar.MONTH, 3)
            set(Calendar.DAY_OF_MONTH, 25)
            set(Calendar.HOUR_OF_DAY, 18)
            set(Calendar.MINUTE, 52)
            set(Calendar.SECOND, 47)
            set(Calendar.MILLISECOND, 123)
            set(Calendar.ZONE_OFFSET, 10 * 60 * 60 * 1000)
        }
        val date = cal.time
        // NOTE - Java implementations are inconsistent - some will normalise the time to UTC
        // while others preserve the time zone as supplied.  The test below allows for either.
        val expected1 = JSONString("2019-04-25T18:52:47.123+10:00")
        val expected2 = JSONString("2019-04-25T08:52:47.123Z")
        val result = JSONSerializer.serialize(date)
        expect(true) { result == expected1 || result == expected2 }
    }

    @Test fun `java-sql-Date should return JSONString`() {
        val str = "2019-04-25"
        val date = java.sql.Date.valueOf(str)
        expect(JSONString(str)) { JSONSerializer.serialize(date) }
    }

    @Test fun `java-sql-Time should return JSONString`() {
        val str = "22:41:19"
        val time = java.sql.Time.valueOf(str)
        expect(JSONString(str)) { JSONSerializer.serialize(time) }
    }

    @Test fun `java-sql-Timestamp should return JSONString`() {
        val str = "2019-04-25 22:41:19.5"
        val timestamp = java.sql.Timestamp.valueOf(str)
        expect(JSONString(str)) { JSONSerializer.serialize(timestamp) }
    }

    @Test fun `Instant should return JSONString`() {
        val str = "2019-04-25T21:01:09.456Z"
        val inst = Instant.parse(str)
        expect(JSONString(str)) { JSONSerializer.serialize(inst) }
    }

    @Test fun `LocalDate should return JSONString`() {
        val date = LocalDate.of(2019, 4, 25)
        expect(JSONString("2019-04-25")) { JSONSerializer.serialize(date) }
    }

    @Test fun `LocalDateTime should return JSONString`() {
        val date = LocalDateTime.of(2019, 4, 25, 21, 6, 5)
        expect(JSONString("2019-04-25T21:06:05")) { JSONSerializer.serialize(date) }
    }

    @Test fun `LocalTime should return JSONString`() {
        val date = LocalTime.of(21, 6, 5)
        expect(JSONString("21:06:05")) { JSONSerializer.serialize(date) }
    }

    @Test fun `OffsetTime should return JSONString`() {
        val time = OffsetTime.of(21, 6, 5, 456000000, ZoneOffset.ofHours(10))
        expect(JSONString("21:06:05.456+10:00")) { JSONSerializer.serialize(time) }
    }

    @Test fun `OffsetDateTime should return JSONString`() {
        val time = OffsetDateTime.of(2019, 4, 25, 21, 6, 5, 456000000, ZoneOffset.ofHours(10))
        expect(JSONString("2019-04-25T21:06:05.456+10:00")) { JSONSerializer.serialize(time) }
    }

    @Test fun `ZonedDateTime should return JSONString`() {
        val zdt = ZonedDateTime.of(2019, 4, 25, 21, 16, 23, 123000000, ZoneId.of("Australia/Sydney"))
        expect(JSONString("2019-04-25T21:16:23.123+10:00[Australia/Sydney]")) { JSONSerializer.serialize(zdt) }
    }

    @Test fun `Year should return JSONString`() {
        val year = Year.of(2019)
        expect(JSONString("2019")) { JSONSerializer.serialize(year) }
    }

    @Test fun `YearMonth should return JSONString`() {
        val yearMonth = YearMonth.of(2019, 4)
        expect(JSONString("2019-04")) { JSONSerializer.serialize(yearMonth) }
    }

    @Test fun `MonthDay should return JSONString`() {
        val month = MonthDay.of(4, 23)
        expect(JSONString("--04-23")) { JSONSerializer.serialize(month) }
    }

    @Test fun `Duration should return JSONString`() {
        val duration = Duration.ofHours(2)
        expect(JSONString("PT2H")) { JSONSerializer.serialize(duration) }
    }

    @Test fun `Period should return JSONString`() {
        val period = Period.ofMonths(3)
        expect(JSONString("P3M")) { JSONSerializer.serialize(period) }
    }

    @Test fun `UUID should return JSONString`() {
        val uuidString = "12ce3730-2d97-11e7-aeed-67b0e6bf0ed7"
        val uuid = UUID.fromString(uuidString)
        expect(JSONString(uuidString)) { JSONSerializer.serialize(uuid) }
    }

    @Test fun `URI should return JSONString`() {
        val uriString = "http://pwall.net"
        val uri = URI(uriString)
        expect(JSONString(uriString)) { JSONSerializer.serialize(uri) }
    }

    @Test fun `URL should return JSONString`() {
        val urlString = "http://pwall.net"
        val url = URL(urlString)
        expect(JSONString(urlString)) { JSONSerializer.serialize(url) }
    }

    @Test fun `BigInteger should return JSONLong`() {
        val bigIntLong = 123456789012345678L
        val bigInteger = BigInteger.valueOf(bigIntLong)
        expect(JSONLong(bigIntLong)) { JSONSerializer.serialize(bigInteger) }
    }

    @Test fun `BigInteger should return JSONString when config option selected`() {
        val bigIntString = "123456789012345678"
        val bigInteger = BigInteger(bigIntString)
        val config = JSONConfig().apply {
            bigIntegerString = true
        }
        expect(JSONString(bigIntString)) { JSONSerializer.serialize(bigInteger, config) }
    }

    @Test fun `BigDecimal should return JSONDecimal`() {
        val bigDecString = "12345678901234567890.88888"
        val bigDecimal = BigDecimal(bigDecString)
        expect(JSONDecimal(bigDecString)) { JSONSerializer.serialize(bigDecimal) }
    }

    @Test fun `BigDecimal should return JSONString when config option selected`() {
        val bigDecString = "12345678901234567890.88888"
        val bigDecimal = BigDecimal(bigDecString)
        val config = JSONConfig().apply {
            bigDecimalString = true
        }
        expect(JSONString(bigDecString)) { JSONSerializer.serialize(bigDecimal, config) }
    }

    @Test fun `BitSet should return JSONArray`() {
        val bitSet = BitSet(4)
        bitSet.set(1)
        bitSet.set(3)
        val expected = JSONArray().apply {
            addValue(1)
            addValue(3)
        }
        expect(expected) { JSONSerializer.serialize(bitSet) }
    }

    @Test fun `Simple data class should return JSONObject`() {
        val obj = Dummy1("abc", 123)
        val expected = JSONObject().apply {
            putValue("field1", "abc")
            putValue("field2", 123)
        }
        expect(expected) { JSONSerializer.serialize(obj) }
    }

    @Test fun `Simple data class with extra property should return JSONObject`() {
        val obj = Dummy2("abc", 123)
        obj.extra = "qqqqq"
        val expected = JSONObject().apply {
            putValue("field1", "abc")
            putValue("field2", 123)
            putValue("extra", "qqqqq")
        }
        expect(expected) { JSONSerializer.serialize(obj) }
    }

    @Test fun `Simple data class with optional field should omit null field`() {
        val obj = Dummy2("abc", 123)
        obj.extra = null
        val expected = JSONObject().apply {
            putValue("field1", "abc")
            putValue("field2", 123)
        }
        expect(expected) { JSONSerializer.serialize(obj) }
    }

    @Test fun `Simple data class with optional field should include null field when config set`() {
        val obj = Dummy2("abc", 123)
        obj.extra = null
        val expected = JSONObject().apply {
            putValue("field1", "abc")
            putValue("field2", 123)
            putNull("extra")
        }
        val config = JSONConfig().apply {
            includeNulls = true
        }
        expect(expected) { JSONSerializer.serialize(obj, config) }
    }

    @Test fun `Derived class should return JSONObject`() {
        val obj = Derived()
        obj.field1 = "qwerty"
        obj.field2 = 98765
        obj.field3 = 0.012
        val expected = JSONObject().apply {
            putValue("field1", "qwerty")
            putValue("field2", 98765)
            putValue("field3", 0.012)
        }
        expect(expected) { JSONSerializer.serialize(obj) }
    }

    @Test fun `Annotated class should return JSONObject using specified name`() {
        val obj = DummyWithNameAnnotation()
        obj.field1 = "qwerty"
        obj.field2 = 98765
        val expected = JSONObject().apply {
            putValue("field1", "qwerty")
            putValue("fieldX", 98765)
        }
        expect(expected) { JSONSerializer.serialize(obj) }
    }

    @Test fun `Annotated data class should return JSONObject using specified name`() {
        val obj = DummyWithParamNameAnnotation("abc", 123)
        val expected = JSONObject().apply {
            putValue("field1", "abc")
            putValue("fieldX", 123)
        }
        expect(expected) { JSONSerializer.serialize(obj) }
    }

    @Test fun `Annotated data class with custom annotation should return JSONObject using specified name`() {
        val obj = DummyWithCustomNameAnnotation("abc", 123)
        val config = JSONConfig().apply {
            addNameAnnotation(CustomName::class, "symbol")
        }
        val expected = JSONObject().apply {
            putValue("field1", "abc")
            putValue("fieldX", 123)
        }
        expect(expected) { JSONSerializer.serialize(obj, config) }
    }

    @Test fun `Nested class should return nested JSONObject`() {
        val obj1 = Dummy1("asdfg", 987)
        val obj3 = Dummy3(obj1, "what?")
        val expected1 = JSONObject().apply {
            putValue("field1", "asdfg")
            putValue("field2", 987)
        }
        val expected = JSONObject().apply {
            put("dummy1", expected1)
            putValue("text", "what?")
        }
        expect(expected) { JSONSerializer.serialize(obj3) }
    }

    @Test fun `Class with @JSONIgnore should return JSONObject skipping field`() {
        val obj = DummyWithIgnore("alpha", "beta", "gamma")
        val expected = JSONObject().apply {
            putValue("field1", "alpha")
            putValue("field3", "gamma")
        }
        expect(expected) { JSONSerializer.serialize(obj) }
    }

    @Test fun `Class with custom ignore annotation should return JSONObject skipping field`() {
        val obj = DummyWithCustomIgnore("alpha", "beta", "gamma")
        val config = JSONConfig().apply {
            addIgnoreAnnotation(CustomIgnore::class)
        }
        val expected = JSONObject().apply {
            putValue("field1", "alpha")
            putValue("field3", "gamma")
        }
        expect(expected) { JSONSerializer.serialize(obj, config) }
    }

    @Test fun `Class with @JSONIncludeIfNull should include null field`() {
        val obj = DummyWithIncludeIfNull("alpha", null, "gamma")
        val expected = JSONObject().apply {
            putValue("field1", "alpha")
            putNull("field2")
            putValue("field3", "gamma")
        }
        expect(expected) { JSONSerializer.serialize(obj) }
    }

    @Test fun `Class with custom include if null annotation should include null field`() {
        val obj = DummyWithCustomIncludeIfNull("alpha", null, "gamma")
        val config = JSONConfig().apply {
            addIncludeIfNullAnnotation(CustomIncludeIfNull::class)
        }
        val expected = JSONObject().apply {
            putValue("field1", "alpha")
            putNull("field2")
            putValue("field3", "gamma")
        }
        expect(expected) { JSONSerializer.serialize(obj, config) }
    }

    @Test fun `Class with @JSONIncludeAllProperties should include null field`() {
        val obj = DummyWithIncludeAllProperties("alpha", null, "gamma")
        val expected = JSONObject().apply {
            putValue("field1", "alpha")
            putNull("field2")
            putValue("field3", "gamma")
        }
        expect(expected) { JSONSerializer.serialize(obj) }
    }

    @Test fun `Class with custom include all properties annotation should include null field`() {
        val obj = DummyWithCustomIncludeAllProperties("alpha", null, "gamma")
        val config = JSONConfig().apply {
            addIncludeAllPropertiesAnnotation(CustomIncludeAllProperties::class)
        }
        val expected = JSONObject().apply {
            putValue("field1", "alpha")
            putNull("field2")
            putValue("field3", "gamma")
        }
        expect(expected) { JSONSerializer.serialize(obj, config) }
    }

    @Test fun `Pair should return JSONArray`() {
        val pair = "xyz" to "abc"
        val expected = JSONArray().apply {
            add(JSONString("xyz"))
            add(JSONString("abc"))
        }
        expect(expected) { JSONSerializer.serialize(pair) }
    }

    @Test fun `Triple should return JSONArray`() {
        val triple = Triple("xyz","abc","def")
        val expected = JSONArray().apply {
            add(JSONString("xyz"))
            add(JSONString("abc"))
            add(JSONString("def"))
        }
        expect(expected) { JSONSerializer.serialize(triple) }
    }

    @Test fun `Heterogenous Triple should return JSONArray`() {
        val triple = Triple("xyz",88,"def")
        val expected = JSONArray().apply {
            add(JSONString("xyz"))
            add(JSONInt(88))
            add(JSONString("def"))
        }
        expect(expected) { JSONSerializer.serialize(triple) }
    }

    @Test fun `object should return JSONObject()`() {
        val obj = DummyObject
        expect(JSONObject().putValue("field1", "abc")) { JSONSerializer.serialize(obj) }
    }

    @Test fun `nested object should return JSONObject()`() {
        val obj = NestedDummy()
        val nested = JSONObject().putValue("field1", "abc")
        expect(JSONObject().putJSON("obj", nested)) { JSONSerializer.serialize(obj) }
    }

    @Test fun `class with constant val should serialize correctly`() {
        val constClass = DummyWithVal()
        expect(JSONObject().putValue("field8", "blert")) { JSONSerializer.serialize(constClass) }
    }

    @Test fun `java class should serialize correctly`() {
        val javaClass1 = JavaClass1(1234, "Hello!")
        val expected = JSONObject().apply {
            putValue("field1", 1234)
            putValue("field2", "Hello!")
        }
        expect(expected) { JSONSerializer.serialize(javaClass1) }
    }

    @Test fun `List derived class should serialize to JSONArray`() {
        val obj = DummyList(listOf(LocalDate.of(2019, 10, 6), LocalDate.of(2019, 10, 5)))
        val expected = JSONArray().apply {
            addValue("2019-10-06")
            addValue("2019-10-05")
        }
        expect(expected) { JSONSerializer.serialize(obj) }
    }

    @Test fun `Map derived class should serialize to JSONObject`() {
        val obj = DummyMap(emptyMap()).apply {
            put("aaa", LocalDate.of(2019, 10, 6))
            put("bbb", LocalDate.of(2019, 10, 5))
        }
        val expected = JSONObject().apply {
            putValue("aaa", "2019-10-06")
            putValue("bbb", "2019-10-05")
        }
        expect(expected) { JSONSerializer.serialize(obj) }
    }

    @Test fun `sealed class should serialize with extra member to indicate derived class`() {
        val expected = JSONObject().apply {
            putValue("class", "Const")
            putValue("number", 2.0)
        }
        expect(expected) { JSONSerializer.serialize(Const(2.0)) }
    }

    @Test fun `sealed class object should serialize correctly`() {
        val expected = JSONObject().apply {
            putValue("class", "NotANumber")
        }
        expect(expected) { JSONSerializer.serialize(NotANumber) }
    }

    @Test fun `sealed class should serialize with custom discriminator`() {
        val config = JSONConfig().apply {
            sealedClassDiscriminator = "?"
        }
        val expected = JSONObject().apply {
            putValue("?", "Const")
            putValue("number", 2.0)
        }
        expect(expected) { JSONSerializer.serialize(Const(2.0), config) }
    }

    @Test fun `should fail on use of circular reference`() {
        val circular1 = Circular1()
        val circular2 = Circular2()
        circular1.ref = circular2
        circular2.ref = circular1
        val exception = assertFailsWith<JSONKotlinException> {
            JSONSerializer.serialize(circular1)
        }
        expect("Circular reference: field ref in Circular2") { exception.message }
    }

    @Test fun `should omit null members`() {
        val dummy5 = Dummy5(null, 123)
        val serialized = JSONSerializer.serialize(dummy5)
        expect(true) { serialized is JSONObject }
        expect(1) { (serialized as JSONObject).size }
    }

    @Test fun `should serialize Java Stream of strings`() {
        val stream = Stream.of("abc", "def")
        val serialized = JSONSerializer.serialize(stream)
        expect(true) { serialized is JSONSequence<*> }
        with(serialized as JSONSequence<*>) {
            expect(2) { size }
            expect(JSONString("abc")) { get(0) }
            expect(JSONString("def")) { get(1) }
        }
    }

    @Test fun `should serialize Java IntStream`() {
        val stream = IntStream.of(987, 654, 321)
        val serialized = JSONSerializer.serialize(stream)
        expect(true) { serialized is JSONSequence<*> }
        with(serialized as JSONSequence<*>) {
            expect(3) { size }
            expect(JSONInt(987)) { get(0) }
            expect(JSONInt(654)) { get(1) }
            expect(JSONInt(321)) { get(2) }
        }
    }

    private fun intEquals(a: Int, b: Int): Boolean {
        return a == b
    }

    private fun longEquals(a: Long, b: Long): Boolean {
        return a == b
    }

    private fun floatEquals(a: Float, b: Float): Boolean {
        return abs(a - b) < 0.0000001
    }

    private fun doubleEquals(a: Double, b: Double): Boolean {
        return abs(a - b) < 0.000000001
    }

}
<file_sep>/*
 * @(#) JSONConfigTest.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019, 2020, 2021 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.reflect.full.createType
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.expect
import kotlin.test.Test
import kotlin.test.assertTrue
import kotlin.test.fail

class JSONConfigTest {

    private val stringType = String::class.createType()

    @Test fun `add fromJSON mapping should return the function`() {
        val config = JSONConfig()
        expect(8192) { config.readBufferSize }
        expect(Charsets.UTF_8) { config.charset }
        assertNull(config.findFromJSONMapping(stringType))
        assertNull(config.findFromJSONMapping(String::class))
        config.fromJSON { json -> json?.toString() }
        assertNotNull(config.findFromJSONMapping(stringType))
        assertNotNull(config.findFromJSONMapping(String::class))
    }

    @Test fun `fromJSON mapping should map simple data class`() {
        val config = JSONConfig().apply {
            fromJSON { json ->
                if (json !is JSONObject)
                    fail("Must be JSONObject")
                Dummy1(json.getString("a"), json.getInt("b"))
            }
        }
        val json = JSONObject().apply {
            putValue("a", "xyz")
            putValue("b", 888)
        }
        expect(Dummy1("xyz", 888)) { JSONDeserializer.deserialize(Dummy1::class.createType(), json, config) }
    }

    @Test fun `fromJSON mapping should not interfere with other deserialization`() {
        val config = JSONConfig().apply {
            fromJSON { json ->
                if (json !is JSONObject)
                    fail("Must be JSONObject")
                Dummy1(json.getString("a"), json.getInt("b"))
            }
        }
        val json = JSONArray(JSONString("AAA"), JSONString("BBB"))
        val result = JSONDeserializer.deserialize<List<Any>>(json, config)
        expect(listOf("AAA", "BBB")) { result }
    }

    @Test fun `fromJSON mapping should map nested class`() {
        val config = JSONConfig().apply {
            fromJSON { json ->
                if (json !is JSONObject)
                    fail("Must be JSONObject")
                Dummy1(json.getString("a"), json.getInt("b"))
            }
        }
        val json1 = JSONObject().apply {
            putValue("a", "xyz")
            putValue("b", 888)
        }
        val json2 = JSONObject().apply {
            put("dummy1", json1)
            putValue("text", "Hello!")
        }
        expect(Dummy3(Dummy1("xyz", 888), "Hello!")) {
            JSONDeserializer.deserialize(Dummy3::class.createType(), json2, config)
        }
    }

    @Test fun `add toJSON mapping should return the function`() {
        val config = JSONConfig()
        assertNull(config.findToJSONMapping(stringType))
        assertNull(config.findToJSONMapping(String::class))
        config.toJSON<String> { str -> JSONString(str ?: fail("String expected")) }
        assertNotNull(config.findToJSONMapping(stringType))
        assertNotNull(config.findToJSONMapping(String::class))
    }

    @Test fun `toJSON mapping should map simple data class`() {
        val config = JSONConfig().apply {
            toJSON<Dummy1> { obj ->
                obj?.let {
                    JSONObject().apply {
                        putValue("a", it.field1)
                        putValue("b", it.field2)
                    }
                }
            }
        }
        val expected = JSONObject().apply {
            putValue("a", "xyz")
            putValue("b", 888)
        }
        expect(expected) { JSONSerializer.serialize(Dummy1("xyz", 888), config) }
    }

    @Test fun `toJSON mapping should map nested class`() {
        val config = JSONConfig().apply {
            toJSON<Dummy1> { obj ->
                obj?.let {
                    JSONObject().apply {
                        putValue("a", it.field1)
                        putValue("b", it.field2)
                    }
                }
            }
        }
        val dummy1 = JSONObject().apply {
            putValue("a", "xyz")
            putValue("b", 888)
        }
        val expected = JSONObject().apply {
            putJSON("dummy1", dummy1)
            putValue("text", "Hi there!")
        }
        expect(expected) { JSONSerializer.serialize(Dummy3(Dummy1("xyz", 888), "Hi there!"), config) }
    }

    @Test fun `toJSON mapping should be transferred on combineMappings`() {
        val config = JSONConfig().apply {
            toJSON<Dummy1> { obj ->
                obj?.let {
                    JSONObject().apply {
                        putValue("a", it.field1)
                        putValue("b", it.field2)
                    }
                }
            }
        }
        val config2 = JSONConfig().apply {
            combineMappings(config)
        }
        val expected = JSONObject().apply {
            putValue("a", "xyz")
            putValue("b", 888)
        }
        expect(expected) { JSONSerializer.serialize(Dummy1("xyz", 888), config2) }
    }

    @Test fun `toJSON mapping should be transferred on combineAll`() {
        val config = JSONConfig().apply {
            toJSON<Dummy1> { obj ->
                obj?.let {
                    JSONObject().apply {
                        putValue("a", it.field1)
                        putValue("b", it.field2)
                    }
                }
            }
        }
        val config2 = JSONConfig().apply {
            combineAll(config)
        }
        val expected = JSONObject().apply {
            putValue("a", "xyz")
            putValue("b", 888)
        }
        expect(expected) { JSONSerializer.serialize(Dummy1("xyz", 888), config2) }
    }

    @Test fun `JSONName annotation should be transferred on combineAll`() {
        val obj = DummyWithCustomNameAnnotation("abc", 123)
        val config = JSONConfig().apply {
            addNameAnnotation(CustomName::class, "symbol")
        }
        val config2 = JSONConfig().apply {
            combineAll(config)
        }
        val expected = JSONObject().apply {
            putValue("field1", "abc")
            putValue("fieldX", 123)
        }
        expect(expected) { JSONSerializer.serialize(obj, config2) }
    }

    @Test fun `Switch settings and numeric values should be transferred on combineAll`() {
        val config = JSONConfig().apply {
            bigIntegerString = true
            bigDecimalString = true
            includeNulls = true
            allowExtra = true
            streamOutput = true
            readBufferSize = 16384
            stringifyInitialSize = 512
        }
        val config2 = JSONConfig().apply {
            combineAll(config)
        }
        expect(true) { config2.bigIntegerString }
        expect(true) { config2.bigDecimalString }
        expect(true) { config2.includeNulls }
        expect(true) { config2.allowExtra }
        expect(true) { config2.streamOutput }
        expect(16384) { config2.readBufferSize }
        expect(512) { config2.stringifyInitialSize }
    }

    @Test fun `toJSON mapping of nullable type should be selected correctly`() {
        val config = JSONConfig().apply {
            toJSON(Dummy1::class.createType(nullable = true)) { JSONString("A") }
        }
        expect(JSONString("A")) { JSONSerializer.serialize(Dummy1("X", 0), config) }
    }

    @Test fun `toJSON mapping of non-nullable type should be selected correctly`() {
        val config = JSONConfig().apply {
            toJSON(Dummy1::class.createType(nullable = false)) { JSONString("A") }
        }
        expect(JSONString("A")) { JSONSerializer.serialize(Dummy1("X", 0), config) }
    }

    @Test fun `toJSON mapping should select correct function among derived classes`() {
        val config = JSONConfig().apply {
            toJSON<DummyA> { JSONString("A") }
            toJSON<DummyB> { JSONString("B") }
            toJSON<DummyC> { JSONString("C") }
            toJSON<DummyD> { JSONString("D") }
        }
        expect(JSONString("A")) { JSONSerializer.serialize(DummyA(), config)}
        expect(JSONString("B")) { JSONSerializer.serialize(DummyB(), config)}
        expect(JSONString("C")) { JSONSerializer.serialize(DummyC(), config)}
        expect(JSONString("D")) { JSONSerializer.serialize(DummyD(), config)}
    }

    @Test fun `toJSON mapping should select correct function when order is reversed`() {
        val config = JSONConfig().apply {
            toJSON<DummyD> { JSONString("D") }
            toJSON<DummyC> { JSONString("C") }
            toJSON<DummyB> { JSONString("B") }
            toJSON<DummyA> { JSONString("A") }
        }
        expect(JSONString("A")) { JSONSerializer.serialize(DummyA(), config)}
        expect(JSONString("B")) { JSONSerializer.serialize(DummyB(), config)}
        expect(JSONString("C")) { JSONSerializer.serialize(DummyC(), config)}
        expect(JSONString("D")) { JSONSerializer.serialize(DummyD(), config)}
    }

    @Test fun `toJSON mapping should select correct function when exact match not present`() {
        val config = JSONConfig().apply {
            toJSON<DummyA> { JSONString("A") }
            toJSON<DummyB> { JSONString("B") }
            toJSON<DummyC> { JSONString("C") }
        }
        expect(JSONString("A")) { JSONSerializer.serialize(DummyA(), config)}
        expect(JSONString("B")) { JSONSerializer.serialize(DummyB(), config)}
        expect(JSONString("C")) { JSONSerializer.serialize(DummyC(), config)}
        expect(JSONString("C")) { JSONSerializer.serialize(DummyD(), config)}
    }

    @Test fun `fromJSON mapping should select correct function among derived classes`() {
        val config = JSONConfig().apply {
            fromJSON { if (it == JSONString("A")) DummyA() else fail() }
            fromJSON { if (it == JSONString("B")) DummyB() else fail() }
            fromJSON { if (it == JSONString("C")) DummyC() else fail() }
            fromJSON { if (it == JSONString("D")) DummyD() else fail() }
        }
        assertTrue(JSONDeserializer.deserialize(DummyA::class.createType(), JSONString("A"), config) is DummyA)
        assertTrue(JSONDeserializer.deserialize(DummyB::class.createType(), JSONString("B"), config) is DummyB)
        assertTrue(JSONDeserializer.deserialize(DummyC::class.createType(), JSONString("C"), config) is DummyC)
        assertTrue(JSONDeserializer.deserialize(DummyD::class.createType(), JSONString("D"), config) is DummyD)
    }

    @Test fun `fromJSON mapping should select correct function when order is reversed`() {
        val config = JSONConfig().apply {
            fromJSON { if (it == JSONString("D")) DummyD() else fail() }
            fromJSON { if (it == JSONString("C")) DummyC() else fail() }
            fromJSON { if (it == JSONString("B")) DummyB() else fail() }
            fromJSON { if (it == JSONString("A")) DummyA() else fail() }
        }
        assertTrue(JSONDeserializer.deserialize(DummyA::class.createType(), JSONString("A"), config) is DummyA)
        assertTrue(JSONDeserializer.deserialize(DummyB::class.createType(), JSONString("B"), config) is DummyB)
        assertTrue(JSONDeserializer.deserialize(DummyC::class.createType(), JSONString("C"), config) is DummyC)
        assertTrue(JSONDeserializer.deserialize(DummyD::class.createType(), JSONString("D"), config) is DummyD)
    }

    @Test fun `fromJSON mapping should select correct function when exact match not present`() {
        val config = JSONConfig().apply {
            fromJSON { if (it == JSONString("B")) DummyB() else fail() }
            fromJSON { if (it == JSONString("C")) DummyC() else fail() }
            fromJSON { if (it == JSONString("D")) DummyD() else fail() }
        }
        assertTrue(JSONDeserializer.deserialize(DummyA::class.createType(), JSONString("B"), config) is DummyB)
        assertTrue(JSONDeserializer.deserialize(DummyB::class.createType(), JSONString("B"), config) is DummyB)
        assertTrue(JSONDeserializer.deserialize(DummyC::class.createType(), JSONString("C"), config) is DummyC)
        assertTrue(JSONDeserializer.deserialize(DummyD::class.createType(), JSONString("D"), config) is DummyD)
    }

    @Test fun `toJSON mapping with JSONConfig toJSONString should use toString`() {
        val config = JSONConfig().apply {
            toJSONString<Dummy9>()
        }
        expect(JSONString("abcdef")) { JSONSerializer.serialize(Dummy9("abcdef"), config) }
    }

    @Test fun `fromJSON mapping with JSONConfig fromJSONString should use String constructor`() {
        val config = JSONConfig().apply {
            fromJSONString<Dummy9>()
        }
        expect(Dummy9("abcdef")) { JSONDeserializer.deserialize<Dummy9>(JSONString("abcdef"), config) }
    }

    @Test fun `multiple JSONConfig mappings using apply should work correctly`() {
        val config = JSONConfig().apply {
            toJSON<Dummy1> { obj ->
                obj?.let {
                    JSONObject().apply {
                        putValue("a", it.field1)
                        putValue("b", it.field2)
                    }
                }
            }
            fromJSON { json ->
                require(json is JSONObject) { "Must be JSONObject" }
                Dummy1(json.getString("a"), json.getInt("b"))
            }
            toJSON<Dummy3> { obj ->
                obj?.let {
                    JSONObject().let { json ->
                        json["dummy1"] = JSONSerializer.serialize(it.dummy1, this)
                        json.putValue("text", it.text)
                    }
                }
            }
            fromJSON { json ->
                require(json is JSONObject) { "Must be JSONObject" }
                Dummy3(JSONDeserializer.deserialize(json.getObject("dummy1"), this) ?: fail(), json.getString("text"))
            }
        }
        val json1 = JSONObject().apply {
            putValue("a", "xyz")
            putValue("b", 888)
        }
        val dummy1 = Dummy1("xyz", 888)
        expect(json1) { JSONSerializer.serialize(dummy1, config) }
        expect(dummy1) { JSONDeserializer.deserialize<Dummy1>(json1, config) }
        val json3 = JSONObject().apply {
            put("dummy1", json1)
            putValue("text", "excellent")
        }
        val dummy3 = Dummy3(dummy1, "excellent")
        expect(json3) { JSONSerializer.serialize(dummy3, config) }
        expect(dummy3) { JSONDeserializer.deserialize<Dummy3>(json3, config) }
    }

}
<file_sep># json-kotlin

[![Build Status](https://travis-ci.com/pwall567/json-kotlin.svg?branch=master)](https://travis-ci.com/github/pwall567/json-kotlin)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Kotlin](https://img.shields.io/static/v1?label=Kotlin&message=v1.7.21&color=7f52ff&logo=kotlin&logoColor=7f52ff)](https://github.com/JetBrains/kotlin/releases/tag/v1.7.21)
[![Maven Central](https://img.shields.io/maven-central/v/net.pwall.json/json-kotlin?label=Maven%20Central)](https://search.maven.org/search?q=g:%22net.pwall.json%22%20AND%20a:%22json-kotlin%22)

JSON serialization and deserialization for Kotlin.

This document provides introductory information on the `json-kotlin` library; fuller information is available in the
[User Guide](USERGUIDE.md).

## IMPORTANT

This project is in the process of being superseded by [`kjson`](https://github.com/pwall567/kjson).
New users are encouraged to use that project instead of this one; existing users are encouraged to migrate when
convenient.

This library will continue to be maintained, but enhancements will be added to the new library first, for example
[an improved way of specifying polymorphic class deserialization](https://github.com/pwall567/kjson/blob/main/CUSTOM.md#fromjsonpolymorphic).

## Background

This library provides JSON serialization and deserialization functionality for Kotlin.
It uses Kotlin reflection to serialize and deserialize arbitrary objects, and it includes code to handle most of the
Kotlin standard library classes.

When instantiating deserialized objects it does not require the class to have a no-argument constructor, and unlike some
JSON libraries it does not use the `sun.misc.Unsafe` class to force instantiation.

## Supported Classes

Support is included for the following standard Kotlin classes:

- `String`, `StringBuilder`, `CharSequence`, `Char`, `CharArray`
- `Int`, `Long`, `Short`, `Byte`, `Double`, `Float`
- `Array`, `IntArray`, `LongArray`, `ShortArray`, `ByteArray`, `DoubleArray`, `FloatArray`
- `Boolean`, `BooleanArray`
- `Collection`, `List`, `ArrayList`, `LinkedList`, `Set`, `HashSet`, `LinkedHashSet`, `Sequence`
- `Map`, `HashMap`, `LinkedHashMap`
- `Pair`, `Triple`
- `Enum`

Also, support is included for the following standard Java classes:

- `java.math.BigDecimal`, `java.math.BigInteger`
- `java.net.URI`, `java.net.URL`
- `java.util.Enumeration`, `java.util.Bitset`, `java.util.UUID`, `java.util.Date`, `java.util.Calendar`
- `java.sql.Date`, `java.sql.Time`, `java.sql.Timestamp`
- `java.time.Instant`, `java.time.LocalDate`, `java.time.LocalTime`, `java.time.LocalDateTime`,
  `java.time.OffsetTime`, `java.time.OffsetDateTime`, `java.time.ZonedDateTime`, `java.time.Year`,
  `java.time.YearMonth`, `java.time.MonthDay`, `java.time.Duration`, `java.time.Period`
- `java.util.stream.Stream`, `java.util.stream.IntStream`, `java.util.stream.LongStream`,
  `java.util.stream.DoubleStream`

## Quick Start

### Serialization

To serialize any object (say, a `data class`):
```kotlin
    val json = dataClassInstance.stringifyJSON()
```
The result `json` is a `String` serialized from the object, recursively serializing any nested objects, collections
etc.
The JSON object will contain serialized forms of all of the properties of the object (as declared in `val` and `var`
statements).

For example, given the class:
```kotlin
    data class Example(val abc: String, val def: Int, val ghi: List<String>)
```
and the instantiation:
```kotlin
    val example = Example("hello", 12345, listOf("A", "B"))
```
then
```kotlin
    val json = example.stringifyJSON()
```
will yield:
```json
{"abc":"hello","def":12345,"ghi":["A","B"]}
```

### Deserialization

Deserialization is slightly more complicated, because the target data type must be specified to the function.
This can be achieved in a number of ways (the following examples assume `jsonString` is a `String` containing JSON):

The type can be inferred from the context:
```kotlin
    val example: Example? = jsonString.parseJSON()
```

The type may be specified as a type parameter:
```kotlin
    val example = jsonString.parseJSON<Example>()
```

The type may be specified as a `KClass`:
```kotlin
    val example = jsonString.parseJSON(Example::class)
```

The type may be specified as a `KType`:
```kotlin
    val example = jsonString.parseJSON(Example::class.starProjectedType) as Example
```
(This form is generally only needed when deserializing parameterized types where the parameter types can not be
inferred; the `as` expression is needed because `KType` does not convey inferred type information.)

Because of the limitations caused by [type erasure](https://kotlinlang.org/docs/reference/generics.html#type-erasure),
when deserializing parameterized types (like generic collections), the above forms will not always convey sufficient
information.
For these cases, the `JSONDelegate` class provides a valuable mechanism:
```kotlin
    val listExample: List<Example> by JSONDelegate(jsonList)
```
or:
```kotlin
    val listExample by JSONDelegate<List<Example>>(jsonList)
```

## Sealed Classes

The library will handle Kotlin sealed classes.
See the [User Guide](USERGUIDE.md#sealed-classes) for more details.

## Customization

### Annotations

#### Change the name used for a property

When serializing or deserializing a Kotlin object, the property name discovered by reflection will be used as the name
in the JSON object.
An alternative name may be specified if required, by the use of the `@JSONName` annotation:
```kotlin
    data class Example(val abc: String, @JSONName("xyz") val def: Int)
```

#### Ignore a property on serialization

If it is not necessary (or desirable) to output a particular field, the `@JSONIgnore` annotation may be used to prevent
serialization:
```kotlin
    data class Example(val abc: String, @Ignore val def: Int)
```

#### Include properties when null

If a property is `null`, the default behaviour when serializing is to omit the property from the output object.
If this behaviour is not desired, the property may be annotated with the `@JSONIncludeIfNull` annotation to indicate
that it is to be included even if `null`:
```kotlin
    data class Example(@JSONIncludeIfNull val abc: String?, val def: Int)
```

To indicate that all properties in a class are to be included in the output even if null, the
`@JSONIncludeAllProperties` may be used on the class:
```kotlin
    @JSONIncludeAllProperties
    data class Example(val abc: String?, val def: Int)
```

And to specify that all properties in all classes are to be output if null, the `includeNulls` flag may be set in the
`JSONConfig`:
```kotlin
    val config = JSONConfig().apply {
        includeNulls = true
    }
    val json = example.stringifyJSON(config)
```

#### Allow extra properties in a class to be ignored

The default behaviour when extra properties are found during deserialization is to throw an exception.
To allow (and ignore) any extra properties, the `@JSONAllowExtra` annotation may be added to the class:
```kotlin
    @JSONAllowExtra
    data class Example(val abc: String, val def: Int)
```

To allow (and ignore) extra properties throughout the deserialization process, the `allowExtra` flag may be set in the
`JSONConfig`:
```kotlin
    val config = JSONConfig().apply {
        allowExtra = true
    }
    val json = example.stringifyJSON(config)
```

#### Using existing tags from other software

If you have classes that already contain annotations for the above purposes, you can tell `json-kotlin` to use
those annotations by specifying them in a `JSONConfig`:
```kotlin
    val config = JSONConfig().apply {
        addNameAnnotation(MyName::class, "name")
        addIgnoreAnnotation(MyIgnore::class)
        addIncludeIfNullAnnotation(MyIncludeIfNull::class)
        addIncludeAllPropertiesAnnotation(MyIncludeAllProperties::class)
        addAllowExtraPropertiesAnnotation(MyAllowExtraProperties::class)
    }
    val json = example.stringifyJSON(config)
```

The `JSONConfig` may be supplied as an optional final argument on most `json-kotlin` function calls (see the KDoc or
source for more details).

### Custom Serialization

The `JSONConfig` is also used to specify custom serialization:
```kotlin
    val config = JSONConfig().apply {
        toJSON<Example> { obj ->
            obj?.let {
                JSONObject().apply {
                    putValue("custom1", it.abc)
                    putValue("custom2", it.def)
                }
            }
        }
    }
```
Or deserialization:
```kotlin
    val config = JSONConfig().apply {
        fromJSON { json ->
            require(json is JSONObject) { "Must be JSONObject" }
            Example(json.getString("custom1"), json.getInt("custom2"))
        }
    }
```

The `toJSON` function must supply a lambda will the signature `(Any?) -> JSONValue?` and the `fromJSON` function must
supply a lambda with the signature `(JSONValue?) -> Any?`.
`JSONValue` is the interface implemented by each node in the `jsonutil` library (see below).

Both `toJSON` and `fromJSON` may be specified repeatedly in the same `JSONConfig` to cover multiple classes.

## Mixed Kotlin and Java

If you need to serialize or deserialize a Kotlin class from Java, the `JSONJava` class provides this capability while
still retaining all the Kotlin functionality, like Kotlin-specific classes and nullability checking:
```
    String json = JSONJava.stringify(example);
```
Or:
```
    Example example = JSONJava.parse(Example.class, json);
```

## More Detail

The deserialization functions operate as a two-stage process.
The JSON string is first parsed into an internal form using the
[`jsonutil`](https://github.com/pwall567/jsonutil) library ([Javadoc](https://pwall.net/oss/jsonutil/));
the resulting tree of `JSONValue` objects is then traversed to create the desired classes.

(Note that the
[`json-stream`](https://github.com/pwall567/json-stream) library and its non-blocking coroutine-aware version
[`json-co-stream`](https://github.com/pwall567/json-co-stream) may be used to parse a stream of JSON data on-the-fly,
and in this case, each object may be converted to the required target form as its last character is read.)

It is possible to perform serialization using the same two-stage approach, but from version 3.2 of this library onwards,
the `JSONStringify` functions are used to stringify direct to a string, or, if the `appendJSON()` function is used, to
any form of `Appendable` including the various `Writer` classes.
As always, the KDoc, the source or the unit test classes provide more information.

This information is of significance when custom serialization and deserialization are required.
Regardless of whether the `JSONStringify` functions are used to output directly to a string, the custom serialization
is still required to create the internal `JSONValue`-based form.
This ensures that errant serialization functions don't disrupt the remainder of the JSON, for example by omitting a
trailing quote or bracket character.

## Breaking changes

**Version 2.0** introduced a change to `JSONConfig` which makes it incompatible with earlier versions - the functions to
add information to the `JSONConfig` object (e.g. serialization and deserialization mappings) no longer return the object
itself for chaining purposes.
The recommended approach to perform repeat actions such as this is to use the Kotlin `apply {}` function.

If there is anyone affected by this change (unlikely, I know!) version 1.2 is still available.

Also, **version 3.8** changed the visibility of an internal function in `JSONDeserializer` from `public` to `private`
(and changed its signature, although that's hardly relevant if it's no longer public).
It was never intended that this function would be part of the published API, so the "major version" has not been
incremented as would normally be the case for a breaking change.

**Version 4.0** changes the content of error messages in exceptions, and thus may be a breaking change for any code that
is dependent on the precise text of the message.
In particular:
- Most messages now include a `JSONPointer` when appropriate, showing the location in the JSON where the error occurred.
- Errors on deserialization of objects using constructor parameters now include more detail.

## Dependency Specification

The latest version of the library is 4.8, and it may be obtained from the Maven Central repository.

### Maven
```xml
    <dependency>
      <groupId>net.pwall.json</groupId>
      <artifactId>json-kotlin</artifactId>
      <version>4.8</version>
    </dependency>
```
### Gradle
```groovy
    implementation 'net.pwall.json:json-kotlin:4.8'
```
### Gradle (kts)
```kotlin
    implementation("net.pwall.json:json-kotlin:4.8")
```

<NAME>

2023-07-10
<file_sep>/*
 * @(#) JSONAutoTest.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.reflect.full.starProjectedType
import kotlin.test.expect
import kotlin.test.Test

import java.lang.reflect.Type

/**
 * Tests for [JSONAuto].  These tests are somewhat rudimentary because [JSONAuto] simply delegates requests to
 * [JSONSerializer] and [JSONDeserializer], and those classes are extensively tested separately.
 *
 * @author  <NAME>
 */
class JSONAutoTest {

    @Test fun `JSONAuto should correctly stringify an object`() {
        val obj = Dummy1("abdef", 54321)
        val expected = JSONObject().apply {
            putValue("field1", "abdef")
            putValue("field2", 54321)
        }
        expect(expected) { JSON.parse(JSONAuto.stringify(obj)) }
    }

    @Test fun `JSONAuto should parse a string to create an object`() {
        val json = """{"field1":"abdef","field2":54321}"""
        val expected: Dummy1? = Dummy1("abdef", 54321)
        expect(expected) { JSONAuto.parse(json) }
    }

    @Test fun `JSONAuto should parse a string to create an object with explicit type`() {
        val json = """{"field1":"abdef","field2":54321}"""
        expect(Dummy1("abdef", 54321)) { JSONAuto.parse(Dummy1::class.starProjectedType, json) }
    }

    @Test fun `JSONAuto should parse List using parameterised type`() {
        val json = """[{"field1":"abcdef","field2":567},{"field1":"qwerty","field2":9999}]"""
        val expected = listOf(Dummy1("abcdef", 567), Dummy1("qwerty", 9999))
        expect(expected) { JSONAuto.parse<List<Dummy1>>(json) }
    }

    @Test fun `JSONAuto should parse List using Java Type`() {
        val json = """[{"field1":567,"field2":"abcdef"},{"field1":9999,"field2":"qwerty"}]"""
        val type: Type = JavaClass2::class.java.getField("field1").genericType
        expect(listOf(JavaClass1(567, "abcdef"), JavaClass1(9999, "qwerty"))) { JSONAuto.parse(type, json) }
    }

}
<file_sep># Custom Serialization and Deserialization - `json-kotlin`

## Background

The library will use the obvious JSON serializations for the main Kotlin data types &mdash; `String`, `Int`, `Boolean`
etc.
For arrays and collections, it wil use the JSON array or object syntax, recursively calling the library to process the
items.

Also, there are a number of standard classes for which there is an obvious JSON representation, for example `LocalDate`
and `UUID` (for a complete list, see the [User Guide](USERGUIDE.md#type-mapping)).

But the dominant use of `json-kotlin` is expected to be for the serialization and deserialization of user-defined
classes, particularly Kotlin data classes.
In these cases, the library will serialize or deserialize the Kotlin object as a JSON object using the Kotlin property
names, recursively invoking the library to serialize or deserialize the properties (see the
[User Guide](USERGUIDE.md#deserialization) for more details).

If this is not the required behaviour, custom serialization and deserialization may be used to tailor the JSON
representation to the form needed.

## Serialization

Custom serialization converts the object to a `JSONValue`; the library will convert the `JSONValue` to string form if
that is required.
There are two ways of specifying custom serialization.

### `toJSON` function in the class

If the class of an object to be serialized has a member function named `toJSON`, taking no parameters and returning a
`JSONValue`, that function will be used for serialization.
For example:
```kotlin
class Person(val firstName: String, val surname: String) {

    fun toJSON(): JSONValue {
        return JSONString("$firstName|$surname")
    }

}
```

Note that the result need not be a `JSONObject` (although it generally will be).

### `toJSON` lambda in the `JSONConfig`

There are many cases, for example when the class is part of an external library, when the above technique is not
possible.
Also, some architectural rules may demand that there be no external dependencies in the principal classes of the
published API of a system.
In such cases, a `toJSON` lambda may be specified in the [`JSONConfig`](USERGUIDE.md#configuration).

For example, if the `Person` class above did not have a `toJSON` function:
```kotlin
    config.toJSON<Person> { person ->
        JSONString("${person.firstName}|${person.surname}")
    }
```

The type may be specified as a type parameter (as above), or as type variable:
```kotlin
    val personType = Person::class.starProjectedType
    config.toJSON(personType) { p ->
        val person = p as Person // this is necessary because there is insufficient type information in this case
        JSONString("${person.firstName}|${person.surname}")
    }
```

### `toJSONString`

This is a shortcut for `toJSON` where the lambda simply returns a `JSONString` containing a `toString()` of the object.
For Example:
```kotlin
    config.toJSONString<Person>()
```

## Deserialization

Custom deserialization converts a `JSONValue` to an object of the specified (or implied) type.
If the source is a string, it will already have been converted to a structure of `JSONValue` objects.

### `fromJSON` function in the companion object

If the target class has a companion object with a function taking a `JSONValue` and returning an object of the target
class, that function will be used for deserialization.
For example:
```kotlin
class Person(val firstName: String, val surname: String) {

    fun toJSON(): JSONValue {
        return JSONString("$firstName|$surname")
    }

    companion object {
        @Suppress("unused")
        fun fromJSON(json: JSONValue): DummyFromJSON {
            require(json is JSONString) { "Can't deserialize ${json::class} as Person" }
            val names = json.get().split('|')
            require(names.length == 2) { "Person string has incorrect format" }
            return Person(names[0], names[1])
        }
    }

}
```

### `fromJSON` lambda in the `JSONConfig`

Again, it may not be possible to modify the class, so a `fromJSON` lambda may be specified in the `JSONConfig`.
The above example may be specified as:
```kotlin
    config.fromJSON { json ->
        require(json is JSONString) { "Can't deserialize ${json::class} as Person" }
        val names = json.get().split('|')
        require(names.length == 2) { "Person string has incorrect format" }
        Person(names[0], names[1])
    }
```

The result type in this example is implied by the return type of the lambda.
As with `toJSON`, the type may be specified explicitly:
```kotlin
    val personType = Person::class.starProjectedType
    config.fromJSON(personType) { json ->
        require(json is JSONString) { "Can't deserialize ${json::class} as Person" }
        val names = json.get().split('|')
        require(names.length == 2) { "Person string has incorrect format" }
        Person(names[0], names[1])
    }
```

2021-01-03
<file_sep>/*
 * @(#) JSONSerializerFunctionsTest.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2020, 2022 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlin.test.expect

import java.time.Instant
import java.util.Calendar

import net.pwall.json.JSONSerializerFunctions.findToJSON
import net.pwall.json.JSONSerializerFunctions.formatISO8601
import net.pwall.json.JSONSerializerFunctions.isSealedSubclass
import net.pwall.json.JSONSerializerFunctions.isToStringClass

class JSONSerializerFunctionsTest {

    @Test fun `should find toJSON when it is present`() {
        assertNotNull(DummyFromJSON::class.findToJSON())
    }

    @Test fun `should return null when toJSON is not present`() {
        assertNull(Dummy1::class.findToJSON())
    }

    @Test fun `should return true when class is a subclass of a sealed class`() {
        assertTrue(NotANumber::class.isSealedSubclass())
    }

    @Test fun `should return false when class is not a subclass of a sealed class`() {
        assertFalse(Dummy1::class.isSealedSubclass())
    }

    @Test fun `should recognise a toString-able class`() {
        assertTrue(Instant::class.isToStringClass())
    }

    @Test fun `should recognise a not-toString-able class`() {
        assertFalse(Dummy1::class.isToStringClass())
    }

    @Test fun `should correctly format Calendar`() {
        val cal = Calendar.getInstance().apply {
            set(Calendar.YEAR, 2020)
            set(Calendar.MONTH, 3)
            set(Calendar.DAY_OF_MONTH, 23)
            set(Calendar.HOUR_OF_DAY, 19)
            set(Calendar.MINUTE, 25)
            set(Calendar.SECOND, 31)
            set(Calendar.MILLISECOND, 123)
            set(Calendar.ZONE_OFFSET, 10 * 60 * 60 * 1000)
        }
        expect("2020-04-23T19:25:31.123+10:00") { cal.formatISO8601() }
    }

    @Test fun `should perform reflection on system classes`() {
        val map: Map<String, String> = emptyMap()
        assertNull(map::class.findToJSON())
        val int = 1
        assertNull(int::class.findToJSON())
        val lambda: (Int) -> Int = { it }
        assertNull(lambda::class.findToJSON())
        val str = "???"
        assertNull(str::class.findToJSON())
    }

}
<file_sep># Spring and `json-kotlin`

## Default Spring Serialization and Deserialization

Many users will seek to use `json-kotlin` in conjunction with the
[Spring Framework](https://spring.io/projects/spring-framework).

If a class of the following form is present in the classpath scanned by Spring, it will be used by the framework as the
default JSON serialization and deserialization function for Spring Boot etc.

```kotlin
package my.example

import java.io.Reader
import java.io.Writer
import java.lang.reflect.Type

import org.springframework.http.MediaType
import org.springframework.http.converter.json.AbstractJsonHttpMessageConverter
import org.springframework.stereotype.Service

import net.pwall.json.JSONAuto
import net.pwall.json.JSONConfig
import net.pwall.json.JSONString
import net.pwall.json.JSONStringify.appendJSON

import my.custom.type.Money

@Service
class JSONConverter : AbstractJsonHttpMessageConverter() {

    override fun canRead(mediaType: MediaType?): Boolean {
        return mediaType != null && mediaType.equalsTypeAndSubtype(MediaType.APPLICATION_JSON)
    }

    override fun canWrite(mediaType: MediaType?): Boolean {
        return mediaType != null && mediaType.equalsTypeAndSubtype(MediaType.APPLICATION_JSON)
    }

    override fun readInternal(resolvedType: Type, reader: Reader): Any? {
        return JSONAuto.parse(resolvedType, reader, config)
    }

    override fun writeInternal(o: Any, type: Type?, writer: Writer) {
        writer.appendJSON(o, config)
    }

    companion object {

        /**
         * JSONConfig containing specific configuration for this application.
         *
         * It is placed in a companion object to allow it to be used from other parts of the application, e.g.
         *     val event = jsonString.parseJSON<Event>(JSONConverter.config)
         */
        val config = JSONConfig().apply {

            allowExtra = true // example configuration setting

            toJSONString<Money>() // example custom serialization

            fromJSON { json -> // example custom deserialization
                require(json is JSONString) { "JSON representation of Money must be string" }
                Money.of(json.value)
            }

        }

    }

}
```

2021-01-03
<file_sep>/*
 * @(#) JSONStringify.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2020, 2021 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.reflect.KProperty
import kotlin.reflect.full.staticProperties
import kotlin.reflect.jvm.isAccessible

import java.math.BigDecimal
import java.math.BigInteger
import java.util.BitSet
import java.util.Calendar
import java.util.Date
import java.util.Enumeration
import java.util.stream.BaseStream

import net.pwall.json.JSONKotlinException.Companion.fail
import net.pwall.json.JSONSerializerFunctions.findToJSON
import net.pwall.json.JSONSerializerFunctions.formatISO8601
import net.pwall.json.JSONSerializerFunctions.isSealedSubclass
import net.pwall.json.JSONSerializerFunctions.isToStringClass
import net.pwall.util.Strings

/**
 * JSON Auto serialize for Kotlin - serialize direct to `String`.
 *
 * @author  <NAME>
 */
object JSONStringify {

    /**
     * Serialize an object to JSON. (The word "stringify" is borrowed from the JavaScript implementation of JSON.)
     *
     * @param   obj     the object
     * @param   config  an optional [JSONConfig] to customise the conversion
     * @return          the JSON form of the object
     */
    fun stringify(obj: Any?, config: JSONConfig = JSONConfig.defaultConfig) : String {
        return when (obj) {
            null -> "null"
            else -> StringBuilder(config.stringifyInitialSize).apply {
                appendJSON(obj, config)
            }.toString()
        }
    }

    /**
     * Append the serialized form of an object to an [Appendable] in JSON.
     *
     * @receiver        the [Appendable] (e.g. [StringBuilder])
     * @param   obj     the object
     * @param   config  an optional [JSONConfig] to customise the conversion
     */
    fun Appendable.appendJSON(obj: Any?, config: JSONConfig = JSONConfig.defaultConfig) {
        appendJSON(obj, config, mutableSetOf())
    }

    private fun Appendable.appendJSON(obj: Any?, config: JSONConfig, references: MutableSet<Any>) {

        if (obj == null) {
            append("null")
            return
        }

        config.findToJSONMapping(obj::class)?.let {
            JSON.appendJSON(this, it(obj))
            return
        }

        when (obj) {
            is JSONValue -> obj.appendJSON(this)
            is CharSequence -> appendJSONString(obj)
            is CharArray -> {
                append('"')
                for (ch in obj)
                    appendJSONChar(ch)
                append('"')
            }
            is Char -> {
                append('"')
                appendJSONChar(obj)
                append('"')
            }
            is Number -> appendJSONNumber(obj, config, references)
            is Boolean -> append(if (obj) "true" else "false")
            is Array<*> -> appendJSONArray(obj, config, references)
            is Pair<*, *> -> appendJSONPair(obj, config, references)
            is Triple<*, *, *> -> appendJSONTriple(obj, config, references)
            else -> appendJSONObject(obj, config, references)
        }

    }

    private fun Appendable.appendJSONNumber(number: Number, config: JSONConfig, references: MutableSet<Any>) {
        when (number) {
            is Int -> Strings.appendInt(this, number)
            is Short, is Byte -> Strings.appendInt(this, number.toInt())
            is Long -> Strings.appendLong(this, number)
            is Float, is Double -> append(number.toString())
            is BigInteger -> {
                if (config.bigIntegerString) {
                    append('"')
                    append(number.toString())
                    append('"')
                }
                else
                    append(number.toString())
            }
            is BigDecimal -> {
                if (config.bigDecimalString) {
                    append('"')
                    append(number.toString())
                    append('"')
                }
                else
                    append(number.toString())
            }
            else -> appendJSONObject(number, config, references)
        }
    }

    private fun Appendable.appendJSONArray(array: Array<*>, config: JSONConfig, references: MutableSet<Any>) {
        if (array.isArrayOf<Char>()) {
            append('"')
            for (ch in array)
                appendJSONChar(ch as Char)
            append('"')
        }
        else {
            append('[')
            if (array.isNotEmpty()) {
                for (i in array.indices) {
                    if (i > 0)
                        append(',')
                    appendJSON(array[i], config, references)
                }
            }
            append(']')
        }
    }

    private fun Appendable.appendJSONPair(pair: Pair<*, *>, config: JSONConfig, references: MutableSet<Any>) {
        append('[')
        appendJSON(pair.first, config, references)
        append(',')
        appendJSON(pair.second, config, references)
        append(']')
    }

    private fun Appendable.appendJSONTriple(pair: Triple<*, *, *>, config: JSONConfig, references: MutableSet<Any>) {
        append('[')
        appendJSON(pair.first, config, references)
        append(',')
        appendJSON(pair.second, config, references)
        append(',')
        appendJSON(pair.third, config, references)
        append(']')
    }

    private fun Appendable.appendJSONObject(obj: Any, config: JSONConfig, references: MutableSet<Any>) {
        val objClass = obj::class
        if (objClass.isToStringClass() || obj is Enum<*>) {
            appendJSONString(obj.toString())
            return
        }
        objClass.findToJSON()?.let {
            try {
                it.call(obj).appendJSON(this)
                return
            }
            catch (e: Exception) {
                fail("Error in custom toJSON - ${objClass.simpleName}", e)
            }
        }
        when (obj) {
            is Iterable<*> -> appendJSONIterator(obj.iterator(), config, references)
            is Iterator<*> -> appendJSONIterator(obj, config, references)
            is Sequence<*> -> appendJSONIterator(obj.iterator(), config, references)
            is Enumeration<*> -> appendJSONEnumeration(obj, config, references)
            is BaseStream<*, *> -> appendJSONIterator(obj.iterator(), config, references)
            is Map<*, *> -> appendJSONMap(obj, config, references)
            is Calendar -> appendJSONString(obj.formatISO8601())
            is Date -> appendJSONString((Calendar.getInstance().apply { time = obj }).formatISO8601())
            is BitSet -> appendJSONBitSet(obj)
            else -> {
                try {
                    references.add(obj)
                    append('{')
                    var continuation = false
                    if (objClass.isSealedSubclass()) {
                        appendJSONString(config.sealedClassDiscriminator)
                        append(':')
                        appendJSONString(objClass.simpleName ?: "null")
                        continuation = true
                    }
                    val includeAll = config.hasIncludeAllPropertiesAnnotation(objClass.annotations)
                    val statics: Collection<KProperty<*>> = objClass.staticProperties
                    if (objClass.isData && objClass.constructors.isNotEmpty()) {
                        // data classes will be a frequent use of serialization, so optimise for them
                        val constructor = objClass.constructors.first()
                        for (parameter in constructor.parameters) {
                            val member = objClass.members.find { it.name == parameter.name }
                            if (member is KProperty<*>)
                                continuation = appendUsingGetter(member, parameter.annotations, obj, config, references,
                                        includeAll, continuation)
                        }
                        // now check whether there are any more properties not in constructor
                        for (member in objClass.members) {
                            if (member is KProperty<*> && !statics.contains(member) &&
                                    !constructor.parameters.any { it.name == member.name })
                                continuation = appendUsingGetter(member, member.annotations, obj, config, references,
                                        includeAll, continuation)
                        }
                    }
                    else {
                        for (member in objClass.members) {
                            if (member is KProperty<*> && !statics.contains(member)) {
                                val combinedAnnotations = ArrayList(member.annotations)
                                objClass.constructors.firstOrNull()?.parameters?.find { it.name == member.name }?.let {
                                    combinedAnnotations.addAll(it.annotations)
                                }
                                continuation = appendUsingGetter(member, combinedAnnotations, obj, config, references,
                                        includeAll, continuation)
                            }
                        }
                    }
                    append('}')
                }
                finally {
                    references.remove(obj)
                }
            }
        }
    }

    private fun Appendable.appendUsingGetter(member: KProperty<*>, annotations: List<Annotation>?, obj: Any,
            config: JSONConfig, references: MutableSet<Any>, includeAll: Boolean, continuation: Boolean): Boolean {
        if (!config.hasIgnoreAnnotation(annotations)) {
            val name = config.findNameFromAnnotation(annotations) ?: member.name
            val wasAccessible = member.isAccessible
            member.isAccessible = true
            try {
                val v = member.getter.call(obj)
                if (v != null && v in references)
                    fail("Circular reference: field ${member.name} in ${obj::class.simpleName}")
                if (v != null || config.hasIncludeIfNullAnnotation(annotations) || config.includeNulls || includeAll) {
                    if (continuation)
                        append(',')
                    appendJSONString(name)
                    append(':')
                    appendJSON(v, config, references)
                    return true
                }
            }
            catch (e: JSONException) {
                throw e
            }
            catch (e: Exception) {
                fail("Error getting property ${member.name} from ${obj::class.simpleName}", e)
            }
            finally {
                member.isAccessible = wasAccessible
            }
        }
        return continuation
    }

    private fun Appendable.appendJSONIterator(iterator: Iterator<*>, config: JSONConfig, references: MutableSet<Any>) {
        append('[')
        if (iterator.hasNext()) {
            while (true) {
                appendJSON(iterator.next(), config, references)
                if (!iterator.hasNext())
                    break
                append(',')
            }
        }
        append(']')
    }

    private fun Appendable.appendJSONEnumeration(enumeration: Enumeration<*>, config: JSONConfig,
            references: MutableSet<Any>) {
        append('[')
        if (enumeration.hasMoreElements()) {
            while (true) {
                appendJSON(enumeration.nextElement(), config, references)
                if (!enumeration.hasMoreElements())
                    break
                append(',')
            }
        }
        append(']')
    }

    private fun Appendable.appendJSONMap(map: Map<*, *>, config: JSONConfig, references: MutableSet<Any>) {
        append('{')
        map.entries.iterator().let {
            if (it.hasNext()) {
                while (true) {
                    val ( key, value ) = it.next()
                    appendJSONString(key.toString())
                    append(':')
                    appendJSON(value, config, references)
                    if (!it.hasNext())
                        break
                    append(',')
                }
            }
        }
        append('}')
    }

    private fun Appendable.appendJSONBitSet(bitSet: BitSet) {
        append('[')
        var continuation = false
        for (i in 0 until bitSet.length()) {
            if (bitSet.get(i)) {
                if (continuation)
                    append(',')
                Strings.appendInt(this, i)
                continuation = true
            }
        }
        append(']')
    }

    private fun Appendable.appendJSONString(cs: CharSequence) {
        append('"')
        for (ch in cs)
            appendJSONChar(ch)
        append('"')
    }

    private fun Appendable.appendJSONChar(ch: Char) {
        when (ch) {
            '"', '\\' -> append('\\').append(ch)
            in ' '..'\u007F' -> append(ch)
            '\n' -> append("\\n")
            '\t' -> append("\\t")
            '\r' -> append("\\r")
            '\b' -> append("\\b")
            '\u000C' -> append("\\f")
            else -> {
                append("\\u")
                Strings.appendHex(this, ch)
            }
        }
    }

}
<file_sep>/*
 * @(#) JSONDelegate.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

/**
 * A delegate class to create a property from a JSON string.
 *
 * @constructor
 * @param   json    the string to be parsed
 * @param   config  an optional [JSONConfig] to customise the conversion
 * @param   T       the target type
 * @author  <NAME>
 */
class JSONDelegate<out T>(val json: String, val config: JSONConfig = JSONConfig.defaultConfig) :
        ReadOnlyProperty<Any?, T> {

    private var value: Any? = UNINITIALISED

    /**
     * Get the value of the property - the first time through this will parse the supplied string and store it for
     * subsequent references.
     *
     * This function is not intended to be thread- or coroutine-safe, but if it is used by multiple processes in
     * parallel the worst that can happen (in very rare cases) is that the string will be re-parsed unnecessarily.
     *
     * @param   thisRef     a reference to the containing object - not used
     * @param   property    the property descriptor - used to get the type
     */
    @Suppress("UNCHECKED_CAST")
    override operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        if (value === UNINITIALISED)
            value = JSONAuto.parse(property.returnType, json, config)
        return value as T
    }

    private object UNINITIALISED

}
<file_sep>/*
 * @(#) JSONConfig.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019, 2020, 2021 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.KType
import kotlin.reflect.full.createType
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.isSuperclassOf
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.full.isSupertypeOf
import kotlin.reflect.typeOf

import net.pwall.json.JSONKotlinException.Companion.fail
import net.pwall.json.annotation.JSONAllowExtra
import net.pwall.json.annotation.JSONIgnore
import net.pwall.json.annotation.JSONIncludeAllProperties
import net.pwall.json.annotation.JSONIncludeIfNull
import net.pwall.json.annotation.JSONName

/**
 * Configuration class for JSON Auto serialize / deserialize for Kotlin.
 *
 * @author  <NAME>
 */
@OptIn(ExperimentalStdlibApi::class)
class JSONConfig {

    /** Name of property to store sealed class subclass name as discriminator */
    var sealedClassDiscriminator = defaultSealedClassDiscriminator
        set(newValue) {
            if (newValue.isNotEmpty()) field = newValue else fail("Sealed class discriminator invalid")
        }

    /** Read buffer size (for `json-ktor`), arbitrarily limited to multiple of 16, not greater than 256K */
    var readBufferSize = defaultBufferSize
        set (newValue) {
            if ((newValue and 15) == 0 && newValue in 16..(256 * 1024))
                field = newValue
            else
                fail("Read buffer size invalid - $newValue")
        }

    /** Initial allocation size for stringify operations, arbitrarily limited to 16 to 256K */
    var stringifyInitialSize = defaultStringifyInitialSize
        set (newValue) {
            if (newValue in 16..(256 * 1024))
                field = newValue
            else
                fail("Stringify initial allocation size invalid - $newValue")
        }

    /** Character set (for `json-ktor` and  `json-ktor-client`) */
    var charset = defaultCharset

    /** Switch to control how `BigInteger` is serialized / deserialized: `true` -> string, `false` -> number */
    var bigIntegerString = defaultBigIntegerString

    /** Switch to control how `BigDecimal` is serialized / deserialized: `true` -> string, `false` -> number */
    var bigDecimalString = defaultBigDecimalString

    /** Switch to control whether null fields in objects are output as "null": `true` -> yes, `false` -> no */
    var includeNulls = defaultIncludeNulls

    /** Switch to control whether extra fields are allowed on deserialization: `true` -> yes, `false` -> no */
    var allowExtra = defaultAllowExtra

    /** Switch to control whether `json-ktor` uses streamed output */
    var streamOutput = defaultStreamOutput

    private val fromJSONMap: MutableMap<KType, FromJSONMapping> = LinkedHashMap()

    private val toJSONMap: MutableMap<KType, ToJSONMapping> = LinkedHashMap()

    private val nameAnnotations: MutableList<Pair<KClass<*>, KProperty.Getter<String>>> =
            arrayListOf(namePropertyPair(JSONName::class, "name"))

    private val ignoreAnnotations: MutableList<KClass<*>> = arrayListOf(JSONIgnore::class, Transient::class)

    private val includeIfNullAnnotations: MutableList<KClass<*>> = arrayListOf(JSONIncludeIfNull::class)

    private val includeAllPropertiesAnnotations: MutableList<KClass<*>> = arrayListOf(JSONIncludeAllProperties::class)

    private val allowExtraPropertiesAnnotations: MutableList<KClass<*>> = arrayListOf(JSONAllowExtra::class)

    /**
     * Find a `fromJSON` mapping function that will create the specified [KType], or the closest subtype of it.
     *
     * @param   type    the target type
     * @return          the mapping function, or `null` if not found
     */
    fun findFromJSONMapping(type: KType): FromJSONMapping? {
        if (type.classifier == Any::class)
            return null
        var best: Map.Entry<KType, FromJSONMapping>? = null
        for (entry in fromJSONMap.entries) {
            if (entry.key.isSubtypeOf(type) && best.let { it == null || it.key.isSubtypeOf(entry.key) })
                best = entry
        }
        return best?.value
    }

    /**
     * Find a `fromJSON` mapping function that will create the specified [KClass], or the closest subclass of it.
     *
     * @param   targetClass the target class
     * @return              the mapping function, or `null` if not found
     */
    fun findFromJSONMapping(targetClass: KClass<*>): FromJSONMapping? {
        if (targetClass == Any::class)
            return null
        var best: KClass<*>? = null
        var nullable = false
        var result: FromJSONMapping? = null
        for (entry in fromJSONMap.entries) {
            val classifier = entry.key.classifier as KClass<*>
            if (classifier.isSubclassOf(targetClass) && best.let {
                        it == null ||
                                if (it == classifier) !nullable else it.isSubclassOf(classifier) }) {
                best = classifier
                nullable = entry.key.isMarkedNullable
                result = entry.value
            }
        }
        return result
    }

    /**
     * Find a `toJSON` mapping function that will accept the specified [KType], or the closest supertype of it.
     *
     * @param   type    the source type
     * @return          the mapping function, or `null` if not found
     */
    fun findToJSONMapping(type: KType): ToJSONMapping? {
        var best: Map.Entry<KType, ToJSONMapping>? = null
        for (entry in toJSONMap.entries) {
            if (entry.key.isSupertypeOf(type) && best.let { it == null || it.key.isSupertypeOf(entry.key) })
                best = entry
        }
        return best?.value
    }

    /**
     * Find a `toJSON` mapping function that will accept the specified [KClass], or the closest superclass of it.
     *
     * @param   sourceClass the source class
     * @return              the mapping function, or `null` if not found
     */
    fun findToJSONMapping(sourceClass: KClass<*>): ToJSONMapping? {
        var best: KClass<*>? = null
        var nullable = false
        var result: ToJSONMapping? = null
        for (entry in toJSONMap.entries) {
            val classifier = entry.key.classifier as KClass<*>
            if (classifier.isSuperclassOf(sourceClass) && best.let {
                        it == null ||
                                if (it == classifier) nullable else it.isSuperclassOf(classifier) }) {
                best = classifier
                nullable = entry.key.isMarkedNullable
                result = entry.value
            }
        }
        return result
    }

    /**
     * Add custom mapping from JSON to the specified type.
     *
     * @param   type    the target type
     * @param   mapping the mapping function
     */
    fun fromJSON(type: KType, mapping: FromJSONMapping) {
        fromJSONMap[type] = mapping
    }

    /**
     * Add custom mapping from JSON to the specified type, using a constructor that takes a single [String] parameter.
     *
     * @param   type    the target type
     */
    fun fromJSONString(type: KType) {
        fromJSONMap[type] = { json ->
            when (json) {
                null -> if (type.isMarkedNullable) null else fail("Can't deserialize null as $type")
                is JSONString -> {
                    val resultClass = type.classifier as? KClass<*> ?: fail("Can't deserialize $type")
                    val constructor = resultClass.constructors.find {
                        it.parameters.size == 1 && it.parameters[0].type == stringType
                    }
                    constructor?.call(json.toString()) ?: fail("Can't deserialize $type")
                }
                else -> fail("Can't deserialize ${json::class.simpleName} as $type")
            }
        }
    }

    /**
     * Add custom mapping from a specified type to JSON.
     *
     * @param   type    the source type
     * @param   mapping the mapping function
     */
    fun toJSON(type: KType, mapping: ToJSONMapping) {
        toJSONMap[type] = mapping
    }

    /**
     * Add custom mapping from a specified type to JSON using the `toString()` function to create a JSON string.
     *
     * @param   type    the source type
     */
    fun toJSONString(type: KType) {
        toJSONMap[type] = { obj -> obj?.let { JSONString(it.toString())} }
    }

    /**
     * Add custom mapping from JSON to the inferred type.
     *
     * @param   mapping the mapping function
     * @param   T       the type to be mapped
     */
    inline fun <reified T: Any> fromJSON(noinline mapping: (JSONValue?) -> T?) {
        fromJSON(typeOf<T>(), mapping)
    }

    /**
     * Add custom mapping from JSON to the inferred type, using a constructor that takes a single [String] parameter.
     *
     * @param   T       the type to be mapped
     */
    inline fun <reified T: Any> fromJSONString() {
        fromJSONString(typeOf<T>())
    }

    /**
     * Add custom mapping from an inferred type to JSON.
     *
     * @param   mapping the mapping function
     * @param   T       the type to be mapped
     */
    inline fun <reified T: Any> toJSON(noinline mapping: (T?) -> JSONValue?) {
        toJSON(typeOf<T>()) { mapping(it as T?) }
    }

    /**
     * Add custom mapping from an inferred type to JSON using the `toString()` function to create a JSON string.
     *
     * @param   T       the type to be mapped
     */
    inline fun <reified T: Any> toJSONString() {
        toJSONString(typeOf<T>())
    }

    /**
     * Add an annotation specification to the list of annotations that specify the name to be used when serializing or
     * deserializing a property.
     *
     * @param   nameAnnotationClass the annotation class to specify the name
     * @param   argumentName        the name of the argument to the annotation that holds the name
     * @param   T                   the annotation class
     */
    fun <T: Annotation> addNameAnnotation(nameAnnotationClass: KClass<T>, argumentName: String) {
        nameAnnotations.add(namePropertyPair(nameAnnotationClass, argumentName))
    }

    private fun <T: Annotation> namePropertyPair(nameAnnotationClass: KClass<T>, argumentName: String):
            Pair<KClass<*>, KProperty.Getter<String>> {
        return nameAnnotationClass to findAnnotationStringProperty(nameAnnotationClass, argumentName).getter
    }

    @Suppress("UNCHECKED_CAST")
    private fun <T: Annotation> findAnnotationStringProperty(annotationClass: KClass<T>, argumentName: String):
            KProperty<String> {
        for (member in annotationClass.members) {
            if (member is KProperty<*> && member.name == argumentName && member.returnType == stringType) {
                return member as KProperty<String>
            }
        }
        throw IllegalArgumentException(
                "Annotation class ${annotationClass.simpleName} does not have a String property \"$argumentName\"")
    }

    /**
     * Find the name to be used when serializing or deserializing a property, from the annotations supplied.
     *
     * @param   annotations the [Annotation]s (from the parameter, or the property, or both)
     * @return              the name to be used, or `null` if no annotation found
     */
    fun findNameFromAnnotation(annotations: List<Annotation>?): String? {
        if (annotations != null) {
            for (entry in nameAnnotations) {
                for (annotation in annotations) {
                    if (annotation::class.isSubclassOf(entry.first))
                        return entry.second.call(annotation)
                }
            }
        }
        return null
    }

    /**
     * Add an annotation specification to the list of annotations that specify that the property is to be ignored when
     * serializing or deserializing.
     *
     * @param   ignoreAnnotationClass   the annotation class
     * @param   T                       the annotation class
     */
    fun <T: Annotation> addIgnoreAnnotation(ignoreAnnotationClass: KClass<T>) {
        ignoreAnnotations.add(ignoreAnnotationClass)
    }

    /**
     * Test whether a property has an annotation to indicate that it is to be ignored when serializing or deserializing.
     *
     * @param   annotations the [Annotation]s (from the parameter, or the property, or both)
     * @return              `true` if an "ignore" annotation appears in the supplied list
     */
    fun hasIgnoreAnnotation(annotations: List<Annotation>?) = hasBooleanAnnotation(ignoreAnnotations, annotations)

    /**
     * Add an annotation specification to the list of annotations that specify that the property is to be included when
     * serializing or deserializing even if `null`.
     *
     * @param   includeIfNullAnnotationClass    the annotation class
     * @param   T                               the annotation class
     */
    fun <T: Annotation> addIncludeIfNullAnnotation(includeIfNullAnnotationClass: KClass<T>) {
        includeIfNullAnnotations.add(includeIfNullAnnotationClass)
    }

    /**
     * Test whether a property has an annotation to indicate that it is to be included when serializing or deserializing
     * even if `null`.
     *
     * @param   annotations the [Annotation]s (from the parameter, or the property, or both)
     * @return              `true` if an "include if null" annotation appears in the supplied list
     */
    fun hasIncludeIfNullAnnotation(annotations: List<Annotation>?) =
            hasBooleanAnnotation(includeIfNullAnnotations, annotations)

    /**
     * Add an annotation specification to the list of annotations that specify that all properties in a class are to be
     * included when serializing even if `null`.
     *
     * @param   ignoreAllPropertiesAnnotationClass  the annotation class
     * @param   T                                   the annotation class
     */
    fun <T: Annotation> addIncludeAllPropertiesAnnotation(ignoreAllPropertiesAnnotationClass: KClass<T>) {
        includeAllPropertiesAnnotations.add(ignoreAllPropertiesAnnotationClass)
    }

    /**
     * Test whether a property has an annotation to indicate that it is to be included when serializing even if `null`.
     *
     * @param   annotations the [Annotation]s (from the class)
     * @return              `true` if an "include all properties" annotation appears in the supplied list
     */
    fun hasIncludeAllPropertiesAnnotation(annotations: List<Annotation>?) =
            hasBooleanAnnotation(includeAllPropertiesAnnotations, annotations)

    /**
     * Add an annotation specification to the list of annotations that specify that extra properties in a class are to
     * be ignored when deserializing.
     *
     * @param   allowExtraPropertiesAnnotationClass the annotation class
     * @param   T                                   the annotation class
     */
    fun <T: Annotation> addAllowExtraPropertiesAnnotation(allowExtraPropertiesAnnotationClass: KClass<T>) {
        allowExtraPropertiesAnnotations.add(allowExtraPropertiesAnnotationClass)
    }

    /**
     * Test whether a property has an annotation to indicate that extra properties in a class are to be ignored when
     * deserializing.
     *
     * @param   annotations the [Annotation]s (from the class)
     * @return              `true` if an "allow extra properties" annotation appears in the supplied list
     */
    fun hasAllowExtraPropertiesAnnotation(annotations: List<Annotation>?) =
            hasBooleanAnnotation(allowExtraPropertiesAnnotations, annotations)

    /**
     * Test whether a property has a boolean annotation matching the specified list.
     *
     * @param   annotationList  the list of pre-configured annotation classes.
     * @param   annotations     the [Annotation]s (from the parameter, or the property, or both)
     * @return                  `true` if an "ignore" annotation appears in the supplied list
     */
    private fun hasBooleanAnnotation(annotationList: List<KClass<*>>, annotations: List<Annotation>?): Boolean {
        if (annotations != null) {
            for (entry in annotationList) {
                for (annotation in annotations) {
                    if (annotation::class.isSubclassOf(entry))
                        return true
                }
            }
        }
        return false
    }

    /**
     * Combine another `JSONConfig` into this one.
     *
     * @param   config  the other `JSONConfig`
     */
    fun combineAll(config: JSONConfig) {
        sealedClassDiscriminator = config.sealedClassDiscriminator
        readBufferSize = config.readBufferSize
        stringifyInitialSize = config.stringifyInitialSize
        charset = config.charset
        bigIntegerString = config.bigIntegerString
        bigDecimalString = config.bigDecimalString
        includeNulls = config.includeNulls
        allowExtra = config.allowExtra
        streamOutput = config.streamOutput
        fromJSONMap.putAll(config.fromJSONMap)
        toJSONMap.putAll(config.toJSONMap)
        nameAnnotations.addAll(config.nameAnnotations)
        ignoreAnnotations.addAll(config.ignoreAnnotations)
        includeIfNullAnnotations.addAll(config.includeIfNullAnnotations)
        includeAllPropertiesAnnotations.addAll(config.includeAllPropertiesAnnotations)
        allowExtraPropertiesAnnotations.addAll(config.allowExtraPropertiesAnnotations)
    }

    /**
     * Combine custom mappings from another `JSONConfig` into this one.
     *
     * @param   config  the other `JSONConfig`
     */
    fun combineMappings(config: JSONConfig) {
        fromJSONMap.putAll(config.fromJSONMap)
        toJSONMap.putAll(config.toJSONMap)
    }

    companion object {

        val stringType = String::class.createType()

        const val defaultSealedClassDiscriminator = "class"

        const val defaultBufferSize = DEFAULT_BUFFER_SIZE

        const val defaultStringifyInitialSize = 1024

        const val defaultBigIntegerString = false

        const val defaultBigDecimalString = false

        const val defaultIncludeNulls = false

        const val defaultAllowExtra = false

        const val defaultStreamOutput = false

        val defaultCharset = Charsets.UTF_8

        val defaultConfig = JSONConfig()

    }

}
<file_sep>/*
 * @(#) JSONAuto.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019, 2020, 2021 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.typeOf

import java.io.File
import java.io.InputStream
import java.io.Reader
import java.lang.reflect.Type

@OptIn(ExperimentalStdlibApi::class)
object JSONAuto {

    /**
     * Serialize an object to JSON. (The word "stringify" is borrowed from the JavaScript implementation of JSON.)
     *
     * @param   obj     the object
     * @param   config  an optional [JSONConfig] to customise the conversion
     * @return          the JSON form of the object
     */
    fun stringify(obj: Any?, config: JSONConfig = JSONConfig.defaultConfig): String =
            JSONStringify.stringify(obj, config)

    /**
     * Deserialize JSON from string ([CharSequence]) to a specified [KType].
     *
     * @param   resultType  the target type
     * @param   str         the JSON in string form
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    fun parse(resultType: KType, str: CharSequence, config: JSONConfig = JSONConfig.defaultConfig): Any? =
            JSONDeserializer.deserialize(resultType, JSON.parse(str), config)

    /**
     * Deserialize JSON from string ([CharSequence]) to a specified [KClass].
     *
     * @param   resultClass the target class
     * @param   str         the JSON in string form
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    fun <T: Any> parse(resultClass: KClass<T>, str: CharSequence, config: JSONConfig = JSONConfig.defaultConfig): T? =
            JSONDeserializer.deserialize(resultClass, JSON.parse(str), config)

    /**
     * Deserialize JSON from string ([CharSequence]) to the inferred [KType].
     *
     * @param   str         the JSON in string form
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    inline fun <reified T: Any> parse(str: CharSequence, config: JSONConfig = JSONConfig.defaultConfig): T? =
            parse(typeOf<T>(), str, config) as T?

    /**
     * Deserialize JSON from string ([CharSequence]) to a specified Java [Type].
     *
     * @param   javaType    the target type
     * @param   str         the JSON in string form
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    fun parse(javaType: Type, str: CharSequence, config: JSONConfig = JSONConfig.defaultConfig): Any? =
            JSONDeserializer.deserialize(javaType, JSON.parse(str), config)

    /**
     * Deserialize JSON from a [Reader] to a specified [KType].
     *
     * @param   resultType  the target type
     * @param   reader      the JSON in the form of a [Reader]
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    fun parse(resultType: KType, reader: Reader, config: JSONConfig = JSONConfig.defaultConfig): Any? =
            JSONDeserializer.deserialize(resultType, JSON.parse(reader), config)

    /**
     * Deserialize JSON from a [Reader] to a specified [KClass].
     *
     * @param   resultClass the target class
     * @param   reader      the JSON in the form of a [Reader]
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    fun <T: Any> parse(resultClass: KClass<T>, reader: Reader, config: JSONConfig = JSONConfig.defaultConfig): T? =
            JSONDeserializer.deserialize(resultClass, JSON.parse(reader), config)

    /**
     * Deserialize JSON from a [Reader] to the inferred [KType].
     *
     * @param   reader      the JSON in the form of a [Reader]
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    inline fun <reified T: Any> parse(reader: Reader, config: JSONConfig = JSONConfig.defaultConfig): T? =
            parse(typeOf<T>(), reader, config) as T?

    /**
     * Deserialize JSON from a [Reader] to a specified Java [Type].
     *
     * @param   javaType    the target type
     * @param   reader      the JSON in the form of a [Reader]
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    fun parse(javaType: Type, reader: Reader, config: JSONConfig = JSONConfig.defaultConfig): Any? =
            JSONDeserializer.deserialize(javaType, JSON.parse(reader), config)

    /**
     * Deserialize JSON from an [InputStream] to a specified [KType].
     *
     * @param   resultType  the target type
     * @param   inputStream the JSON in the form of an [InputStream]
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    fun parse(resultType: KType, inputStream: InputStream, config: JSONConfig = JSONConfig.defaultConfig): Any? =
            JSONDeserializer.deserialize(resultType, JSON.parse(inputStream), config)

    /**
     * Deserialize JSON from an [InputStream] to a specified [KClass].
     *
     * @param   resultClass the target class
     * @param   inputStream the JSON in the form of an [InputStream]
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    fun <T: Any> parse(resultClass: KClass<T>, inputStream: InputStream,
            config: JSONConfig = JSONConfig.defaultConfig): T? =
                    JSONDeserializer.deserialize(resultClass, JSON.parse(inputStream), config)

    /**
     * Deserialize JSON from an [InputStream] to the inferred [KType].
     *
     * @param   inputStream the JSON in the form of an [InputStream]
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    inline fun <reified T: Any> parse(inputStream: InputStream, config: JSONConfig = JSONConfig.defaultConfig): T? =
            parse(typeOf<T>(), inputStream, config) as T?

    /**
     * Deserialize JSON from an [InputStream] to a specified Java [Type].
     *
     * @param   javaType    the target type
     * @param   inputStream the JSON in the form of an [InputStream]
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    fun parse(javaType: Type, inputStream: InputStream, config: JSONConfig = JSONConfig.defaultConfig): Any? =
            JSONDeserializer.deserialize(javaType, JSON.parse(inputStream), config)

    /**
     * Deserialize JSON from a [File] to a specified [KType].
     *
     * @param   resultType  the target type
     * @param   file        the JSON in the form of a [File]
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    fun parse(resultType: KType, file: File, config: JSONConfig = JSONConfig.defaultConfig): Any? =
            JSONDeserializer.deserialize(resultType, JSON.parse(file), config)

    /**
     * Deserialize JSON from a [File] to a specified [KClass].
     *
     * @param   resultClass the target class
     * @param   file        the JSON in the form of a [File]
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    fun <T: Any> parse(resultClass: KClass<T>, file: File, config: JSONConfig = JSONConfig.defaultConfig): T? =
            JSONDeserializer.deserialize(resultClass, JSON.parse(file), config)

    /**
     * Deserialize JSON from a [File] to the inferred [KType].
     *
     * @param   file        the JSON in the form of a [File]
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    inline fun <reified T: Any> parse(file: File, config: JSONConfig = JSONConfig.defaultConfig): T? =
            parse(typeOf<T>(), file, config) as T?

    /**
     * Deserialize JSON from a [File] to a specified Java [Type].
     *
     * @param   javaType    the target type
     * @param   file        the JSON in the form of a [File]
     * @param   config      an optional [JSONConfig] to customise the conversion
     * @return              the converted object
     */
    fun parse(javaType: Type, file: File, config: JSONConfig = JSONConfig.defaultConfig): Any? =
            JSONDeserializer.deserialize(javaType, JSON.parse(file), config)

}
<file_sep># Change Log

The format is based on [Keep a Changelog](http://keepachangelog.com/).

## [4.8] - 2023-07-10
### Changed
- `pom.xml`: updated dependency versions

## [4.7.5] - 2023-01-08
### Changed
- `pom.xml`: bumped dependency versions

## [4.7.4] - 2022-11-21
### Changed
- `pom.xml`: bumped dependency versions

## [4.7.3] - 2022-11-08
### Changed
- `pom.xml`: bumped dependency versions
- `pom.xml`: updated to Kotlin 1.6.10

## [4.7.2] - 2022-10-16
### Changed
- `pom.xml`: bumped dependency versions

## [4.7.1] - 2022-04-18
### Changed
- `pom.xml`: bumped dependency version

## [4.7] - 2022-02-20
### Changed
- `pom.xml`: bumped dependency versions

## [4.6] - 2022-02-11
### Changed
- `JSONSerializerFunctions`: fixed bug in serialization of some internal Kotlin classes

## [4.5] - 2021-11-07
### Changed
- `pom.xml`: updated to Kotlin 1.5.20

## [4.4] - 2021-09-16
### Changed
- `JSONDeserializer`: improved type determination for parameterised types with upper bound
- `pom.xml`: bumped dependency versions

## [4.3] - 2021-05-04
### Changed
- several: switched to use `typeOf()`
- `pom.xml`: added compiler switch
- `JSONDeserializer`, `JSONDeserializerFunctions`: add validation of UUID (workaround for Java bug)

## [4.2] - 2021-04-19
### Changed
- `pom.xml`: updated dependency versions
- `JSONFun.kt`: removed `value` extension variables (no longer necessary)

## [4.1] - 2021-02-28
### Changed
- `JSONDeserializer`: added duplicate check on deserialization of `Set`

## [4.0] - 2021-01-10
### Added
- `JSONKotlinException`: New exception class
### Changed
- several: switched to use `JSONKotlinException`
- `JSONDeserializer`: make use of pointer on error messages
- `JSONDeserializer`: improved error messages on deserializing objects

## [3.16] - 2021-01-07
### Changed
- `JSONFun.kt`: added `value` extension variables for `JSONValue` primitive types

## [3.15] - 2021-01-03
### Added
- `USERGUIDE.md`
- `CUSTOM.md`
### Changed
- `pom.xml`: updated dependency versions
- `JSONDeserializer`: simplified `deserializeAny`
- `JSONSerializer`, `JSONDeserializer`, `JSONStringify`: added support for Java Streams
- `JSONConfig`: bug fix in `combineAll`

## [3.14] - 2020-11-30
### Changed
- `JSONFun`: additional extension functions on `JSONValue`
- `JSONConfig`: bug fix on `fromJSON`
- `JSONDeserializer`: minor bug fix

## [3.13] - 2020-11-25
### Changed
- `JSONAuto`: added more helper functions

## [3.12] - 2020-11-25
### Changed
- `JSONDeserializer`: modified to allow use by `YAMLSequence` and `YAMLMapping`
- `README.md`: added badges
### Added
- `travis.yml`

## [3.11] - 2020-09-16
### Changed
- `pom.xml`: updated to Kotlin 1.4.0

## [3.10.1] - 2020-08-25
### Changed
- `pom.xml`: fixed generation of sources jar
- `JSONJava`, `JSONJavaTest`: moved to kotlin directory (to allow sources jar generation)

## [3.10] - 2020-08-25
### Changed
- `JSONDeserializer`: further improvements to handling of parameterized types

## [3.9] - 2020-08-20
### Changed
- `JSONConfig`: changed `toJSON` and `fromJSON` to use `JSONTypeRef`

## [3.8] - 2020-07-12
### Changed
- `JSONDeserializer`: improved handling of parameterized types

## [3.7] - 2020-07-07
### Changed
- `JSONDeserializerFunctions`: added `hasSingleParameter` and `findParameterName` (moved from `JSONDeserializer`)
- `JSONDeserializer`: improved deserialization of objects with missing parameters

## [3.6] - 2020-05-03
### Added
- `JSONDeserializerFunctions`:  (split out from `JSONDeserializer`)
### Changed
- `JSONDeserializerFunctions`: improved handling of `fromJSON` (in companion object)

## [3.5] - 2020-04-23
### Changed
- `JSONConfig`: added `streamOutput` switch (for `json-ktor`)
- `JSONSerializerFunctions`: changed `toJsonCache` handling (more efficient)
- `JSONSerializer`, `JSONSerializerFunctions`: improved handling of classes output by `toString()`
- `JSONDeserializer`, `JSONSerializerFunctions`: add classes from `java.time` package

## [3.4.1] - 2020-04-21
### Changed
- `pom.xml`: updated dependency versions

## [3.4] - 2020-04-18
### Changed
- `JSONSerializer`, `JSONStringify`: added checks for circular references

## [3.3] - 2020-04-18
### Added
- `JSONSerializerFunctions`: common functionality for `JSONSerializer`, `JSONStringify` etc. (split out from
`JSONSerializer`)
### Changed
- `JSONDeserializer`: added check for `@JSONIgnore`
### Removed
- `TestAnnotatedClasses.kt`, `TestSealedClasses.kt`: moved to `json-kotlin-test-classes`

## [3.2] - 2020-04-13
### Changed
- `JSONConfig`, `JSONAuto`, `JSONFun`, `JSONSerializer`: accommodate JSONStringify
- `JSONTypeRef`: added `nullable` parameter
### Added
- `JSONStringify`: stringify without intermediate JSONValue tree

## [3.1] - 2020-04-05
### Changed
- `JSONConfig`, `JSONSerializer`: added `includeNulls` switch
- `JSONConfig`, `JSONSerializer`: added annotations to specify that null properties are to be included
- `JSONConfig`, `JSONDeserializer`: added `allowExtra` switch
- `JSONConfig`, `JSONDeserializer`: added annotations to specify that extra properties are to be ignored
### Added
- `JSONIncludeIfNull`: Annotation class
- `JSONIncludeAllProperties`: Annotation class
- `JSONAllowExtra`: Annotation class

## [3.0] - 2020-01-27
### Added
- `JSONTypeRef`: implementation of the [TypeReference](https://gafter.blogspot.com/2006/12/super-type-tokens.html)
pattern
### Changed
- `JSONAuto`, `JSONDeserializer`: changed to use `JSONTypeRef`
- `JSONFun`: improved `toKType`
- `JSONConfig`: added switches to select handling of `BigInteger` and `BigDecimal`
- Several: switched to version 3.0 of `jsonutil` - improved handling of decimal fractions (`JSONDecimal`)

## [2.1] - 2019-12-11
### Changed
- `JSONFun`: added new `targetKType` that takes nested `KType`
- `JSONFun`: added `CharSequence.parseListJSON()`, `CharSequence.parseSetJSON()` and `CharSequence.parseMapJSON()`

### Added
- `JSONDelegate`: delegate class to assist in deserializing parameterized classes.

## [2.0] - 2019-11-17
### Changed
- `JSONConfig`: Sealed class discriminator property may now be specified
- `JSONConfig`: Modifying functions that used to return `this` for chaining no longer do so (breaking change)


## [1.2] - 2019-11-11
### Changed
- `JSONSerializer`: Added serialization of sealed classes
- `JSONDeserializer`: Added deserialization of sealed classes
- Tests: switched to use of external repository (to avoid compilation order issues)

## [1.1] - 2019-11-03
### Changed
- Updated version to 1.0
- `JSONConfig`: improved selection of custom serialization and deserialization functions
- `JSONConfig`: added shortcut  custom serialization / deserialization functions using `toString()` and constructor
  taking `String` parameter
- `JSONDeserializer`: changed to use new custom serialization and deserialization functions
- `JSONSerializer`: changed to use new custom serialization and deserialization functions
- Several: added more KDoc

### Added
- `JSONJava`: to access Kotlin serialization and deserialization from Java

## [1.0] - 2019-10-08
### Changed
- Updated version to 1.0
- `JSONDeserializer`: improved deserialization, including defaults if types not specified

## [0.13] - 2019-09-22
### Changed
- `JSONConfig`: added `charset`

## [0.12] - 2019-09-19
### Changed
- `JSONFun`: changed name of `targetJSON` function to `targetKType`
- `JSONFun`: added `toKType` extension function
- `JSONDeserializer`: added function to take Java `Type`
- `JSONAuto`: added function to take Java `Type`

## [0.11] - 2019-09-17
### Changed
- `JSONDeserializer`: added shared function to access type parameters
- `JSONAuto`: added `targetJSON` function

## [0.10] - 2019-09-11
### Changed
- `JSONConfig`: added more KDoc
- `JSONConfig`: added custom name and ignore annotation specification
- `JSONConfig`: added `defaultConfig` (and added use of default to other functions)
- `JSONConfig`: added combine functions
- `JSONDeserializer`: changed to use custom name and ignore annotations
- `JSONFun`: changed `asJSON` to `asJSONValue`; changed `toJSON` to `isJSON`
- `JSONFun`: added coverage of data types `Char`, `Short`, `Byte`
- `JSONFun`: added added more extensions functions
- `JSONFun`: added KDoc
- `JSONSerializer`: changed to use custom name and ignore annotations
- `JSONDeserializer`: improved enum handling

## [0.9] - 2019-07-17
### Changed
- `JSONAuto`: added optional `JSONConfig` to all methods
- `JSONAuto`: Changed `parse` to take `CharSequence` instead of `String`
- `JSONConfig`: fixed typo in method names
- `JSONDeserializer`: added deserialization of `java.util.BitSet`
- `JSONDeserializer`: switched to use `JSONInt` `typealias`
- `JSONFun`: renamed `toJSON` to `isJSON`
- `JSONFun`: added `Any?.asJSON()`
- `JSONFun`: added `CharSequence.parseJSON()`
- `JSONFun`: added `Any?.stringifyJSON()`
- `JSONSerializer`: added serialization of `java.math.BigDecimal`, `java.math.BigInteger`, `java.net.URI`,
  `java.net.URL`
- `JSONSerializer`: fixed typo - `invokeSetter` should have been `invokeGetter`
- `JSONSerializer`: switched to use `JSONInt` `typealias`
- `pom.xml`: switched to use of parent POM
- `pom.xml`: added link to `jsonutil` JavaDoc

## [0.8] - 2019-07-08
### Changed
- `JSONConfig`: changed to use system default buffer size
- `JSONSerializer`: added serialization of `java.sql.Date`, `java.sql.Time`, `java.sql.Timestamp`
- `JSONDeserializer`: added deserialization of `java.sql.Date`, `java.sql.Time`, `java.sql.Timestamp`
- `README.md`: documentation added (more needed)

### Added
- `JSONFun`: JSON helper functions
- `JSONFunTest`: tests for JSON helper functions

## [0.7] - 2019-07-06
### Changed
- `JSONDeserializer`: added deserialization of Kotlin `object`s (serialization already worked)
- `JSONSerializer`: improved serialization of Java objects
- `JSONDeserializer`: improved deserialization of Java objects
- `JSONSerializer`: added serialization of `java.time.Duration`, `java.time.Period`
- `JSONDeserializer`: added deserialization of `java.time.Duration`, `java.time.Period`
- `JSONConfig`: added `readBufferSize`

## [0.6] - 2019-05-23
### Changed
- `JSONAuto`: added `JSONConfig` to functions
- `JSONDeserializer`: added `JSONConfig` to functions

## [0.5] - 2019-05-21
### Changed
- `JSONSerializer`: added serialization of `Pair` and `Triple`
- `JSONSerializerTest`: corresponding tests
- `JSONDeserializer`: added deserialization of `Pair` and `Triple`
- `JSONDeserializerTest`: corresponding tests
- `JSONDeserializer`: added checking of types marked nullable
- `JSONDeserializerTest`: corresponding tests
- `JSONDeserializer`: added use of custom deserialization from `JSONConfig`
- `JSONDeserializerTest`: corresponding tests
- `JSONSerializer`: added use of custom serialization from `JSONConfig`
- `JSONSerializerTest`: corresponding tests

### Added
- `JSONConfig`: new configuration class

## [0.4] - 2019-05-14
### Changed
- `JSONDeserializerTest`: modified to use `KType` values
- `JSONSerializer`: optimisation of data classes
- `JSONAuto`: added `stringify()`

## [0.3] - 2019-04-26
### Changed
- `JSONAuto` renamed to `JSONDeserializer`
- `JSONAutoTest` renamed to `JSONDeserializerTest`

### Added
- `JSONAuto`: new version (pass-through to `JSONDeserializer`)
- `DummyClasses` (test): split out from `JSONDeserializerTest`
- `JSONSerializer`
- `JSONSerializerTest`
- `JSONIgnore` annotation class

## [0.2] - 2019-04-23
### Changed
- `JSONAutoTest`: added more tests (derived classes)
- `JSONAuto`: added code to check for `@JSONName` annotation
- `JSONAutoTest`: added tests for `@JSONName` annotation

### Added
- `JSONName`: added annotation


## [0.1] - 2019-04-22
### Added
- `JSONAuto`: `parse` and `deserialize` functions
- `JSONAutoTest`
<file_sep>/*
 * @(#) JSONDeserializerFunctionsTest.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2021 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.test.Test
import kotlin.test.assertFailsWith
import kotlin.test.expect

import java.util.UUID

class JSONDeserializerFunctionsTest {

    @Test fun `should create UUID`() {
        val s = "dbfaa208-ac99-11eb-aca1-d790d50d0f28"
        expect(UUID.fromString(s)) { JSONDeserializerFunctions.createUUID(s) }
    }

    @Test fun `should throw exception on invalid UUID`() {
        val s = "dbfaa208-ac99-11eb-aca1-d790d50d0f" // 2 bytes too short
        assertFailsWith<IllegalArgumentException> { JSONDeserializerFunctions.createUUID(s) }.let {
            expect("Not a valid UUID - dbfaa208-ac99-11eb-aca1-d790d50d0f") { it.message }
        }
    }

}
<file_sep>/*
 * @(#) JSONDeserializerFunctions.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019, 2020, 2021 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.full.companionObject
import kotlin.reflect.full.functions
import kotlin.reflect.full.isSuperclassOf

import java.util.UUID

import net.pwall.json.validation.JSONValidation

object JSONDeserializerFunctions {

    private val fromJsonCache = HashMap<Pair<KClass<*>, KClass<*>>, KFunction<*>>()

    fun findFromJSON(resultClass: KClass<*>, parameterClass: KClass<*>): KFunction<*>? {
        val cacheKey = resultClass to parameterClass
        fromJsonCache[cacheKey]?.let { return it }
        val newEntry = try {
            resultClass.companionObject?.functions?.find { function ->
                function.name == "fromJSON" &&
                        function.parameters.size == 2 &&
                        function.parameters[0].type.classifier == resultClass.companionObject &&
                        (function.parameters[1].type.classifier as KClass<*>).isSuperclassOf(parameterClass) &&
                        function.returnType.classifier == resultClass
            }
        }
        catch (e: Exception) {
            null
        }
        return newEntry?.apply { fromJsonCache[cacheKey] = this }
    }

    fun KFunction<*>.hasSingleParameter(paramClass: KClass<*>) =
        parameters.size == 1 && (parameters[0].type.classifier as? KClass<*>)?.isSuperclassOf(paramClass) ?: false

    fun findParameterName(parameter: KParameter, config: JSONConfig): String? =
            config.findNameFromAnnotation(parameter.annotations) ?: parameter.name

    fun createUUID(string: String): UUID {
        if (!JSONValidation.isUUID(string))
            throw IllegalArgumentException("Not a valid UUID - $string")
        return UUID.fromString(string)
    }

}
<file_sep>/*
 * @(#) JSONFunTest.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019, 2020 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.reflect.KTypeProjection
import kotlin.reflect.full.createType
import kotlin.reflect.full.starProjectedType
import kotlin.test.assertNull
import kotlin.test.expect
import kotlin.test.Test

import java.lang.reflect.Type

class JSONFunTest {

    @Test fun `makeJSON with asJSONValue should create the same JSONObject as explicit creation`() {
        val expected = JSONObject().apply {
            putValue("testString", "xyz")
            putValue("testInt", 12345)
        }
        expect(expected) { makeJSON("testString" to "xyz".asJSONValue(), "testInt" to 12345.asJSONValue()) }
    }

    @Test fun `makeJSON with isJSON should create the same JSONObject as explicit creation`() {
        val expected = JSONObject().apply {
            putValue("testString", "xyz")
            putValue("testInt", 12345)
        }
        expect(expected) { makeJSON("testString" isJSON "xyz", "testInt" isJSON 12345) }
    }

    @Test fun `asJSONValue should correctly handle String`() {
        val testString = "testing..."
        expect(JSONString(testString)) { testString.asJSONValue() }
    }

    @Test fun `asJSONValue should correctly handle Char`() {
        val testChar = 'Q'
        expect(JSONString("Q")) { testChar.asJSONValue() }
    }

    @Test fun `asJSONValue should correctly handle Int`() {
        val testInt = 987654321
        expect(JSONInt(testInt)) { testInt.asJSONValue() }
    }

    @Test fun `asJSONValue should correctly handle Long`() {
        val testLong = 1234567890123456789
        expect(JSONLong(testLong)) { testLong.asJSONValue() }
    }

    @Test fun `asJSONValue should correctly handle Short`() {
        val testShort: Short = 1234
        expect(JSONInt(testShort.toInt())) { testShort.asJSONValue() }
    }

    @Test fun `asJSONValue should correctly handle Byte`() {
        val testByte: Byte = 123
        expect(JSONInt(testByte.toInt())) { testByte.asJSONValue() }
    }

    @Test fun `asJSONValue should correctly handle Float`() {
        val testFloat = 3.14159F
        expect(JSONFloat(testFloat)) { testFloat.asJSONValue() }
    }

    @Test fun `asJSONValue should correctly handle Double`() {
        val testDouble = 3.14159265358979323846
        expect(JSONDouble(testDouble)) { testDouble.asJSONValue() }
    }

    @Test fun `asJSONValue should correctly handle Boolean`() {
        val testBoolean = true
        expect(JSONBoolean(testBoolean)) { testBoolean.asJSONValue() }
    }

    @Test fun `isJSON should correctly handle String`() {
        val testString = "what next?"
        expect("testString" to JSONString(testString)) { "testString" isJSON testString }
    }

    @Test fun `isJSON should correctly handle Char`() {
        val testChar = 'Z'
        expect("testChar" to JSONString("Z")) { "testChar" isJSON testChar }
    }

    @Test fun `isJSON should correctly handle Int`() {
        val testInt = 987654321
        expect("testInt" to JSONInt(testInt)) { "testInt" isJSON testInt }
    }

    @Test fun `isJSON should correctly handle Long`() {
        val testLong = 1234567890123456789
        expect("testLong" to JSONLong(testLong)) { "testLong" isJSON testLong }
    }

    @Test fun `isJSON should correctly handle Short`() {
        val testShort: Short = 1234
        expect("testShort" to JSONInt(testShort.toInt())) { "testShort" isJSON testShort }
    }

    @Test fun `isJSON should correctly handle Byte`() {
        val testByte: Byte = 123
        expect("testByte" to JSONInt(testByte.toInt())) { "testByte" isJSON testByte }
    }

    @Test fun `isJSON should correctly handle Float`() {
        val testFloat = 3.14159F
        expect("testFloat" to JSONFloat(testFloat)) { "testFloat" isJSON testFloat }
    }

    @Test fun `isJSON should correctly handle Double`() {
        val testDouble = 3.14159265358979323846
        expect("testDouble" to JSONDouble(testDouble)) { "testDouble" isJSON testDouble }
    }

    @Test fun `isJSON should correctly handle Boolean`() {
        val testBoolean = true
        expect("testBoolean" to JSONBoolean(testBoolean)) { "testBoolean" isJSON testBoolean }
    }

    @Test fun `parseJSON should correctly parse string`() {
        val json = """{"field1":"Hi there!","field2":888}"""
        val expected: Dummy1? = Dummy1("Hi there!", 888)
        expect(expected) { json.parseJSON() }
    }

    @Test fun `parseJSON should correctly parse string using parameterised type`() {
        val json = """{"field1":"Hi there!","field2":888}"""
        expect(Dummy1("Hi there!", 888)) { json.parseJSON<Dummy1>() }
    }

    @Test fun `parseJSON should correctly parse string using explicit KClass`() {
        val json = """{"field1":"Hi there!","field2":888}"""
        expect(Dummy1("Hi there!", 888)) { json.parseJSON(Dummy1::class) }
    }

    @Test fun `parseJSON should correctly parse string using explicit KType`() {
        val json = """{"field1":"Hi there!","field2":888}"""
        expect(Dummy1("Hi there!", 888)) { json.parseJSON(Dummy1::class.starProjectedType) }
    }

    @Test fun `stringify should work on any object`() {
        val dummy1 = Dummy1("Hi there!", 888)
        val expected = JSONObject().apply {
            putValue("field1", "Hi there!")
            putValue("field2", 888)
        }
        expect(expected) { JSON.parse(dummy1.stringifyJSON()) }
    }

    @Test fun `stringify should work on null`() {
        val dummy1 = null
        expect("null") { dummy1.stringifyJSON() }
    }

    @Test fun `asJSONValue should correctly handle arbitrary object`() {
        val dummy1 = Dummy1("Hi there!", 888)
        expect(makeJSON("field1" isJSON "Hi there!", "field2" isJSON 888)) { dummy1.asJSONValue() }
    }

    @Test fun `asJSONValue should correctly handle null`() {
        val dummy1 = null
        assertNull(dummy1.asJSONValue())
    }

    @Test fun `targetKType should create correct type`() {
        val listStrings = listOf("abc", "def")
        val jsonArrayString = JSONArray().apply {
            addValue("abc")
            addValue("def")
        }
        expect(listStrings) { JSONDeserializer.deserialize(targetKType(List::class, String::class), jsonArrayString) }
    }

    @Test fun `targetKType should create correct complex type`() {
        val listStrings = listOf(listOf("abc", "def"))
        val jsonArrayArrayString = JSONArray().apply {
            add(JSONArray().apply {
                addValue("abc")
                addValue("def")
            })
        }
        expect(listStrings) {
            JSONDeserializer.deserialize(targetKType(List::class, targetKType(List::class, String::class)),
                    jsonArrayArrayString)
        }
    }

    @Test fun `toKType should convert simple class`() {
        val type: Type = JavaClass1::class.java
        expect(JavaClass1::class.starProjectedType) { type.toKType() }
    }

    @Test fun `toKType should convert parameterized class`() {
        val field = JavaClass2::class.java.getField("field1")
        val type: Type = field.genericType
        val expected = java.util.List::class.createType(
                listOf(KTypeProjection.invariant(JavaClass1::class.createType(nullable = true))))
        expect(expected) { type.toKType() }
    }

    @Test fun `toKType should convert parameterized class with extends`() {
        val field = JavaClass2::class.java.getField("field2")
        val type: Type = field.genericType
        val expected = java.util.List::class.createType(
                listOf(KTypeProjection.covariant(JavaClass1::class.createType(nullable = true))))
        expect(expected) { type.toKType() }
    }

    @Test fun `toKType should convert parameterized class with super`() {
        val field = JavaClass2::class.java.getField("field3")
        val type: Type = field.genericType
        val expected = java.util.List::class.createType(
                listOf(KTypeProjection.contravariant(JavaClass1::class.createType(nullable = true))))
        expect(expected) { type.toKType() }
    }

    @Test fun `toKType should convert nested parameterized class`() {
        val field = JavaClass2::class.java.getField("field4")
        val type: Type = field.genericType
        val expected = java.util.List::class.createType(
                listOf(KTypeProjection.invariant(java.util.List::class.createType(
                        listOf(KTypeProjection.invariant(JavaClass1::class.createType(nullable = true))),
                        nullable = true))))
        expect(expected) { type.toKType() }
    }

    @Test fun `toKType should convert nested parameterized class with extends`() {
        val field = JavaClass2::class.java.getField("field5")
        val type: Type = field.genericType
        val expected = java.util.List::class.createType(
                listOf(KTypeProjection.invariant(java.util.List::class.createType(
                        listOf(KTypeProjection.covariant(JavaClass1::class.createType(nullable = true))),
                        nullable = true))))
        expect(expected) { type.toKType() }
    }

    @Test fun `decode should convert a JSONObject to a specified type`() {
        val json = JSONObject().apply {
            putValue("field1", "abdef")
            putValue("field2", 54321)
        }
        val expected: Dummy1? = Dummy1("abdef", 54321)
        expect(expected) { json.deserialize(Dummy1::class.starProjectedType) }
    }

    @Test fun `decode should convert a JSONObject to a specified class`() {
        val json = JSONObject().apply {
            putValue("field1", "abdef")
            putValue("field2", 54321)
        }
        val expected: Dummy1? = Dummy1("abdef", 54321)
        expect(expected) { json.deserialize(Dummy1::class) }
    }

    @Test fun `decode should convert a JSONObject to an implied class`() {
        val json = JSONObject().apply {
            putValue("field1", "abdef")
            putValue("field2", 54321)
        }
        val expected: Dummy1? = Dummy1("abdef", 54321)
        expect(expected) { json.deserialize() }
    }

    @Test fun `decode should convert a JSONObject to a specified Java class`() {
        val json = JSONObject().apply {
            putValue("field1", "abdef")
            putValue("field2", 54321)
        }
        val expected: Dummy1? = Dummy1("abdef", 54321)
        expect(expected) { json.deserialize(Dummy1::class.java) }
    }

}
<file_sep>/*
 * @(#) JSONSerializer.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019, 2020, 2021 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.reflect.KProperty
import kotlin.reflect.full.staticProperties
import kotlin.reflect.jvm.isAccessible

import java.math.BigDecimal
import java.math.BigInteger
import java.util.BitSet
import java.util.Calendar
import java.util.Date
import java.util.Enumeration
import java.util.stream.BaseStream

import net.pwall.json.JSONKotlinException.Companion.fail
import net.pwall.json.JSONSerializerFunctions.findToJSON
import net.pwall.json.JSONSerializerFunctions.formatISO8601
import net.pwall.json.JSONSerializerFunctions.isSealedSubclass
import net.pwall.json.JSONSerializerFunctions.isToStringClass

/**
 * JSON Auto serialize for Kotlin.
 *
 * @author  <NAME>
 */
object JSONSerializer {

    fun serialize(obj: Any?, config: JSONConfig = JSONConfig.defaultConfig): JSONValue? =
            serialize(obj, config, mutableSetOf())

    private fun serialize(obj: Any?, config: JSONConfig, references: MutableSet<Any>): JSONValue? {

        if (obj == null)
            return null

        config.findToJSONMapping(obj::class)?.let { return it(obj) }

        when (obj) {

            is JSONValue -> return obj

            is CharSequence -> return JSONString(obj)

            is Char -> return JSONString(StringBuilder().append(obj))

            is Number -> return serializeNumber(obj, config)

            is Boolean -> return if (obj) JSONBoolean.TRUE else JSONBoolean.FALSE

            is CharArray -> return JSONString(StringBuilder().append(obj))

            is Array<*> -> return serializeArray(obj, config, references)

            is Pair<*, *> -> return serializePair(obj, config, references)

            is Triple<*, *, *> -> return serializeTriple(obj, config, references)
        }

        val objClass = obj::class

        if (objClass.isToStringClass() || obj is Enum<*>)
            return JSONString(obj.toString())

        try {
            objClass.findToJSON()?.let { return it.call(obj) }
        }
        catch (e: Exception) {
            fail("Error in custom toJSON - ${objClass.simpleName}", e)
        }

        when (obj) {

            is Iterable<*> -> return JSONArray(obj.map { serialize(it, config, references) })

            is Iterator<*> -> return JSONArray().apply {
                for (item in obj)
                    add(serialize(item, config, references))
            }

            is BaseStream<*, *> -> return JSONArray().apply {
                for (item in obj)
                    add(serialize(item, config, references))
            }

            is Sequence<*> -> return JSONArray().apply {
                for (item in obj)
                    add(serialize(item, config, references))
            }

            is Enumeration<*> -> return JSONArray().apply {
                for (item in obj)
                    add(serialize(item, config, references))
            }

            is Map<*, *> -> return serializeMap(obj, config, references)

            is Calendar -> return JSONString(obj.formatISO8601())

            is Date -> return JSONString((Calendar.getInstance().apply { time = obj }).formatISO8601())

            is BitSet -> return serializeBitSet(obj)

        }

        val result = JSONObject()

        try {
            references.add(obj)
            if (objClass.isSealedSubclass())
                result[config.sealedClassDiscriminator] = JSONString(objClass.simpleName ?: "null")

            val includeAll = config.hasIncludeAllPropertiesAnnotation(objClass.annotations)
            if (objClass.isData && objClass.constructors.isNotEmpty()) {
                // data classes will be a frequent use of serialization, so optimise for them
                val constructor = objClass.constructors.first()
                for (parameter in constructor.parameters) {
                    val member = objClass.members.find { it.name == parameter.name }
                    if (member is KProperty<*>)
                        result.addUsingGetter(member, parameter.annotations, obj, config, references, includeAll)
                }
                // now check whether there are any more properties not in constructor
                val statics: Collection<KProperty<*>> = objClass.staticProperties
                for (member in objClass.members) {
                    if (member is KProperty<*> && !statics.contains(member) &&
                            !constructor.parameters.any { it.name == member.name })
                        result.addUsingGetter(member, member.annotations, obj, config, references, includeAll)
                }
            }
            else {
                val statics: Collection<KProperty<*>> = objClass.staticProperties
                for (member in objClass.members) {
                    if (member is KProperty<*> && !statics.contains(member)) {
                        val combinedAnnotations = ArrayList(member.annotations)
                        objClass.constructors.firstOrNull()?.parameters?.find { it.name == member.name }?.let {
                            combinedAnnotations.addAll(it.annotations)
                        }
                        result.addUsingGetter(member, combinedAnnotations, obj, config, references, includeAll)
                    }
                }
            }
        }
        finally {
            references.remove(obj)
        }
        return result
    }

    private fun JSONObject.addUsingGetter(member: KProperty<*>, annotations: List<Annotation>?, obj: Any,
            config: JSONConfig, references: MutableSet<Any>, includeAll: Boolean) {
        if (!config.hasIgnoreAnnotation(annotations)) {
            val name = config.findNameFromAnnotation(annotations) ?: member.name
            val wasAccessible = member.isAccessible
            member.isAccessible = true
            try {
                val v = member.getter.call(obj)
                if (v != null && v in references)
                    fail("Circular reference: field ${member.name} in ${obj::class.simpleName}")
                if (v != null || config.hasIncludeIfNullAnnotation(annotations) || config.includeNulls || includeAll)
                    put(name, serialize(v, config, references))
            }
            catch (e: JSONException) {
                throw e
            }
            catch (e: Exception) {
                fail("Error getting property ${member.name} from ${obj::class.simpleName}", e)
            }
            finally {
                member.isAccessible = wasAccessible
            }
        }
    }

    private fun serializeNumber(number: Number, config: JSONConfig): JSONValue = when (number) {

        is Int -> JSONInt(number)

        is Long -> JSONLong(number)

        is Short -> JSONInt(number.toInt())

        is Byte -> JSONInt(number.toInt())

        is Float -> JSONFloat(number)

        is BigInteger -> if (config.bigIntegerString) JSONString(number.toString()) else JSONLong(number.toLong())

        is BigDecimal -> if (config.bigDecimalString) JSONString(number.toString()) else JSONDecimal(number)

        else -> JSONDouble(number.toDouble())

    }

    private fun serializeArray(array: Array<*>, config: JSONConfig, references: MutableSet<Any>): JSONValue {

        if (array.isArrayOf<Char>())
            return JSONString(StringBuilder().apply {
                for (ch in array)
                    append(ch)
            })

        return JSONArray(array.map { serialize(it, config, references) })
    }

    private fun serializePair(pair: Pair<*, *>, config: JSONConfig, references: MutableSet<Any>) = JSONArray().apply {
        add(serialize(pair.first, config, references))
        add(serialize(pair.second, config, references))
    }

    private fun serializeTriple(triple: Triple<*, *, *>, config: JSONConfig, references: MutableSet<Any>) =
            JSONArray().apply {
        add(serialize(triple.first, config, references))
        add(serialize(triple.second, config, references))
        add(serialize(triple.third, config, references))
    }

    private fun serializeMap(map: Map<*, *>, config: JSONConfig, references: MutableSet<Any>) = JSONObject().apply {
        for (entry in map.entries) {
            this[entry.key.toString()] = serialize(entry.value, config, references)
        }
    }

    private fun serializeBitSet(bitSet: BitSet) = JSONArray(ArrayList<JSONValue>().apply {
        for (i in 0 until bitSet.length())
            if (bitSet.get(i))
                add(JSONInt(i))
    })

}
<file_sep>/*
 * @(#) JSONStringifyTest.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2020, 2021 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.test.assertFailsWith
import kotlin.test.expect
import kotlin.test.Test

import java.math.BigDecimal
import java.math.BigInteger
import java.net.URI
import java.net.URL
import java.time.Duration
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.MonthDay
import java.time.OffsetDateTime
import java.time.OffsetTime
import java.time.Period
import java.time.Year
import java.time.YearMonth
import java.time.ZonedDateTime
import java.util.BitSet
import java.util.Calendar
import java.util.TimeZone
import java.util.UUID
import java.util.stream.IntStream
import java.util.stream.Stream

import net.pwall.json.JSONStringify.appendJSON
import net.pwall.json.test.JSONExpect.Companion.expectJSON

class JSONStringifyTest {

    @Test fun `should stringify null`() {
        expect("null") { JSONStringify.stringify(null) }
    }

    @Test fun `should use toJSON if specified in JSONConfig`() {
        val config = JSONConfig().apply {
            toJSON<Dummy1> { obj ->
                obj?.let {
                    JSONObject().apply {
                        putValue("a", it.field1)
                        putValue("b", it.field2)
                    }
                }
            }
        }
        expectJSON(JSONStringify.stringify(Dummy1("xyz", 888), config)) {
            count(2)
            property("a", "xyz")
            property("b", 888)
        }
    }

    @Test fun `should stringify a JSONValue`() {
        val json = JSONObject().apply {
            putValue("a", "Hello")
            putValue("b", 27)
        }
        expect("""{"a":"Hello","b":27}""") { JSONStringify.stringify(json) }
    }

    @Test fun `should stringify a simple string`() {
        expect("\"abc\"") { JSONStringify.stringify("abc") }
    }

    @Test fun `should stringify a string with a newline`() {
        expect("\"a\\nc\"") { JSONStringify.stringify("a\nc") }
    }

    @Test fun `should stringify a string with a unicode sequence`() {
        expect("\"a\\u2014c\"") { JSONStringify.stringify("a\u2014c") }
    }

    @Test fun `should stringify a single character`() {
        expect("\"X\"") { JSONStringify.stringify('X') }
    }

    @Test fun `should stringify a charArray`() {
        expect("\"abc\"") { JSONStringify.stringify(charArrayOf('a', 'b', 'c')) }
    }

    @Test fun `should stringify an integer`() {
        expect("123456789") { JSONStringify.stringify(123456789) }
    }

    @Test fun `should stringify an integer 0`() {
        expect("0") { JSONStringify.stringify(0) }
    }

    @Test fun `should stringify a negative integer`() {
        expect("-888") { JSONStringify.stringify(-888) }
    }

    @Test fun `should stringify a long`() {
        expect("123456789012345678") { JSONStringify.stringify(123456789012345678) }
    }

    @Test fun `should stringify a negative long`() {
        expect("-111222333444555666") { JSONStringify.stringify(-111222333444555666) }
    }

    @Test fun `should stringify a short`() {
        val x: Short = 12345
        expect("12345") { JSONStringify.stringify(x) }
    }

    @Test fun `should stringify a negative short`() {
        val x: Short = -4444
        expect("-4444") { JSONStringify.stringify(x) }
    }

    @Test fun `should stringify a byte`() {
        val x: Byte = 123
        expect("123") { JSONStringify.stringify(x) }
    }

    @Test fun `should stringify a negative byte`() {
        val x: Byte = -99
        expect("-99") { JSONStringify.stringify(x) }
    }

    @Test fun `should stringify a float`() {
        val x = 1.2345F
        expect("1.2345") { JSONStringify.stringify(x) }
    }

    @Test fun `should stringify a negative float`() {
        val x: Float = -567.888F
        expect("-567.888") { JSONStringify.stringify(x) }
    }

    @Test fun `should stringify a double`() {
        val x = 1.23456789
        expect("1.23456789") { JSONStringify.stringify(x) }
    }

    @Test fun `should stringify a negative double`() {
        val x = -9.998877665
        expect("-9.998877665") { JSONStringify.stringify(x) }
    }

    @Test fun `should stringify a BigInteger`() {
        val x = BigInteger.valueOf(123456789000)
        expect("123456789000") { JSONStringify.stringify(x) }
    }

    @Test fun `should stringify a negative BigInteger`() {
        val x = BigInteger.valueOf(-123456789000)
        expect("-123456789000") { JSONStringify.stringify(x) }
    }

    @Test fun `should stringify a BigInteger as string when specified in JSONConfig`() {
        val config = JSONConfig().apply {
            bigIntegerString = true
        }
        val x = BigInteger.valueOf(123456789000)
        expect("\"123456789000\"") { JSONStringify.stringify(x, config) }
    }

    @Test fun `should stringify a BigDecimal`() {
        val x = BigDecimal("12345.678")
        expect("12345.678") { JSONStringify.stringify(x) }
    }

    @Test fun `should stringify a BigDecimal as string when specified in JSONConfig`() {
        val config = JSONConfig().apply {
            bigDecimalString = true
        }
        val x = BigDecimal("12345.678")
        expect("\"12345.678\"") { JSONStringify.stringify(x, config) }
    }

    @Test fun `should stringify a Boolean`() {
        var x = true
        expect("true") { JSONStringify.stringify(x) }
        x = false
        expect("false") { JSONStringify.stringify(x) }
    }

    @Test fun `should stringify an array of characters`() {
        expect("\"abc\"") { JSONStringify.stringify(arrayOf('a', 'b', 'c')) }
    }

    @Test fun `should stringify an array of integers`() {
        expect("[123,456,789]") { JSONStringify.stringify(arrayOf(123, 456, 789)) }
    }

    @Test fun `should stringify an array of strings`() {
        expect("""["Hello","World"]""") { JSONStringify.stringify(arrayOf("Hello", "World")) }
    }

    @Test fun `should stringify an array of arrays`() {
        expect("[[11,22],[33,44]]") { JSONStringify.stringify(arrayOf(arrayOf(11, 22), arrayOf(33, 44))) }
    }

    @Test fun `should stringify an empty array`() {
        expect("[]") { JSONStringify.stringify(emptyArray<Int>()) }
    }

    @Test fun `should stringify an array of nulls`() {
        expect("[null,null,null]") { JSONStringify.stringify(arrayOfNulls<String>(3)) }
    }

    @Test fun `should stringify a Pair of integers`() {
        expect("[123,789]") { JSONStringify.stringify(123 to 789) }
    }

    @Test fun `should stringify a Pair of strings`() {
        expect("""["back","front"]""") { JSONStringify.stringify("back" to "front") }
    }

    @Test fun `should stringify a Triple of integers`() {
        expect("[123,789,0]") { JSONStringify.stringify(Triple(123, 789, 0)) }
    }

    @Test fun `should stringify a class with a custom toJson`() {
        expect("""{"dec":"49","hex":"31"}""") { JSONStringify.stringify(DummyFromJSON(49)) }
    }

    @Test fun `should stringify a list of integers`() {
        expect("[1234,4567,7890]") { JSONStringify.stringify(listOf(1234, 4567, 7890)) }
    }

    @Test fun `should stringify a list of strings`() {
        expect("""["alpha","beta","gamma"]""") { JSONStringify.stringify(listOf("alpha", "beta", "gamma")) }
    }

    @Test fun `should stringify a list of strings including null`() {
        expect("""["alpha","beta",null,"gamma"]""") { JSONStringify.stringify(listOf("alpha", "beta", null, "gamma")) }
    }

    @Test fun `should stringify a set of strings`() {
        // unfortunately, we don't know in what order a set iterator will return the entries, so...
        val str = JSONStringify.stringify(setOf("alpha", "beta", "gamma"))
        expect(true) { str.startsWith('[') && str.endsWith(']')}
        val items = str.drop(1).dropLast(1).split(',').sorted()
        expect(3) { items.size }
        expect("\"alpha\"") { items[0] }
        expect("\"beta\"") { items[1] }
        expect("\"gamma\"") { items[2] }
    }

    @Test fun `should stringify the results of an iterator`() {
        val list = listOf(1, 1, 2, 3, 5, 8, 13, 21)
        expect("[1,1,2,3,5,8,13,21]") { JSONStringify.stringify(list.iterator()) }
    }

    @Test fun `should stringify a sequence`() {
        val list = listOf(1, 1, 2, 3, 5, 8, 13, 21)
        expect("[1,1,2,3,5,8,13,21]") { JSONStringify.stringify(list.asSequence()) }
    }

    @Test fun `should stringify the results of an enumeration`() {
        val list = listOf("tahi", "rua", "toru", "wh\u0101")
        expect("""["tahi","rua","toru","wh\u0101"]""") { JSONStringify.stringify(ListEnum(list)) }
    }

    @Test fun `should stringify a Java Stream of strings`() {
        val stream = Stream.of("tahi", "rua", "toru", "wh\u0101")
        expect("""["tahi","rua","toru","wh\u0101"]""") { JSONStringify.stringify(stream) }
    }

    @Test fun `should stringify a Java IntStream`() {
        val stream = IntStream.of(1, 1, 2, 3, 5, 8, 13, 21)
        expect("[1,1,2,3,5,8,13,21]") { JSONStringify.stringify(stream) }
    }

    @Test fun `should stringify a map of string to string`() {
        val map = mapOf("tahi" to "one", "rua" to "two", "toru" to "three")
        val str = JSONStringify.stringify(map)
        expectJSON(str) {
            count(3)
            property("tahi", "one")
            property("rua", "two")
            property("toru", "three")
        }
    }

    @Test fun `should stringify a map of string to integer`() {
        val map = mapOf("un" to 1, "deux" to 2, "trois" to 3, "quatre" to 4)
        val str = JSONStringify.stringify(map)
        expectJSON(str) {
            count(4)
            property("un", 1)
            property("deux", 2)
            property("trois", 3)
            property("quatre", 4)
        }
    }

    @Test fun `should stringify an enum`() {
        val value = DummyEnum.ALPHA
        expect("\"ALPHA\"") { JSONStringify.stringify(value) }
    }

    @Test fun `should stringify an SQL date`() {
        val str = "2020-04-10"
        val date = java.sql.Date.valueOf(str)
        expect("\"$str\"") { JSONStringify.stringify(date) }
    }

    @Test fun `should stringify an SQL time`() {
        val str = "00:21:22"
        val time = java.sql.Time.valueOf(str)
        expect("\"$str\"") { JSONStringify.stringify(time) }
    }

    @Test fun `should stringify an SQL date-time`() {
        val str = "2020-04-10 00:21:22.0"
        val timeStamp = java.sql.Timestamp.valueOf(str)
        expect("\"$str\"") { JSONStringify.stringify(timeStamp) }
    }

    @Test fun `should stringify an Instant`() {
        val str = "2020-04-09T14:28:51.234Z"
        val instant = Instant.parse(str)
        expect("\"$str\"") { JSONStringify.stringify(instant) }
    }

    @Test fun `should stringify a LocalDate`() {
        val str = "2020-04-10"
        val localDate = LocalDate.parse(str)
        expect("\"$str\"") { JSONStringify.stringify(localDate) }
    }

    @Test fun `should stringify a LocalDateTime`() {
        val str = "2020-04-10T00:09:26.123"
        val localDateTime = LocalDateTime.parse(str)
        expect("\"$str\"") { JSONStringify.stringify(localDateTime) }
    }

    @Test fun `should stringify a LocalTime`() {
        val str = "00:09:26.123"
        val localTime = LocalTime.parse(str)
        expect("\"$str\"") { JSONStringify.stringify(localTime) }
    }

    @Test fun `should stringify an OffsetTime`() {
        val str = "10:15:06.543+10:00"
        val offsetTime = OffsetTime.parse(str)
        expect("\"$str\"") { JSONStringify.stringify(offsetTime) }
    }

    @Test fun `should stringify an OffsetDateTime`() {
        val str = "2020-04-10T10:15:06.543+10:00"
        val offsetDateTime = OffsetDateTime.parse(str)
        expect("\"$str\"") { JSONStringify.stringify(offsetDateTime) }
    }

    @Test fun `should stringify a ZonedDateTime`() {
        val str = "2020-04-10T10:15:06.543+10:00[Australia/Sydney]"
        val zonedDateTime = ZonedDateTime.parse(str)
        expect("\"$str\"") { JSONStringify.stringify(zonedDateTime) }
    }

    @Test fun `should stringify a Year`() {
        val str = "2020"
        val year = Year.parse(str)
        expect("\"$str\"") { JSONStringify.stringify(year) }
    }

    @Test fun `should stringify a YearMonth`() {
        val str = "2020-04"
        val yearMonth = YearMonth.parse(str)
        expect("\"$str\"") { JSONStringify.stringify(yearMonth) }
    }

    @Test fun `should stringify a MonthDay`() {
        val str = "--04-23"
        val monthDay = MonthDay.parse(str)
        expect("\"$str\"") { JSONStringify.stringify(monthDay) }
    }

    @Test fun `should stringify a Duration`() {
        val str = "PT2H"
        val duration = Duration.parse(str)
        expect("\"$str\"") { JSONStringify.stringify(duration) }
    }

    @Test fun `should stringify a Period`() {
        val str = "P3M"
        val period = Period.parse(str)
        expect("\"$str\"") { JSONStringify.stringify(period) }
    }

    @Test fun `should stringify a URI`() {
        val str = "http://pwall.net"
        val uri = URI(str)
        expect("\"$str\"") { JSONStringify.stringify(uri) }
    }

    @Test fun `should stringify a URL`() {
        val str = "http://pwall.net"
        val url = URL(str)
        expect("\"$str\"") { JSONStringify.stringify(url) }
    }

    @Test fun `should stringify a UUID`() {
        val str = "e24b6740-7ac3-11ea-9e47-37640adfe63a"
        val uuid = UUID.fromString(str)
        expect("\"$str\"") { JSONStringify.stringify(uuid) }
    }

    @Test fun `should stringify a Calendar`() {
        val cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")).apply {
            set(Calendar.YEAR, 2019)
            set(Calendar.MONTH, 3)
            set(Calendar.DAY_OF_MONTH, 25)
            set(Calendar.HOUR_OF_DAY, 18)
            set(Calendar.MINUTE, 52)
            set(Calendar.SECOND, 47)
            set(Calendar.MILLISECOND, 123)
            set(Calendar.ZONE_OFFSET, 10 * 60 * 60 * 1000)
        }
        expect("\"2019-04-25T18:52:47.123+10:00\"") { JSONStringify.stringify(cal) }
    }

    @Test fun `should stringify a Date`() {
        val cal = Calendar.getInstance().apply {
            set(Calendar.YEAR, 2019)
            set(Calendar.MONTH, 3)
            set(Calendar.DAY_OF_MONTH, 25)
            set(Calendar.HOUR_OF_DAY, 18)
            set(Calendar.MINUTE, 52)
            set(Calendar.SECOND, 47)
            set(Calendar.MILLISECOND, 123)
            set(Calendar.ZONE_OFFSET, 10 * 60 * 60 * 1000)
        }
        val date = cal.time
        // NOTE - Java implementations are inconsistent - some will normalise the time to UTC
        // while others preserve the time zone as supplied.  The test below allows for either.
        val expected1 = "\"2019-04-25T18:52:47.123+10:00\""
        val expected2 = "\"2019-04-25T08:52:47.123Z\""
        val result = JSONStringify.stringify(date)
        expect(true) { result == expected1 || result == expected2 }
    }

    @Test fun `should stringify a BitSet`() {
        val bitSet = BitSet().apply {
            set(1)
            set(3)
        }
        expect("[1,3]") { JSONStringify.stringify(bitSet) }
    }

    @Test fun `should stringify a simple object`() {
        val dummy1 = Dummy1("abcdef", 98765)
        expect("""{"field1":"abcdef","field2":98765}""") { JSONStringify.stringify(dummy1) }
    }

    @Test fun `should stringify a simple object with extra property`() {
        val dummy2 = Dummy2("abcdef", 98765)
        dummy2.extra = "extra123"
        expect("""{"field1":"abcdef","field2":98765,"extra":"extra123"}""") { JSONStringify.stringify(dummy2) }
    }

    @Test fun `should stringify a simple object with extra property omitting null field`() {
        val dummy2 = Dummy2("abcdef", 98765)
        dummy2.extra = null
        expect("""{"field1":"abcdef","field2":98765}""") { JSONStringify.stringify(dummy2) }
    }

    @Test fun `should stringify a simple object with extra property including null field when config set`() {
        val dummy2 = Dummy2("abcdef", 98765)
        dummy2.extra = null
        val config = JSONConfig().apply {
            includeNulls = true
        }
        expect("""{"field1":"abcdef","field2":98765,"extra":null}""") { JSONStringify.stringify(dummy2, config) }
    }

    @Test fun `should stringify an object of a derived class`() {
        val obj = Derived()
        obj.field1 = "qwerty"
        obj.field2 = 98765
        obj.field3 = 0.012
        val json = JSONStringify.stringify(obj)
        expectJSON(json) {
            count(3)
            property("field1", "qwerty")
            property("field2", 98765)
            property("field3", BigDecimal("0.012"))
        }
    }

    @Test fun `should stringify an object using name annotation`() {
        val obj = DummyWithNameAnnotation()
        obj.field1 = "qwerty"
        obj.field2 = 98765
        val json = JSONStringify.stringify(obj)
        expectJSON(json) {
            count(2)
            property("field1", "qwerty")
            property("fieldX", 98765)
        }
    }

    @Test fun `should stringify an object using parameter name annotation`() {
        val obj = DummyWithParamNameAnnotation("abc", 123)
        val json = JSONStringify.stringify(obj)
        expectJSON(json) {
            count(2)
            property("field1", "abc")
            property("fieldX", 123)
        }
    }

    @Test fun `should stringify an object using custom parameter name annotation`() {
        val obj = DummyWithCustomNameAnnotation("abc", 123)
        val config = JSONConfig().apply {
            addNameAnnotation(CustomName::class, "symbol")
        }
        val json = JSONStringify.stringify(obj, config)
        expectJSON(json) {
            count(2)
            property("field1", "abc")
            property("fieldX", 123)
        }
    }

    @Test fun `should stringify a nested object`() {
        val obj1 = Dummy1("asdfg", 987)
        val obj3 = Dummy3(obj1, "what?")
        val json = JSONStringify.stringify(obj3)
        expectJSON(json) {
            count(2)
            property("text", "what?")
            property("dummy1") {
                count(2)
                property("field1", "asdfg")
                property("field2", 987)
            }
        }
    }

    @Test fun `should stringify an object with @JSONIgnore`() {
        val obj = DummyWithIgnore("alpha", "beta", "gamma")
        val json = JSONStringify.stringify(obj)
        expectJSON(json) {
            count(2)
            property("field1", "alpha")
            property("field3", "gamma")
        }
    }

    @Test fun `should stringify an object with custom ignore annotation`() {
        val obj = DummyWithCustomIgnore("alpha", "beta", "gamma")
        val config = JSONConfig().apply {
            addIgnoreAnnotation(CustomIgnore::class)
        }
        val json = JSONStringify.stringify(obj, config)
        expectJSON(json) {
            count(2)
            property("field1", "alpha")
            property("field3", "gamma")
        }
    }

    @Test fun `should stringify an object with @JSONIncludeIfNull`() {
        val obj = DummyWithIncludeIfNull("alpha", null, "gamma")
        val json = JSONStringify.stringify(obj)
        expectJSON(json) {
            count(3)
            property("field1", "alpha")
            property("field2", null)
            property("field3", "gamma")
        }
    }

    @Test fun `should stringify an object with custom include if null annotation`() {
        val obj = DummyWithCustomIncludeIfNull("alpha", null, "gamma")
        val config = JSONConfig().apply {
            addIncludeIfNullAnnotation(CustomIncludeIfNull::class)
        }
        val json = JSONStringify.stringify(obj, config)
        expectJSON(json) {
            count(3)
            property("field1", "alpha")
            property("field2", null)
            property("field3", "gamma")
        }
    }

    @Test fun `should stringify an object with @JSONIncludeAllProperties`() {
        val obj = DummyWithIncludeAllProperties("alpha", null, "gamma")
        val json = JSONStringify.stringify(obj)
        expectJSON(json) {
            count(3)
            property("field1", "alpha")
            property("field2", null)
            property("field3", "gamma")
        }
    }

    @Test fun `should stringify an object with custom include all properties annotation`() {
        val obj = DummyWithCustomIncludeAllProperties("alpha", null, "gamma")
        val config = JSONConfig().apply {
            addIncludeAllPropertiesAnnotation(CustomIncludeAllProperties::class)
        }
        val json = JSONStringify.stringify(obj, config)
        expectJSON(json) {
            count(3)
            property("field1", "alpha")
            property("field2", null)
            property("field3", "gamma")
        }
    }

    @Test fun `should fail on use of circular reference`() {
        val circular1 = Circular1()
        val circular2 = Circular2()
        circular1.ref = circular2
        circular2.ref = circular1
        val exception = assertFailsWith<JSONKotlinException> {
            JSONStringify.stringify(circular1)
        }
        expect("Circular reference: field ref in Circular2") { exception.message }
    }

    @Test fun `should append to existing Appendable`() {
        val sb = StringBuilder()
        sb.appendJSON(Dummy1("Testing..."))
        val json = sb.toString()
        expectJSON(json) {
            count(2)
            property("field1", "Testing...")
            property("field2", 999)
        }
    }

}
<file_sep>/*
 * @(#) JSONDeserializerTest.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019, 2020, 2021 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.collections.ArrayList
import kotlin.collections.LinkedHashMap
import kotlin.reflect.KTypeProjection
import kotlin.reflect.full.createType
import kotlin.reflect.full.starProjectedType
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNull
import kotlin.test.assertSame
import kotlin.test.assertTrue
import kotlin.test.expect
import kotlin.test.fail
import kotlin.test.Test

import java.lang.reflect.Type
import java.math.BigDecimal
import java.math.BigInteger
import java.net.URI
import java.net.URL
import java.time.Duration
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.MonthDay
import java.time.OffsetDateTime
import java.time.OffsetTime
import java.time.Period
import java.time.Year
import java.time.YearMonth
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.ZonedDateTime
import java.util.Arrays
import java.util.BitSet
import java.util.Calendar
import java.util.Date
import java.util.LinkedList
import java.util.TimeZone
import java.util.UUID
import java.util.stream.DoubleStream
import java.util.stream.IntStream
import java.util.stream.LongStream
import java.util.stream.Stream

class JSONDeserializerTest {

    @Test fun `null input should return null`() {
        assertNull(JSONDeserializer.deserialize(String::class, null))
    }

    @Test fun `null input should cause exception for non-null function`() {
        val e = assertFailsWith<JSONKotlinException> { JSONDeserializer.deserializeNonNull(String::class, null) }
        expect("Can't deserialize null as String") { e.message }
    }

    @Test fun `when JSONValue expected it should pass through unchanged`() {
        val json = JSONDouble(0.1)
        assertSame(json, JSONDeserializer.deserialize(JSONValue::class,  json))
        assertSame(json, JSONDeserializer.deserialize(JSONDouble::class,  json))
        val json2 = JSONString("abc")
        assertSame(json2, JSONDeserializer.deserialize(JSONValue::class,  json2))
        assertSame(json2, JSONDeserializer.deserialize(JSONString::class,  json2))
    }

    @Test fun `companion object with fromJSON should use that function`() {
        val json = JSONObject().apply {
            putValue("dec", "17")
            putValue("hex", "11")
        }
        val expected: DummyFromJSON? = DummyFromJSON(17)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `companion object with multiple fromJSON should use the correct function`() {
        val json1 = JSONObject().apply {
            putValue("dec", "17")
            putValue("hex", "11")
        }
        val expected1: DummyMultipleFromJSON? = DummyMultipleFromJSON(17)
        expect(expected1) { JSONDeserializer.deserialize(json1) }
        val json2 = JSONInt(300)
        val expected2: DummyMultipleFromJSON? = DummyMultipleFromJSON(300)
        expect(expected2) { JSONDeserializer.deserialize(json2) }
        val json3 = JSONString("FF")
        val expected3: DummyMultipleFromJSON? = DummyMultipleFromJSON(255)
        expect(expected3) { JSONDeserializer.deserialize(json3) }
    }

    @Test fun `JSONString should return string`() {
        val json = JSONString("abc")
        val expected: String? = "abc"
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `single character JSONString should return character`() {
        val json = JSONString("Q")
        val expected: Char? = 'Q'
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return character array`() {
        val json = JSONString("abcdef")
        val expected: CharArray? = arrayOf('a', 'b', 'c', 'd', 'e', 'f').toCharArray()
        assertTrue(Arrays.equals(expected, JSONDeserializer.deserialize(json)))
    }

    @Test fun `JSONString should return array of Char`() {
        val json = JSONString("abcdef")
        val expected: Array<Char>? = arrayOf('a', 'b', 'c', 'd', 'e', 'f')
        assertTrue(Arrays.equals(expected, JSONDeserializer.deserialize(Array<Char>::class, json)))
    }

    @Test fun `JSONString should return Calendar`() {
        val json = JSONString("2019-04-19T15:34:02.234+10:00")
        val cal = Calendar.getInstance()
        cal.set(2019, 3, 19, 15, 34, 2) // month value is month - 1
        cal.set(Calendar.MILLISECOND, 234)
        cal.set(Calendar.ZONE_OFFSET, 10 * 60 * 60 * 1000)
        assertTrue(calendarEquals(cal, JSONDeserializer.deserialize(json)!!))
    }

    @Test fun `JSONString should return Date`() {
        val json = JSONString("2019-03-10T15:34:02.234+11:00")
        val cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"))
        cal.set(2019, 2, 10, 15, 34, 2) // month value is month - 1
        cal.set(Calendar.MILLISECOND, 234)
        cal.set(Calendar.ZONE_OFFSET, 11 * 60 * 60 * 1000)
        val expected: Date? = cal.time
        expect(expected) { JSONDeserializer.deserialize(json) }
//        val json2 = JSONString("2019-03-11")
//        cal.clear()
//        cal.set(2019, 2, 11, 0, 0, 0)
//        cal.set(Calendar.MILLISECOND, 0)
//        cal.set(Calendar.ZONE_OFFSET, 0)
//        val expected2: Date? = cal.time
//        expect(expected2) { JSONDeserializer.deserialize(json2) }
    }

    @Test fun `JSONString should return java-sql-Date`() {
        val json = JSONString("2019-03-10")
        val expected: java.sql.Date? = java.sql.Date.valueOf("2019-03-10")
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return java-sql-Time`() {
        val json = JSONString("22:45:41")
        val expected: java.sql.Time? = java.sql.Time.valueOf("22:45:41")
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return java-sql-Timestamp`() {
        val json = JSONString("2019-03-10 22:45:41.5")
        val expected: java.sql.Timestamp? = java.sql.Timestamp.valueOf("2019-03-10 22:45:41.5")
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return Instant`() {
        val json = JSONString("2019-03-10T15:34:02.234Z")
        val cal = Calendar.getInstance()
        cal.timeZone = TimeZone.getTimeZone("GMT")
        cal.set(2019, 2, 10, 15, 34, 2) // month value is month - 1
        cal.set(Calendar.MILLISECOND, 234)
        cal.set(Calendar.ZONE_OFFSET, 0)
        val expected: Instant? = Instant.ofEpochMilli(cal.timeInMillis)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return LocalDate`() {
        val json = JSONString("2019-03-10")
        val expected: LocalDate? = LocalDate.of(2019, 3, 10)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return LocalDateTime`() {
        val json = JSONString("2019-03-10T16:43:33")
        val expected: LocalDateTime? = LocalDateTime.of(2019, 3, 10, 16, 43, 33)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return LocalTime`() {
        val json = JSONString("16:43:33")
        val expected: LocalTime? = LocalTime.of(16, 43, 33)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return OffsetTime`() {
        val json = JSONString("16:46:11.234+10:00")
        val expected: OffsetTime? = OffsetTime.of(16, 46, 11, 234000000, ZoneOffset.ofHours(10))
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return OffsetDateTime`() {
        val json = JSONString("2019-03-10T16:46:11.234+10:00")
        val expected: OffsetDateTime? = OffsetDateTime.of(2019, 3, 10, 16, 46, 11, 234000000, ZoneOffset.ofHours(10))
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return ZonedDateTime`() {
        val json = JSONString("2019-01-10T16:46:11.234+11:00[Australia/Sydney]")
        val expected: ZonedDateTime? = ZonedDateTime.of(2019, 1, 10, 16, 46, 11, 234000000,
                ZoneId.of("Australia/Sydney"))
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return Year`() {
        val json = JSONString("2019")
        val expected: Year? = Year.of(2019)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return YearMonth`() {
        val json = JSONString("2019-03")
        val expected: YearMonth? = YearMonth.of(2019, 3)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return MonthDay`() {
        val json = JSONString("--03-10")
        val expected: MonthDay? = MonthDay.of(3, 10)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return Duration`() {
        val json = JSONString("PT2H")
        val expected: Duration? = Duration.ofHours(2)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return Period`() {
        val json = JSONString("P3M")
        val expected: Period? = Period.ofMonths(3)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return UUID`() {
        val uuid = "b082b046-ac9b-11eb-8ea7-5fc81989f104"
        val json = JSONString(uuid)
        val expected: UUID? = UUID.fromString("b082b046-ac9b-11eb-8ea7-5fc81989f104")
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should fail on invalid UUID`() {
        val json = JSONString("b082b046-ac9b-11eb-8ea7-5fc81989f1") // 2 bytes too short
        assertFailsWith<JSONException> { JSONDeserializer.deserialize<UUID>(json) }.let {
            expect("Error deserializing \"b082b046-ac9b-11eb-8ea7-5fc81989f1\" as UUID") { it.message }
        }
    }

    @Test fun `JSONString should return URI`() {
        val uriString = "http://pwall.net"
        val json = JSONString(uriString)
        val expected: URI? = URI(uriString)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return URL`() {
        val urlString = "https://pwall.net"
        val json = JSONString(urlString)
        val expected: URL? = URL(urlString)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONArray should return BitSet`() {
        val bitset = BitSet()
        bitset.set(2)
        bitset.set(7)
        val json = JSONArray().apply {
            addValue(2)
            addValue(7)
        }
        val expected: BitSet? = bitset
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return enum`() {
        val json = JSONString("ALPHA")
        val expected: DummyEnum? = DummyEnum.ALPHA
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return BigInteger`() {
        val str = "123456789"
        val json = JSONString(str)
        val expected: BigInteger? = BigInteger(str)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONLong should return BigInteger`() {
        val value = 123456789012345678
        val json = JSONLong(value)
        val expected: BigInteger? = BigInteger.valueOf(value)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONInt should return BigInteger`() {
        val value = 12345678
        val json = JSONInt(value)
        val expected: BigInteger? = BigInteger.valueOf(value.toLong())
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONString should return BigDecimal`() {
        val str = "123456789.77777"
        val json = JSONString(str)
        val expected: BigDecimal? = BigDecimal(str)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONDecimal should return BigDecimal`() {
        val str = "123456789.77777"
        val json = JSONDecimal(str)
        val expected: BigDecimal? = BigDecimal(str)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONDouble should return BigDecimal`() {
        val value = 1234.5
        val json = JSONDouble(value)
        val expected: BigDecimal? = BigDecimal(value)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONInt should return Int`() {
        val json = JSONInt(1234)
        val expected: Int? = 1234
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONLong should return Long`() {
        val json = JSONLong(123456789012345)
        val expected: Long? = 123456789012345
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONDouble should return Double`() {
        val json = JSONDouble(123.45)
        val expected: Double? = 123.45
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONInt should return Short`() {
        val json = JSONInt(1234)
        val expected: Short? = 1234
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONInt should return Byte`() {
        val json = JSONInt(123)
        val expected: Byte? = 123
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONArray of boolean should return BooleanArray`() {
        val json = JSONArray().apply {
            addValue(true)
            addValue(false)
            addValue(false)
        }
        val expected = booleanArrayOf(true, false, false)
        assertTrue(expected.contentEquals(JSONDeserializer.deserialize(BooleanArray::class, json) ?: fail()))
    }

    @Test fun `JSONArray of boolean should fail if entries not boolean`() {
        val json = JSONArray().apply {
            addValue(123)
            addValue("ABC")
            addValue(false)
        }
        val e = assertFailsWith<JSONKotlinException> { JSONDeserializer.deserialize(BooleanArray::class, json) }
        expect("Can't deserialize 123 as Boolean at /0") { e.message }
    }

    @Test fun `JSONArray of number should return ByteArray`() {
        val json = JSONArray().apply {
            addValue(1)
            addValue(2)
            addValue(3)
        }
        val expected = byteArrayOf(1, 2, 3)
        assertTrue(expected.contentEquals(JSONDeserializer.deserialize(ByteArray::class, json) ?: fail()))
    }

    @Test fun `JSONArray of character should return CharArray`() {
        val json = JSONArray().apply {
            addValue("a")
            addValue("b")
            addValue("c")
        }
        val expected = charArrayOf('a', 'b', 'c')
        assertTrue(expected.contentEquals(JSONDeserializer.deserialize(CharArray::class, json) ?: fail()))
    }

    @Test fun `JSONArray of number should return DoubleArray`() {
        val json = JSONArray().apply {
            addValue(123)
            addValue(0)
            addValue(0.012)
        }
        val expected = doubleArrayOf(123.0, 0.0, 0.012)
        assertTrue(expected.contentEquals(JSONDeserializer.deserialize(DoubleArray::class, json) ?: fail()))
    }

    @Test fun `JSONArray of number should return FloatArray`() {
        val json = JSONArray().apply {
            addValue(123)
            addValue(0)
            addValue(0.012)
        }
        val expected = floatArrayOf(123.0F, 0.0F, 0.012F)
        assertTrue(expected.contentEquals(JSONDeserializer.deserialize(FloatArray::class, json) ?: fail()))
    }

    @Test fun `JSONArray of number should return IntArray`() {
        val json = JSONArray().apply {
            addValue(12345)
            addValue(2468)
            addValue(321321)
        }
        val expected = intArrayOf(12345, 2468, 321321)
        assertTrue(expected.contentEquals(JSONDeserializer.deserialize(IntArray::class, json) ?: fail()))
    }

    @Test fun `JSONArray of number should fail if entries not number`() {
        val json = JSONArray().apply {
            addValue("12345")
            addValue(true)
            addValue(321321)
        }
        val e = assertFailsWith<JSONKotlinException> { JSONDeserializer.deserialize(IntArray::class, json) }
        expect("Can't deserialize \"12345\" as Int at /0") { e.message }
    }

    @Test fun `JSONArray to IntArray should fail if entries not integer`() {
        val json = JSONArray().apply {
            addValue(12345)
            addValue(0.123)
            addValue(321321)
        }
        val e = assertFailsWith<JSONKotlinException> { JSONDeserializer.deserialize(IntArray::class, json) }
        expect("Can't deserialize 0.123 as Int at /1") { e.message }
    }

    @Test fun `JSONArray of number should return LongArray`() {
        val json = JSONArray().apply {
            addValue(123456789123456)
            addValue(0)
            addValue(321321L)
        }
        val expected = longArrayOf(123456789123456, 0, 321321)
        assertTrue(expected.contentEquals(JSONDeserializer.deserialize(LongArray::class, json) ?: fail()))
    }

    @Test fun `JSONArray of number should return ShortArray`() {
        val json = JSONArray().apply {
            addValue(1234)
            addValue(0)
            addValue(321)
        }
        val expected = shortArrayOf(1234, 0, 321)
        assertTrue(expected.contentEquals(JSONDeserializer.deserialize(ShortArray::class, json) ?: fail()))
    }

    private val stringType = String::class.starProjectedType
    private val stringTypeProjection = KTypeProjection.invariant(stringType)
    private val intType = Int::class.starProjectedType
    private val intTypeProjection = KTypeProjection.invariant(intType)
    private val listStringType = List::class.createType(listOf(stringTypeProjection))
    private val arrayListStringType = ArrayList::class.createType(listOf(stringTypeProjection))
    private val linkedListStringType = LinkedList::class.createType(listOf(stringTypeProjection))
    private val setStringType = Set::class.createType(listOf(stringTypeProjection))
    private val hashSetStringType = HashSet::class.createType(listOf(stringTypeProjection))
    private val linkedHashSetStringType = LinkedHashSet::class.createType(listOf(stringTypeProjection))
    private val mapStringIntType = Map::class.createType(listOf(stringTypeProjection, intTypeProjection))
    private val linkedHashMapStringIntType = LinkedHashMap::class.createType(listOf(stringTypeProjection,
            intTypeProjection))

    private val listStrings = listOf("abc", "def")
    private val jsonArrayString = JSONArray().addValue("abc").addValue("def")

    @Test fun `JSONArray of JSONString should return List of String`() {
        expect(listStrings) { JSONDeserializer.deserialize(listStringType, jsonArrayString) }
    }

    @Test fun `JSONArray of JSONString should return ArrayList of String`() {
        expect(ArrayList(listStrings)) { JSONDeserializer.deserialize(arrayListStringType, jsonArrayString) }
    }

    @Test fun `JSONArray of JSONString should return LinkedList of String`() {
        expect(LinkedList(listStrings)) { JSONDeserializer.deserialize(linkedListStringType, jsonArrayString) }
    }

    @Test fun `JSONArray of JSONString should return Set of String`() {
        expect(LinkedHashSet(listStrings)) { JSONDeserializer.deserialize(setStringType, jsonArrayString) }
    }

    @Test fun `JSONArray of JSONString should reject duplicate`() {
        val jsonArrayDuplicate = JSONArray().addValue("abc").addValue("def").addValue("abc")
        val e = assertFailsWith<JSONKotlinException> { JSONDeserializer.deserialize(setStringType, jsonArrayDuplicate) }
        expect("Duplicate not allowed at /2") { e.message }
    }

    @Test fun `JSONArray of JSONString should return HashSet of String`() {
        expect(HashSet(listStrings)) { JSONDeserializer.deserialize(hashSetStringType, jsonArrayString) }
    }

    @Test fun `JSONArray of JSONString should return LinkedHashSet of String`() {
        expect(LinkedHashSet(listStrings)) { JSONDeserializer.deserialize(linkedHashSetStringType, jsonArrayString) }
    }

    private val mapStringInt = mapOf("abc" to 123, "def" to 456, "ghi" to 789)
    private val jsonObjectInt = JSONObject().apply {
        putValue("abc", 123)
        putValue("def", 456)
        putValue("ghi", 789)
    }

    @Test fun `JSONObject should return map of String to Int`() {
        expect(mapStringInt) { JSONDeserializer.deserialize(mapStringIntType, jsonObjectInt)}
    }

    @Test fun `JSONObject should return LinkedHashMap of String to Int`() {
        val linkedHashMapStringInt = LinkedHashMap(mapStringInt)
        val result = JSONDeserializer.deserialize(linkedHashMapStringIntType, jsonObjectInt)
        assertEquals(linkedHashMapStringInt, result)
        assertTrue(result is LinkedHashMap<*, *>)
    }

    @Test fun `JSONObject should return simple data class`() {
        val json = JSONObject().apply {
            putValue("field1", "Hello")
            putValue("field2", 12345)
        }
        val expected: Dummy1? = Dummy1("Hello", 12345)
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONObject should return simple data class with default parameter`() {
        val json = JSONObject().putValue("field1", "Hello")
        val expected: Dummy1? = Dummy1("Hello")
        expect(expected) { JSONDeserializer.deserialize(json) }
    }

    @Test fun `JSONObject should return data class with extra values`() {
        val json = JSONObject().apply {
            putValue("field1", "Hello")
            putValue("field2", 12345)
            putValue("extra", "XXX")
        }
        val expected: Dummy2? = Dummy2("Hello", 12345)
        expected?.extra = "XXX"
        val result = JSONDeserializer.deserialize<Dummy2>(json)
        assertEquals(expected, result)
        assertEquals("XXX", result?.extra)
    }

    @Test fun `JSONObject should return nested data class`() {
        val json1 = JSONObject().apply {
            putValue("field1", "Whoa")
            putValue("field2", 98765)
        }
        val json2 = JSONObject().apply {
            put("dummy1", json1)
            putValue("text", "special")
        }
        val expected: Dummy3? = Dummy3(Dummy1("Whoa", 98765), "special")
        expect(expected) { JSONDeserializer.deserialize(json2) }
    }

    @Test fun `JSONObject should return nested data class with list`() {
        val json1 = JSONObject().apply {
            putValue("field1", "Whoa")
            putValue("field2", 98765)
        }
        val json2 = JSONObject().apply {
            putValue("field1", "Hi!")
            putValue("field2", 333)
        }
        val json3 = JSONArray().apply {
            add(json1)
            add(json2)
        }
        val json4 = JSONObject().apply {
            put("listDummy1", json3)
            putValue("text", "special")
        }
        val expected: Dummy4? = Dummy4(listOf(Dummy1("Whoa", 98765), Dummy1("Hi!", 333)), "special")
        expect(expected) { JSONDeserializer.deserialize(json4) }
    }

    @Test fun `JSONObject should return simple class with properties`() {
        val json = JSONObject().apply {
            putValue("field1", "qqq")
            putValue("field2", 888)
        }
        val expected = Super()
        expected.field1 = "qqq"
        expected.field2 = 888
        expect(expected) { JSONDeserializer.deserialize(Super::class, json) }
    }

    @Test fun `JSONObject should return derived class with properties`() {
        // also test parsing from String
        val str = "{\"field1\":\"qqq\",\"field2\":888,\"field3\":12345.0}"
        val expected = Derived()
        expected.field1 = "qqq"
        expected.field2 = 888
        expected.field3 = 12345.0
        expect(expected) { JSONAuto.parse(Derived::class, str) }
    }

    @Test fun `JSONObject should return simple class with properties using name annotation`() {
        val json = JSONObject().apply {
            putValue("field1", "qqq")
            putValue("fieldX", 888)
        }
        val expected = DummyWithNameAnnotation()
        expected.field1 = "qqq"
        expected.field2 = 888
        expect(expected) { JSONDeserializer.deserialize(DummyWithNameAnnotation::class, json) }
    }

    @Test fun `JSONObject should return data class using name annotation`() {
        val json = JSONObject().apply {
            putValue("field1", "qqq")
            putValue("fieldX", 888)
        }
        expect(DummyWithParamNameAnnotation("qqq", 888)) {
            JSONDeserializer.deserialize(DummyWithParamNameAnnotation::class, json)
        }
    }

    @Test fun `JSONObject should return data class using custom name annotation`() {
        val json = JSONObject().apply {
            putValue("field1", "qqq")
            putValue("fieldX", 888)
        }
        val expected = DummyWithCustomNameAnnotation("qqq", 888)
        val config = JSONConfig().apply {
            addNameAnnotation(CustomName::class, "symbol")
        }
        expect(expected) { JSONDeserializer.deserialize(DummyWithCustomNameAnnotation::class, json, config) }
    }

    private val pairStringStringType = Pair::class.createType(listOf(stringTypeProjection, stringTypeProjection))
    private val pairStringIntType = Pair::class.createType(listOf(stringTypeProjection, intTypeProjection))
    private val tripleStringStringStringType = Triple::class.createType(listOf(stringTypeProjection,
            stringTypeProjection, stringTypeProjection))
    private val tripleStringIntStringType = Triple::class.createType(listOf(stringTypeProjection,
            intTypeProjection, stringTypeProjection))

    @Test fun `JSONArray should return Pair`() {
        val json = JSONArray().apply {
            addValue("abc")
            addValue("def")
        }
        expect("abc" to "def") { JSONDeserializer.deserialize(pairStringStringType, json) }
    }

    @Test fun `JSONArray should return Heterogenous Pair`() {
        val json = JSONArray().apply {
            addValue("abc")
            addValue(88)
        }
        expect("abc" to 88) { JSONDeserializer.deserialize(pairStringIntType, json) }
    }

    @Test fun `JSONArray should return Triple`() {
        val json = JSONArray().apply {
            addValue("abc")
            addValue("def")
            addValue("xyz")
        }
        expect(Triple("abc", "def", "xyz")) { JSONDeserializer.deserialize(tripleStringStringStringType, json) }
    }

    @Test fun `JSONArray should return Heterogenous Triple`() {
        val json = JSONArray().apply {
            addValue("abc")
            addValue(66)
            addValue("xyz")
        }
        expect(Triple("abc", 66, "xyz")) { JSONDeserializer.deserialize(tripleStringIntStringType, json) }
    }

    @Test fun `null should return null for nullable String`() {
        val json: JSONValue? = null
        assertNull(JSONDeserializer.deserialize(String::class.createType(emptyList(), true), json))
    }

    @Test fun `null should fail for non-nullable String`() {
        val e = assertFailsWith<JSONKotlinException> { JSONDeserializer.deserialize(stringType, null) }
        expect("Can't deserialize null as String") { e.message }
    }

    @Test fun `JSONObject should deserialize to object`() {
        val json = JSONObject().putValue("field1", "abc")
        expect(DummyObject) { JSONDeserializer.deserialize(DummyObject::class, json) }
    }

    @Test fun `class with constant val should deserialize correctly`() {
        val json = JSONObject().putValue("field8", "blert")
        expect(DummyWithVal()) { JSONDeserializer.deserialize(DummyWithVal::class, json) }
    }

    @Test fun `java class should deserialize correctly`() {
        val json = JSONObject().apply {
            putValue("field1", 1234)
            putValue("field2", "Hello!")
        }
        expect(JavaClass1(1234, "Hello!")) { JSONDeserializer.deserialize(JavaClass1::class, json) }
    }

    @Test fun `deserialize List using Java Type should work correctly`() {
        val json = JSONArray().apply {
            add(JSONObject().apply {
                putValue("field1", 567)
                putValue("field2", "abcdef")
            })
            add(JSONObject().apply {
                putValue("field1", 9999)
                putValue("field2", "qwerty")
            })
        }
        val type: Type = JavaClass2::class.java.getField("field1").genericType
        expect(listOf(JavaClass1(567, "abcdef"), JavaClass1(9999, "qwerty"))) {
            JSONDeserializer.deserialize(type, json)
        }
    }

    @Test fun `JSONArray should deserialize into List derived type`() {
        val json = JSONArray().apply {
            addValue("2019-10-06")
            addValue("2019-10-05")
        }
        expect(DummyList(listOf(LocalDate.of(2019, 10, 6), LocalDate.of(2019, 10, 5)))) {
            JSONDeserializer.deserialize(DummyList::class, json)
        }
    }

    @Test fun `JSONObject should deserialize into Map derived type`() {
        val json = JSONObject().apply {
            putValue("aaa", "2019-10-06")
            putValue("bbb", "2019-10-05")
        }
        val expected = DummyMap(emptyMap()).apply {
            put("aaa", LocalDate.of(2019, 10, 6))
            put("bbb", LocalDate.of(2019, 10, 5))
        }
        expect(expected) { JSONDeserializer.deserialize(DummyMap::class, json) }
    }

    @Suppress("UNCHECKED_CAST")
    @Test fun `JSONArray should deserialize into Sequence`() {
        val json = JSONArray().apply {
            addValue("abcde")
            addValue("fghij")
        }
        val expected = sequenceOf("abcde", "fghij")
        val stringSequenceType = Sequence::class.createType(listOf(stringTypeProjection))
        assertTrue(sequenceEquals(expected, JSONDeserializer.deserialize(stringSequenceType, json) as Sequence<String>))
    }

    @Suppress("UNCHECKED_CAST")
    @Test fun `JSONArray should deserialize into Array`() {
        val json = JSONArray().apply {
            addValue("abcde")
            addValue("fghij")
        }
        val expected = arrayOf("abcde", "fghij")
        val stringArrayType = Array<String>::class.createType(listOf(stringTypeProjection))
        assertTrue(expected.contentEquals(JSONDeserializer.deserialize(stringArrayType, json) as Array<String>))
    }

    @Suppress("UNCHECKED_CAST")
    @Test fun `JSONArray should deserialize into nested Array`() {
        val list1 = JSONArray().apply {
            addValue("qwerty")
            addValue("asdfgh")
            addValue("zxcvbn")
        }
        val list2 = JSONArray().apply {
            addValue("abcde")
            addValue("fghij")
        }
        val json = JSONArray().apply {
            add(list1)
            add(list2)
        }
        val array1 = arrayOf("qwerty", "asdfgh", "zxcvbn")
        val array2 = arrayOf("abcde", "fghij")
        val expected = arrayOf(array1, array2)
        val stringArrayType = Array<String>::class.createType(listOf(stringTypeProjection))
        val stringArrayArrayType = Array<String>::class.createType(listOf(KTypeProjection.invariant(stringArrayType)))
        val actual = JSONDeserializer.deserialize(stringArrayArrayType, json) as Array<Array<String>>
        assertTrue(expected.contentDeepEquals(actual))
    }

    @Test fun `JSONString should deserialize to Any`() {
        val json = JSONString("Hello!")
        expect("Hello!") { JSONDeserializer.deserializeAny(json) }
    }

    @Test fun `JSONBoolean should deserialize to Any`() {
        val json1 = JSONBoolean.TRUE
        val result1 = JSONDeserializer.deserializeAny(json1)
        assertTrue(result1 is Boolean && result1)
        val json2 = JSONBoolean.FALSE
        val result2 = JSONDeserializer.deserializeAny(json2)
        assertTrue(result2 is Boolean && !result2)
    }

    @Test fun `JSONInt should deserialize to Any`() {
        val json = JSONInt(123456)
        expect(123456) { JSONDeserializer.deserializeAny(json) }
    }

    @Test fun `JSONLong should deserialize to Any`() {
        val json = JSONLong(1234567890123456L)
        expect(1234567890123456L) { JSONDeserializer.deserializeAny(json) }
    }

    @Test fun `JSONFloat should deserialize to Any`() {
        val json = JSONFloat(0.12345F)
        expect(0.12345F) { JSONDeserializer.deserializeAny(json) }
    }

    @Test fun `JSONDouble should deserialize to Any`() {
        val json = JSONDouble(0.123456789)
        expect(0.123456789) { JSONDeserializer.deserializeAny(json) }
    }

    @Test fun `JSONZero should deserialize to Any`() {
        val json = JSONZero()
        expect(0) { JSONDeserializer.deserializeAny(json) }
    }

    @Test fun `JSONArray should deserialize to Any`() {
        val json = JSONArray().apply {
            addValue("abcde")
            addValue("fghij")
        }
        expect(listOf("abcde", "fghij")) { JSONDeserializer.deserializeAny(json) }
    }

    @Test fun `JSONObject should deserialize to Any`() {
        val json = JSONObject().apply {
            putValue("aaa", 1234)
            putValue("ccc", 9999)
            putValue("bbb", 5678)
            putValue("abc", 8888)
        }
        val result = JSONDeserializer.deserializeAny(json)
        // check that the result is a map in the correct order
        if (result is Map<*, *>) {
            val iterator = result.keys.iterator()
            expect(true) { iterator.hasNext() }
            iterator.next().let {
                expect("aaa") { it }
                expect(1234) { result[it] }
            }
            expect(true) { iterator.hasNext() }
            iterator.next().let {
                expect("ccc") { it }
                expect(9999) { result[it] }
            }
            expect(true) { iterator.hasNext() }
            iterator.next().let {
                expect("bbb") { it }
                expect(5678) { result[it] }
            }
            expect(true) { iterator.hasNext() }
            iterator.next().let {
                expect("abc") { it }
                expect(8888) { result[it] }
            }
            expect(false) { iterator.hasNext() }
        }
        else
            fail("Not a Map - $result")
    }

    @Test fun `sealed class should deserialize to correct subclass`() {
        val json = JSONObject().apply {
            putValue("class", "Const")
            putValue("number", 2.0)
        }
        expect(Const(2.0)) { JSONDeserializer.deserialize<Expr>(json) }
    }

    @Test fun `sealed class should deserialize to correct object subclass`() {
        val json = JSONObject().apply {
            putValue("class", "NotANumber")
        }
        expect(NotANumber) { JSONDeserializer.deserialize<Expr>(json) }
    }

    @Test fun `sealed class should deserialize with custom discriminator`() {
        val config = JSONConfig().apply {
            sealedClassDiscriminator = "?"
        }
        val json = JSONObject().apply {
            putValue("?", "Const")
            putValue("number", 2.0)
        }
        expect(Const(2.0)) { JSONDeserializer.deserialize<Expr>(json, config) }
    }

    @Test fun `class should ignore additional fields when allowExtra set in config`() {
        val config = JSONConfig().apply {
            allowExtra = true
        }
        val json = JSONObject().apply {
            putValue("field1", "Hello")
            putValue("field2", 123)
            putValue("extra", "allow")
        }
        expect(Dummy1("Hello", 123)) { JSONDeserializer.deserialize<Dummy1>(json, config) }
    }

    @Test fun `class annotated with @JSONAllowExtra should ignore additional fields`() {
        val json = JSONObject().apply {
            putValue("field1", "Hello")
            putValue("field2", 123)
            putValue("extra", "allow")
        }
        expect(DummyWithAllowExtra("Hello", 123)) { JSONDeserializer.deserialize<DummyWithAllowExtra>(json) }
    }

    @Test fun `class annotated with custom allow extra should ignore additional fields`() {
        val config = JSONConfig().apply {
            addAllowExtraPropertiesAnnotation(CustomAllowExtraProperties::class)
        }
        val json = JSONObject().apply {
            putValue("field1", "Hi")
            putValue("field2", 123)
            putValue("extra", "allow")
        }
        expect(DummyWithCustomAllowExtra("Hi", 123)) {
            JSONDeserializer.deserialize<DummyWithCustomAllowExtra>(json, config)
        }
    }

    @Test fun `field annotated with @JSONIgnore should be ignored on deserialization`() {
        val json = JSONObject().apply {
            putValue("field1", "one")
            putValue("field2", "two")
            putValue("field3", "three")
        }
        expect(DummyWithIgnore(field1 = "one", field3 = "three")) {
            JSONDeserializer.deserialize<DummyWithIgnore>(json)
        }
    }

    @Test fun `field annotated with custom ignore annotation should be ignored on deserialization`() {
        val config = JSONConfig().apply {
            addIgnoreAnnotation(CustomIgnore::class)
        }
        val json = JSONObject().apply {
            putValue("field1", "one")
            putValue("field2", "two")
            putValue("field3", "three")
        }
        expect(DummyWithCustomIgnore(field1 = "one", field3 = "three")) {
            JSONDeserializer.deserialize<DummyWithCustomIgnore>(json, config)
        }
    }

    @Test fun `should deserialize missing members as null where allowed`() {
        val json = JSONObject().apply {
            putValue("field2", 123)
        }
        expect(Dummy5(null, 123)) { JSONDeserializer.deserialize<Dummy5>(json) }
    }

    @Test fun `should deserialize custom parameterised type`() {
        val json = JSONObject().apply {
            put("lines", JSONArray(JSONString("abc"), JSONString("def")))
        }
        val expected = TestPage<String>(lines = listOf("abc", "def"))
        expect(expected) { JSONDeserializer.deserialize<TestPage<String>>(json) }
    }

    @Test fun `should deserialize nested custom parameterised type`() {
        val json1 = JSONObject().apply {
            put("lines", JSONArray(JSONString("abc"), JSONString("def")))
        }
        val json2 = JSONArray(json1, JSONString("xyz"))
        val expected = TestPage<String>(lines = listOf("abc", "def")) to "xyz"
        expect(expected) { JSONDeserializer.deserialize<Pair<TestPage<String>, String>>(json2) }
    }

    @Test fun `should deserialize differently nested custom parameterised type`() {
        val json = JSONObject().apply {
            put("lines", JSONArray(JSONArray(JSONString("abc"), JSONString("ABC")),
                    JSONArray(JSONString("def"), JSONString("DEF"))))
        }
        val expected = TestPage<Pair<String, String>>(lines = listOf("abc" to "ABC", "def" to "DEF"))
        expect(expected) { JSONDeserializer.deserialize<TestPage<Pair<String, String>>>(json) }
    }

    @Test fun `should deserialize complex custom parameterised type`() {
        val obj1 = JSONObject().apply {
            putValue("field1", "abc")
            putValue("field2", 123)
        }
        val obj2 = JSONObject().apply {
            putValue("field1", "def")
            putValue("field2", 456)
        }
        val json = JSONObject().apply {
            put("lines", JSONArray(obj1, obj2))
        }
        val expected = TestPage<Dummy1>(lines = listOf(Dummy1("abc", 123), Dummy1("def", 456)))
        expect(expected) { JSONDeserializer.deserialize<TestPage<Dummy1>>(json) }
    }

    @Test fun `should deserialize another form of custom parameterised type`() {
        val obj1 = JSONObject().apply {
            putValue("field1", "abc")
            putValue("field2", 123)
        }
        val json = JSONObject().apply {
            putValue("description", "testing")
            put("data", obj1)
        }
        val dummy1 = Dummy1("abc", 123)
        val expected = TestDataHolder("testing", dummy1)
        expect(expected) { JSONDeserializer.deserialize<TestDataHolder<Dummy1>>(json) }
    }

    @Test fun `should deserialize yet another form of custom parameterised type`() {
        val json = JSONObject().apply {
            put("lineLists", JSONArray(JSONArray(JSONString("lineA1"), JSONString("lineA2")),
                    JSONArray(JSONString("lineB1"), JSONString("lineB2"))))
        }
        val expected = TestPage2<String>(lineLists = listOf(listOf("lineA1", "lineA2"), listOf("lineB1", "lineB2")))
        expect(expected) { JSONDeserializer.deserialize<TestPage2<String>>(json)}
    }

    @Test fun `should deserialize Java Stream`() {
        val json = JSONArray(JSONString("abc"), JSONString("def"))
        val result: Stream<String> = JSONDeserializer.deserialize(json) ?: fail("result was null")
        val iterator = result.iterator()
        expect(true) { iterator.hasNext() }
        expect("abc") { iterator.next() }
        expect(true) { iterator.hasNext() }
        expect("def") { iterator.next() }
        expect(false) { iterator.hasNext() }
    }

    @Test fun `should deserialize Java IntStream`() {
        val json = JSONArray(JSONInt(2345), JSONInt(6789))
        val result: IntStream = JSONDeserializer.deserialize(json) ?: fail("result was null")
        val iterator = result.iterator()
        expect(true) { iterator.hasNext() }
        expect(2345) { iterator.next() }
        expect(true) { iterator.hasNext() }
        expect(6789) { iterator.next() }
        expect(false) { iterator.hasNext() }
    }

    @Test fun `should deserialize Java LongStream`() {
        val json = JSONArray(JSONLong(1234567812345678), JSONLong(9876543298765432))
        val result: LongStream = JSONDeserializer.deserialize(json) ?: fail("result was null")
        val iterator = result.iterator()
        expect(true) { iterator.hasNext() }
        expect(1234567812345678) { iterator.next() }
        expect(true) { iterator.hasNext() }
        expect(9876543298765432) { iterator.next() }
        expect(false) { iterator.hasNext() }
    }

    @Test fun `should deserialize Java DoubleStream`() {
        val json = JSONArray(JSONDouble(1234.5), JSONDouble(1e40))
        val result: DoubleStream = JSONDeserializer.deserialize(json) ?: fail("result was null")
        val iterator = result.iterator()
        expect(true) { iterator.hasNext() }
        expect(1234.5) { iterator.next() }
        expect(true) { iterator.hasNext() }
        expect(1e40) { iterator.next() }
        expect(false) { iterator.hasNext() }
    }

    @Test fun `should give error message with pointer`() {
        val json = JSON.parse("""{"field1":"abc","field2":"def"}""")
        val e1 = assertFailsWith<JSONKotlinException> { JSONDeserializer.deserialize<Dummy1>(json) }
        expect("Can't deserialize \"def\" as Int at /field2") { e1.message }
        val e2 = assertFailsWith<JSONKotlinException> { JSONDeserializer.deserialize<List<Dummy1>>(JSONArray(json)) }
        expect("Can't deserialize \"def\" as Int at /0/field2") { e2.message }
    }

    @Test fun `should give expanded error message with pointer`() {
        val json = JSON.parse("""{"field2":1}""")
        val e1 = assertFailsWith<JSONKotlinException> { JSONDeserializer.deserialize<Dummy1>(json) }
        expect("Can't create Dummy1; missing: field1") { e1.message }
        val e2 = assertFailsWith<JSONKotlinException> { JSONDeserializer.deserialize<List<Dummy1>>(JSONArray(json)) }
        expect("Can't create Dummy1; missing: field1 at /0") { e2.message }
    }

    @Test fun `should give expanded error message for multiple constructors`() {
        val json = JSON.parse("""[{"aaa":"X"},{"bbb":1},{"ccc":true,"ddd":0}]""")
        val e = assertFailsWith<JSONKotlinException> { JSONDeserializer.deserialize<List<MultiConstructor>>(json) }
        expect("Can't locate constructor for MultiConstructor; properties: ccc, ddd at /2") { e.message }
    }

    @Test fun `should use type projection upperBounds`() {
        val json = JSON.parse("""{"expr":{"class":"Const","number":20.0}}""")
        val expr = JSONDeserializer.deserialize<SealedClassContainer<*>>(json)?.expr
        assertTrue(expr is Const)
        expect(20.0) { expr.number }
    }

    private fun <T> sequenceEquals(seq1: Sequence<T>, seq2: Sequence<T>) = seq1.toList() == seq2.toList()

    private val calendarFields = arrayOf(Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH, Calendar.HOUR_OF_DAY,
            Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND, Calendar.ZONE_OFFSET)

    private fun calendarEquals(a: Calendar, b: Calendar): Boolean {
        for (field in calendarFields)
            if (a.get(field) != b.get(field))
                return false
        return true
    }

    data class TestPage<T>(val header: String? = null, val lines: List<T>)

    data class TestDataHolder<T>(val description: String, val data: T)

    data class TestPage2<T>(val header: String? = null, val lineLists: List<List<T>>)

    data class SealedClassContainer<T: Expr>(val expr: T)

}
<file_sep>/*
 * @(#) JSONDeserializer.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019, 2020, 2021 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.reflect.KCallable
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KMutableProperty
import kotlin.reflect.KParameter
import kotlin.reflect.KProperty
import kotlin.reflect.KType
import kotlin.reflect.KTypeParameter
import kotlin.reflect.KTypeProjection
import kotlin.reflect.KVariance
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.createType
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.isSuperclassOf
import kotlin.reflect.full.starProjectedType
import kotlin.reflect.full.staticFunctions
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.typeOf

import java.lang.reflect.Type
import java.math.BigDecimal
import java.math.BigInteger
import java.time.Duration
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.MonthDay
import java.time.OffsetDateTime
import java.time.OffsetTime
import java.time.Period
import java.time.Year
import java.time.YearMonth
import java.time.ZonedDateTime
import java.util.BitSet
import java.util.Calendar
import java.util.Date
import java.util.LinkedList
import java.util.UUID
import java.util.stream.DoubleStream
import java.util.stream.IntStream
import java.util.stream.LongStream
import java.util.stream.Stream

import net.pwall.json.JSONDeserializerFunctions.createUUID
import net.pwall.json.JSONDeserializerFunctions.findFromJSON
import net.pwall.json.JSONDeserializerFunctions.findParameterName
import net.pwall.json.JSONDeserializerFunctions.hasSingleParameter
import net.pwall.json.JSONKotlinException.Companion.fail
import net.pwall.json.pointer.JSONPointer
import net.pwall.util.ISO8601Date

/**
 * JSON Auto deserialize for Kotlin.
 *
 * @author  <NAME>
 */
@OptIn(ExperimentalStdlibApi::class)
object JSONDeserializer {

    private val anyQType = Any::class.createType(emptyList(), true)

    /**
     * Deserialize a parsed [JSONValue] to a specified [KType].
     *
     * @param   resultType  the target type
     * @param   json        the parsed JSON, as a [JSONValue] (or `null`)
     * @param   config      an optional [JSONConfig]
     * @return              the converted object
     */
    fun deserialize(resultType: KType, json: JSONValue?, config: JSONConfig = JSONConfig.defaultConfig): Any? =
            deserialize(resultType, json, JSONPointer.root, config)

    /**
     * Deserialize a parsed [JSONValue] to a specified [KType] (specifying [JSONPointer]).
     *
     * @param   resultType  the target type
     * @param   json        the parsed JSON, as a [JSONValue] (or `null`)
     * @param   pointer     the [JSONPointer]
     * @param   config      an optional [JSONConfig]
     * @return              the converted object
     */
    fun deserialize(resultType: KType, json: JSONValue?, pointer: JSONPointer,
            config: JSONConfig = JSONConfig.defaultConfig): Any? {
        config.findFromJSONMapping(resultType)?.let {
            try {
                return it(json)
            }
            catch (e: Exception) {
                fail("Error in custom fromJSON", pointer, e)
            }
        }
        if (json == null) {
            if (!resultType.isMarkedNullable)
                fail("Can't deserialize null as ${resultType.simpleName}", pointer)
            return null
        }
        val classifier = resultType.classifier as? KClass<*> ?:
                fail("Can't deserialize ${resultType.simpleName}", pointer)
        return deserialize(resultType, classifier, resultType.arguments, json, pointer, config)
    }

    /**
     * Deserialize a parsed [JSONValue] to a specified [KClass].
     *
     * @param   resultClass the target class
     * @param   json        the parsed JSON, as a [JSONValue] (or `null`)
     * @param   config      an optional [JSONConfig]
     * @param   T           the target class
     * @return              the converted object
     */
    @Suppress("UNCHECKED_CAST")
    fun <T: Any> deserialize(resultClass: KClass<T>, json: JSONValue?,
            config: JSONConfig = JSONConfig.defaultConfig): T? {
        config.findFromJSONMapping(resultClass)?.let { return it(json) as T }
        if (json == null)
            return null
        return deserialize(resultClass.starProjectedType, resultClass, emptyList(), json, JSONPointer.root, config)
    }

    /**
     * Deserialize a parsed [JSONValue] to a specified [KClass], where the result may not be `null`.
     *
     * @param   resultClass the target class
     * @param   json        the parsed JSON, as a [JSONValue]
     * @param   config      an optional [JSONConfig]
     * @param   T           the target class
     * @return              the converted object
     */
    fun <T: Any> deserializeNonNull(resultClass: KClass<T>, json: JSONValue?,
            config: JSONConfig = JSONConfig.defaultConfig): T =
                    deserializeNonNull(resultClass, json, JSONPointer.root, config)

    /**
     * Deserialize a parsed [JSONValue] to a specified [KClass], where the result may not be `null` (specifying
     * [JSONPointer]).
     *
     * @param   resultClass the target class
     * @param   json        the parsed JSON, as a [JSONValue]
     * @param   pointer     the [JSONPointer]
     * @param   config      an optional [JSONConfig]
     * @param   T           the target class
     * @return              the converted object
     */
    @Suppress("UNCHECKED_CAST")
    fun <T: Any> deserializeNonNull(resultClass: KClass<T>, json: JSONValue?, pointer: JSONPointer,
            config: JSONConfig = JSONConfig.defaultConfig): T {
        config.findFromJSONMapping(resultClass)?.let { return it(json) as T }
        if (json == null)
            fail("Can't deserialize null as ${resultClass.simpleName}")
        return deserialize(resultClass.starProjectedType, resultClass, emptyList(), json, pointer, config)
    }

    /**
     * Deserialize a parsed [JSONValue] to a specified Java [Type].
     *
     * @param   javaType    the target type
     * @param   json        the parsed JSON, as a [JSONValue] (or `null`)
     * @param   config      an optional [JSONConfig]
     * @return              the converted object
     */
    fun deserialize(javaType: Type, json: JSONValue?, config: JSONConfig = JSONConfig.defaultConfig): Any? =
            deserialize(javaType.toKType(nullable = true), json, config)

    /**
     * Deserialize a parsed [JSONValue] to an unspecified([Any]) type.  Strings will be converted to `String`, numbers
     * to `Int`, `Long` or `BigDecimal`, booleans to `Boolean`, arrays to `ArrayList<Any?>` and objects to
     * `ListMap<String, Any?>` (a simple implementation of `Map` that preserves order).
     *
     * @param   json        the parsed JSON, as a [JSONValue] (or `null`)
     * @param   config      an optional [JSONConfig] (not used, accepted for compatibility with other functions)
     * @return              the converted object
     */
    fun deserializeAny(json: JSONValue?, @Suppress("UNUSED_PARAMETER") config: JSONConfig? = null): Any? =
            json?.toSimpleValue()

    /**
     * Deserialize a parsed [JSONValue] to the inferred [KType].
     *
     * @param   json        the parsed JSON, as a [JSONValue] (or `null`)
     * @param   config      an optional [JSONConfig]
     * @param   T           the target class
     * @return              the converted object
     */
    inline fun <reified T: Any> deserialize(json: JSONValue?, config: JSONConfig = JSONConfig.defaultConfig): T? =
            deserialize(typeOf<T>(), json, config) as T?

    /**
     * Deserialize a parsed [JSONValue] to a parameterized [KClass], with the specified [KTypeProjection]s.
     *
     * @param   resultType  the target [KType]
     * @param   resultClass the target class
     * @param   types       the [KTypeProjection]s
     * @param   json        the parsed JSON, as a [JSONValue] (or `null`)
     * @param   pointer     a [JSONPointer] to the current location
     * @param   config      a [JSONConfig]
     * @param   T           the target class
     * @return              the converted object
     */
    @Suppress("UNCHECKED_CAST")
    private fun <T: Any> deserialize(resultType: KType, resultClass: KClass<T>, types: List<KTypeProjection>,
            json: JSONValue, pointer: JSONPointer, config: JSONConfig): T {

        // check for JSONValue

        if (resultClass.isSubclassOf(JSONValue::class) && resultClass.isSuperclassOf(json::class))
            return json as T

        // does the target class companion object have a "fromJSON()" method?

        try {
            findFromJSON(resultClass, json::class)?.let {
                return it.call(resultClass.companionObjectInstance, json) as T
            }
        }
        catch (e: Exception) {
            fail("Error in custom in-class fromJSON - ${resultClass.simpleName}", pointer, e)
        }

        when (json) {

            is JSONBoolean -> {
                if (resultClass.isSuperclassOf(Boolean::class))
                    return json.booleanValue() as T
            }

            is JSONString -> return deserializeString(resultClass, json.toString(), pointer)

            is JSONNumberValue -> {

                when (resultClass) {

                    Int::class -> if (json is JSONInt || json is JSONZero)
                        return json.toInt() as T

                    Long::class -> if (json is JSONLong || json is JSONInt || json is JSONZero)
                        return json.toLong() as T

                    Double::class -> return json.toDouble() as T

                    Float::class -> return json.toFloat() as T

                    Short::class -> if (json is JSONInt || json is JSONZero)
                        return json.toShort() as T

                    Byte::class -> if (json is JSONInt || json is JSONZero)
                        return json.toByte() as T

                    BigInteger::class -> if (json is JSONLong || json is JSONInt || json is JSONZero)
                        return json.toBigInteger() as T

                    BigDecimal::class -> return json.toBigDecimal() as T

                }

                if (resultClass.isSuperclassOf(Number::class)) {
                    when (json) {
                        is JSONInt,
                        is JSONZero -> return json.toInt() as T
                        is JSONLong -> return json.toLong() as T
                        is JSONFloat -> return json.toFloat() as T
                        is JSONDouble -> return json.toDouble() as T
                        is JSONDecimal -> return json.toBigDecimal() as T
                    }
                }

                fail("Can't deserialize $json as ${resultClass.simpleName}", pointer)
            }

            is JSONSequence<*> -> return deserializeArray(resultType, resultClass, types, json, pointer, config)

            is JSONMapping<*> -> return deserializeObject(resultType, resultClass, types, json, pointer, config)

        }

        fail("Can't deserialize ${resultClass.simpleName}", pointer)
    }

    @Suppress("UNCHECKED_CAST")
    private fun <T: Any> deserializeString(resultClass: KClass<T>, str: String, pointer: JSONPointer): T {

        try {

            if (resultClass.isSuperclassOf(String::class))
                return str as T

            when (resultClass) {

                Char::class -> {
                    if (str.length != 1)
                        fail("Character must be string of length 1", pointer)
                    return str[0] as T
                }

                CharArray::class -> return str.toCharArray() as T

                Array<Char>::class -> return Array(str.length) { i -> str[i] } as T

                java.sql.Date::class -> return java.sql.Date.valueOf(str) as T

                java.sql.Time::class -> return java.sql.Time.valueOf(str) as T

                java.sql.Timestamp::class -> return java.sql.Timestamp.valueOf(str) as T

                Calendar::class -> return ISO8601Date.decode(str) as T

                Date::class -> return ISO8601Date.decode(str).time as T

                Instant::class -> return Instant.parse(str) as T

                LocalDate::class -> return LocalDate.parse(str) as T

                LocalDateTime::class -> return LocalDateTime.parse(str) as T

                LocalTime::class -> return LocalTime.parse(str) as T

                OffsetTime::class -> return OffsetTime.parse(str) as T

                OffsetDateTime::class -> return OffsetDateTime.parse(str) as T

                ZonedDateTime::class -> return ZonedDateTime.parse(str) as T

                Year::class -> return Year.parse(str) as T

                YearMonth::class -> return YearMonth.parse(str) as T

                MonthDay::class -> return MonthDay.parse(str) as T

                Duration::class -> return Duration.parse(str) as T

                Period::class -> return Period.parse(str) as T

                UUID::class -> return createUUID(str) as T

            }

            // is the target class an enum?

            if (resultClass.isSubclassOf(Enum::class))
                resultClass.staticFunctions.find { it.name == "valueOf" }?.let { return it.call(str) as T }

            // does the target class have a public constructor that takes String? (e.g. StringBuilder, URL, ... )

            resultClass.constructors.find { it.hasSingleParameter(String::class) }?.apply { return call(str) }

        }
        catch (e: JSONException) {
            throw e
        }
        catch (e: Exception) {
            fail("Error deserializing \"$str\" as ${resultClass.simpleName}", pointer, e)
        }

        fail("Can't deserialize \"$str\" as ${resultClass.simpleName}", pointer)
    }

    @Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY")
    private fun <T: Any> deserializeArray(resultType: KType, resultClass: KClass<T>, types: List<KTypeProjection>,
            json: JSONSequence<*>, pointer: JSONPointer, config: JSONConfig): T {
        return when (resultClass) {

            BooleanArray::class -> BooleanArray(json.size) {
                i -> deserializeNonNull(Boolean::class, json[i], pointer.child(i))
            }

            ByteArray::class -> ByteArray(json.size) {
                i -> deserializeNonNull(Byte::class, json[i], pointer.child(i))
            }

            CharArray::class -> CharArray(json.size) {
                i -> deserializeNonNull(Char::class, json[i], pointer.child(i))
            }

            DoubleArray::class -> DoubleArray(json.size) {
                i -> deserializeNonNull(Double::class, json[i], pointer.child(i))
            }

            FloatArray::class -> FloatArray(json.size) {
                i -> deserializeNonNull(Float::class, json[i], pointer.child(i))
            }

            IntArray::class -> IntArray(json.size) {
                i -> deserializeNonNull(Int::class, json[i], pointer.child(i))
            }

            LongArray::class -> LongArray(json.size) {
                i -> deserializeNonNull(Long::class, json[i], pointer.child(i))
            }

            ShortArray::class -> ShortArray(json.size) {
                i -> deserializeNonNull(Short::class, json[i], pointer.child(i))
            }

            Collection::class,
            MutableCollection::class,
            List::class,
            MutableList::class,
            Iterable::class,
            Any::class,
            ArrayList::class-> ArrayList<Any?>(json.size).fillFromJSON(resultType, json, getTypeParam(types), pointer,
                    config)

            LinkedList::class -> LinkedList<Any?>().fillFromJSON(resultType, json, getTypeParam(types), pointer, config)

            Stream::class -> {
                val list = ArrayList<Any?>(json.size).fillFromJSON(resultType, json, getTypeParam(types), pointer,
                        config)
                list.stream()
            }

            IntStream::class -> {
                val intArray = IntArray(json.size) {
                    i -> deserializeNonNull(Int::class, json[i], pointer.child(i), config)
                }
                IntStream.of(*intArray)
            }

            LongStream::class -> {
                val longArray = LongArray(json.size) {
                    i -> deserializeNonNull(Long::class, json[i], pointer.child(i), config)
                }
                LongStream.of(*longArray)
            }

            DoubleStream::class -> {
                val doubleArray = DoubleArray(json.size) {
                    i -> deserializeNonNull(Double::class, json[i], pointer.child(i), config)
                }
                DoubleStream.of(*doubleArray)
            }

            Set::class,
            MutableSet::class,
            LinkedHashSet::class -> LinkedHashSet<Any?>(json.size).fillFromJSON(resultType, json, getTypeParam(types),
                    pointer, config)

            HashSet::class -> HashSet<Any?>(json.size).fillFromJSON(resultType, json, getTypeParam(types), pointer,
                    config)

            Sequence::class -> json.mapIndexed { i, value ->
                deserializeNested(resultType, getTypeParam(types), value, pointer.child(i), config)
            }.asSequence()

            Pair::class -> {
                val result0 = deserializeNested(resultType, getTypeParam(types, 0), json[0], pointer.child(0), config)
                val result1 = deserializeNested(resultType, getTypeParam(types, 1), json[1], pointer.child(1), config)
                result0 to result1
            }

            Triple::class -> {
                val result0 = deserializeNested(resultType, getTypeParam(types, 0), json[0], pointer.child(0), config)
                val result1 = deserializeNested(resultType, getTypeParam(types, 1), json[1], pointer.child(1), config)
                val result2 = deserializeNested(resultType, getTypeParam(types, 2), json[2], pointer.child(2), config)
                Triple(result0, result1, result2)
            }

            BitSet::class -> {
                BitSet().apply {
                    json.mapIndexed { i, value ->
                        if (value !is JSONInt)
                            fail("Can't deserialize BitSet; array member not int", pointer.child(i))
                        set(value.value)
                    }
                }
            }

            else -> {
                if (resultClass.java.isArray) {
                    val type = getTypeParam(types)
                    val itemClass = type.classifier as? KClass<Any> ?: fail("Can't determine array type", pointer)
                    newArray(itemClass, json.size).apply {
                        for (i in json.indices)
                            this[i] = deserializeNested(resultType, type, json[i], pointer.child(i), config)
                    }
                }
                else {

                    // If the target class has a constructor that takes a single List parameter, create a List and
                    // invoke that constructor.  This should catch the less frequently used List classes.

                    resultClass.constructors.find { it.hasSingleParameter(List::class) }?.run {
                        val type = getTypeParam(parameters[0].type.arguments)
                        call(ArrayList<Any?>(json.size).fillFromJSON(resultType, json, type, pointer, config))
                    } ?: fail("Can't deserialize array as ${resultClass.simpleName}", pointer)
                }
            }

        } as T
    }

    @Suppress("UNCHECKED_CAST")
    private fun <T: Any> newArray(itemClass: KClass<T>, size: Int): Array<T?> =
            // there appears to be no way of creating an array of dynamic type in Kotlin
            // other than to use Java reflection
            java.lang.reflect.Array.newInstance(itemClass.java, size) as Array<T?>

    private fun getTypeParam(types: List<KTypeProjection>, n: Int = 0): KType = types.getOrNull(n)?.type ?: anyQType

    private fun MutableCollection<Any?>.fillFromJSON(resultType: KType, json: JSONSequence<*>, type: KType,
            pointer: JSONPointer, config: JSONConfig): MutableCollection<Any?> {
        // using for rather than map to avoid creation of intermediate List
        for (i in json.indices) {
            if (!add(deserializeNested(resultType, type, json[i], pointer.child(i), config)))
                fail("Duplicate not allowed", pointer.child(i))
        }
        return this
    }

    @Suppress("UNCHECKED_CAST")
    private fun <T: Any> deserializeObject(resultType: KType, resultClass: KClass<T>, types: List<KTypeProjection>,
            json: JSONMapping<*>, pointer: JSONPointer, config: JSONConfig): T {
        if (resultClass.isSubclassOf(Map::class)) {
            when (resultClass) {
                HashMap::class -> return deserializeMap(resultType, HashMap(json.size), types, json, pointer,
                        config) as T
                Map::class,
                MutableMap::class,
                LinkedHashMap::class -> return deserializeMap(resultType, LinkedHashMap(json.size), types, json,
                        pointer, config) as T
            }
        }

        // If the target class has a constructor that takes a single Map parameter, create a Map and invoke that
        // constructor.  This should catch the less frequently used Map classes.

        resultClass.constructors.find { it.hasSingleParameter(Map::class) }?.apply {
            return call(deserializeMap(resultType, LinkedHashMap(json.size), parameters[0].type.arguments, json,
                    pointer, config))
        }

        val jsonCopy = JSONMapping<JSONValue>(json)

        if (resultClass.isSealed) {
            val subClassName = (jsonCopy.remove(config.sealedClassDiscriminator) as? JSONString)?.toString() ?:
            fail("No class name for sealed class", pointer)
            val subClass = resultClass.sealedSubclasses.find { it.simpleName == subClassName } ?:
            fail("Can't find named subclass for sealed class", pointer)
            return deserializeObject(subClass.createType(types, nullable = resultType.isMarkedNullable), subClass,
                    types, jsonCopy, pointer, config)
        }

        resultClass.objectInstance?.let {
            return setRemainingFields(resultType, resultClass, it, json, pointer, config)
        }

        if (resultClass.isSuperclassOf(Map::class))
            return deserializeMap(resultType, LinkedHashMap(json.size), types, json, pointer, config) as T

        findBestConstructor(resultClass.constructors, json, config)?.let { constructor ->
            val argMap = HashMap<KParameter, Any?>()
            for (parameter in constructor.parameters) {
                val paramName = findParameterName(parameter, config)
                if (!config.hasIgnoreAnnotation(parameter.annotations)) {
                    if (jsonCopy.containsKey(paramName)) {
                        argMap[parameter] = deserializeNested(resultType, parameter.type, jsonCopy[paramName],
                                pointer.child(paramName), config)
                    }
                    else {
                        if (!parameter.isOptional) {
                            if (parameter.type.isMarkedNullable)
                                argMap[parameter] = null
                            else
                                fail("Can't create $resultClass - missing property $paramName", pointer)
                        }
                    }
                }
                jsonCopy.remove(paramName)
            }
            return setRemainingFields(resultType, resultClass, constructor.callBy(argMap), jsonCopy, pointer, config)
        }
        // there is no matching constructor
        if (resultClass.constructors.size == 1) {
            val missing = resultClass.constructors.first().parameters.filter {
                !config.hasIgnoreAnnotation(it.annotations) && !it.isOptional && !it.type.isMarkedNullable
            }.map {
                findParameterName(it, config)
            }.filter {
                !jsonCopy.containsKey(it)
            }
            fail("Can't create ${resultClass.simpleName}; missing: ${missing.displayList()}", pointer)
        }
        val propMessage = when {
            jsonCopy.isNotEmpty() -> jsonCopy.keys.displayList()
            else -> "none"
        }
        fail("Can't locate constructor for ${resultClass.simpleName}; properties: $propMessage", pointer)
    }

    private fun Collection<Any?>.displayList(): String = joinToString(", ")

    private fun deserializeMap(resultType: KType, map: MutableMap<Any, Any?>, types: List<KTypeProjection>,
            json: JSONMapping<*>, pointer: JSONPointer, config: JSONConfig): MutableMap<Any, Any?> {
        val keyClass = getTypeParam(types, 0).classifier as? KClass<*> ?:
                fail("Key type can not be determined for Map", pointer)
        val valueType = getTypeParam(types, 1)
        for (entry in json.entries) {
            map[deserializeString(keyClass, entry.key, pointer)] = deserializeNested(resultType, valueType, entry.value,
                    pointer.child(entry.key), config)
        }
        return map
    }

    private fun <T: Any> setRemainingFields(resultType: KType, resultClass: KClass<T>, instance: T,
            json: Map<String, JSONValue?>, pointer: JSONPointer, config: JSONConfig): T {
        for (entry in json) { // JSONObject fields not used in constructor
            val member = findField(resultClass.members, entry.key, config)
            if (member != null) {
                if (!config.hasIgnoreAnnotation(member.annotations)) {
                    val value = deserializeNested(resultType, member.returnType, entry.value, pointer.child(entry.key),
                            config)
                    if (member is KMutableProperty<*>) {
                        val wasAccessible = member.isAccessible
                        member.isAccessible = true
                        try {
                            member.setter.call(instance, value)
                        }
                        catch (e: Exception) {
                            fail("Error setting property ${entry.key} in ${resultClass.simpleName}", pointer, e)
                        }
                        finally {
                            member.isAccessible = wasAccessible
                        }
                    }
                    else {
                        if (member.getter.call(instance) != value)
                            fail("Can't set property ${entry.key} in ${resultClass.simpleName}", pointer)
                    }
                }
            }
            else {
                if (!(config.allowExtra || config.hasAllowExtraPropertiesAnnotation(resultClass.annotations)))
                    fail("Can't find property ${entry.key} in ${resultClass.simpleName}", pointer)
            }
        }
        return instance
    }

    private fun findField(members: Collection<KCallable<*>>, name: String, config: JSONConfig): KProperty<*>? {
        for (member in members)
            if (member is KProperty<*> && (config.findNameFromAnnotation(member.annotations) ?: member.name) == name)
                    return member
        return null
    }

    private fun <T: Any> findBestConstructor(constructors: Collection<KFunction<T>>, json: JSONMapping<*>,
            config: JSONConfig): KFunction<T>? {
        var result: KFunction<T>? = null
        var best = -1
        for (constructor in constructors) {
            val parameters = constructor.parameters
            if (parameters.any { findParameterName(it, config) == null || it.kind != KParameter.Kind.VALUE })
                continue
            val n = findMatchingParameters(parameters, json, config)
            if (n > best) {
                result = constructor
                best = n
            }
        }
        return result
    }

    private fun findMatchingParameters(parameters: List<KParameter>, json: JSONMapping<*>, config: JSONConfig): Int {
        var n = 0
        for (parameter in parameters) {
            if (json.containsKey(findParameterName(parameter, config)))
                n++
            else {
                if (!(parameter.isOptional || parameter.type.isMarkedNullable))
                    return -1
            }
        }
        return n
    }

    private fun deserializeNested(enclosingType: KType, resultType: KType, json: JSONValue?, pointer: JSONPointer,
            config: JSONConfig): Any? =
                    deserialize(resultType.applyTypeParameters(enclosingType, pointer), json, pointer, config)

    private fun KType.applyTypeParameters(enclosingType: KType, pointer: JSONPointer): KType {
        (classifier as? KTypeParameter)?.let { typeParameter ->
            val enclosingClass = enclosingType.classifierAsClass(this, pointer)
            val index = enclosingClass.typeParameters.indexOfFirst { it.name == typeParameter.name }
            return enclosingType.arguments.getOrNull(index)?.type ?:
                    enclosingClass.typeParameters.getOrNull(index)?.upperBounds?.singleOrNull() ?:
                    fail("Can't create $simpleName - no type information for ${typeParameter.name}", pointer)
        }

        if (arguments.isEmpty())
            return this

        return classifierAsClass(this, pointer).createType(arguments.map { (variance, type) ->
            if (variance == null || type == null)
                KTypeProjection.STAR
            else
                type.applyTypeParameters(enclosingType, pointer).let {
                    when (variance) {
                        KVariance.INVARIANT -> KTypeProjection.invariant(it)
                        KVariance.IN -> KTypeProjection.contravariant(it)
                        KVariance.OUT -> KTypeProjection.covariant(it)
                    }
                }
        }, isMarkedNullable, annotations)
    }

    private fun KType.classifierAsClass(target: KType, pointer: JSONPointer): KClass<*> =
            classifier as? KClass<*> ?: fail("Can't create $target - insufficient type information", pointer)

    private val KType.simpleName
        get() = (classifier as? KClass<*>)?.simpleName ?: toString()

}
<file_sep>/*
 * @(#) JSONSerializerFunctions.kt
 *
 * json-kotlin Kotlin JSON Auto Serialize/deserialize
 * Copyright (c) 2019, 2020, 2022 <NAME>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json

import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.full.isSubclassOf

import java.math.BigDecimal
import java.math.BigInteger
import java.net.URI
import java.net.URL
import java.time.Duration
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.MonthDay
import java.time.OffsetDateTime
import java.time.OffsetTime
import java.time.Period
import java.time.Year
import java.time.YearMonth
import java.time.ZonedDateTime
import java.util.Calendar
import java.util.LinkedList
import java.util.UUID

import net.pwall.util.ISO8601Date

/**
 * Utility functions for JSON Serialization.  These functions are not expected to be of use outside the `json-kotlin`
 * family of projects.
 *
 * @author  <NAME>
 */
object JSONSerializerFunctions {

    private val toJsonCache = HashMap<KClass<*>, KFunction<JSONValue>?>()

    private val toStringClasses = setOf(java.sql.Date::class, java.sql.Time::class, java.sql.Timestamp::class,
            Instant::class, LocalDate::class, LocalDateTime::class, LocalTime::class, OffsetTime::class,
            OffsetDateTime::class, ZonedDateTime::class, Year::class, YearMonth::class, MonthDay::class,
            Duration::class, Period::class, URI::class, URL::class, UUID::class)

    private val uncachedClasses = setOf(Any::class, String::class, Boolean::class, Int::class, Long::class, Byte::class,
            Short::class, Double::class, Float::class, BigDecimal::class, BigInteger::class, ArrayList::class,
            LinkedList::class, HashMap::class, LinkedHashMap::class, HashSet::class)

    /**
     * Is the class best represented by a string of the `toString()` result?
     *
     * @receiver            the class of the object
     * @return              `true` if the object should be output as a string
     */
    fun KClass<*>.isToStringClass() = this in toStringClasses

    fun KClass<*>.findToJSON(): KFunction<JSONValue>? {
        if (this in uncachedClasses || this in toStringClasses)
            return null
        if (toJsonCache.containsKey(this))
            return toJsonCache[this]
        try {
            for (function in members) {
                if (function is KFunction<*> &&
                        function.name == "toJSON" &&
                        function.parameters.size == 1 &&
                        function.parameters[0].kind == KParameter.Kind.INSTANCE &&
                        function.returnType.isJSONValue())
                    @Suppress("UNCHECKED_CAST")
                    return (function as KFunction<JSONValue>).apply { toJsonCache[this@findToJSON] = this }
            }
        }
        catch (_: Throwable) {
        }
        toJsonCache[this] = null
        return null
    }

    private fun KType.isJSONValue(): Boolean {
        classifier?.let { if (it is KClass<*>) return it.isSubclassOf(JSONValue::class) }
        return false
    }

    fun KClass<*>.isSealedSubclass(): Boolean {
        for (supertype in supertypes) {
            (supertype.classifier as? KClass<*>)?.let {
                if (it.isSealed)
                    return true
                if (it != Any::class)
                    if (it.isSealedSubclass())
                        return true
            }
        }
        return false
    }

    fun Calendar.formatISO8601() : String = ISO8601Date.toString(this, true,
            ISO8601Date.YEAR_MASK or ISO8601Date.MONTH_MASK or ISO8601Date.DAY_OF_MONTH_MASK or
                    ISO8601Date.HOUR_OF_DAY_MASK or ISO8601Date.MINUTE_MASK or ISO8601Date.SECOND_MASK or
                    ISO8601Date.MILLISECOND_MASK or ISO8601Date.ZONE_OFFSET_MASK)

}
<file_sep><?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>net.pwall.json</groupId>
  <artifactId>json-kotlin</artifactId>
  <version>4.8</version>
  <name>JSON serialization and deserialization for Kotlin</name>
  <description>Kotlin classes to perform auto-serialization and deserialization.</description>
  <packaging>jar</packaging>
  <url>https://github.com/pwall567/json-kotlin</url>

  <parent>
    <groupId>net.pwall.maven</groupId>
    <artifactId>maven-kotlin</artifactId>
    <version>5.1</version>
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
  </properties>

  <licenses>
    <license>
      <name>The MIT License (MIT)</name>
      <url>http://opensource.org/licenses/MIT</url>
      <distribution>repo</distribution>
    </license>
  </licenses>

  <scm>
    <connection>scm:git:git://github.com/pwall567/json-kotlin.git</connection>
    <url>https://github.com/pwall567/json-kotlin</url>
  </scm>

  <developers>
    <developer>
      <id><EMAIL></id>
      <name><NAME></name>
      <email><EMAIL></email>
      <url>https://pwall.net</url>
      <roles>
        <role>architect</role>
        <role>developer</role>
      </roles>
      <timezone>Australia/Sydney</timezone>
    </developer>
  </developers>

  <pluginRepositories>
    <pluginRepository>
      <id>central</id>
      <name>Maven Central</name>
      <url>https://repo1.maven.org/maven2/</url>
    </pluginRepository>
  </pluginRepositories>

  <dependencies>
    <dependency>
      <groupId>net.pwall.json</groupId>
      <artifactId>json-kotlin-annotations</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>net.pwall.json</groupId>
      <artifactId>json-pointer</artifactId>
      <version>2.4</version>
    </dependency>
    <dependency>
      <groupId>net.pwall.json</groupId>
      <artifactId>json-validation</artifactId>
      <version>1.5</version>
    </dependency>
    <dependency>
      <groupId>net.pwall.json</groupId>
      <artifactId>jsonutil</artifactId>
      <version>5.1</version>
    </dependency>
    <dependency>
      <groupId>net.pwall.util</groupId>
      <artifactId>javautil</artifactId>
      <version>2.4</version>
    </dependency>
    <dependency>
      <groupId>org.jetbrains.kotlin</groupId>
      <artifactId>kotlin-stdlib-jdk8</artifactId>
    </dependency>
    <dependency>
      <groupId>org.jetbrains.kotlin</groupId>
      <artifactId>kotlin-reflect</artifactId>
    </dependency>
    <dependency>
      <groupId>org.jetbrains.kotlin</groupId>
      <artifactId>kotlin-test-junit</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>net.pwall.json</groupId>
      <artifactId>json-kotlin-test-classes</artifactId>
      <version>1.8</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>net.pwall.json</groupId>
      <artifactId>json-kotlin-test</artifactId>
      <version>1.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <sourceDirectory>src/main/kotlin</sourceDirectory>
    <testSourceDirectory>src/test/kotlin</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>compile-kotlin</id>
            <phase>process-sources</phase>
            <goals>
              <goal>compile</goal>
            </goals>
          </execution>
          <execution>
            <id>test-compile-kotlin</id>
            <phase>test-compile</phase>
            <goals>
              <goal>test-compile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <executions>
          <execution>
            <id>compile-java</id>
            <phase>compile</phase>
          </execution>
          <execution>
            <id>test-compile-java</id>
            <phase>test-compile</phase>
          </execution>
        </executions>
        <configuration>
          <source>${java.version}</source>
          <target>${java.version}</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.jetbrains.dokka</groupId>
        <artifactId>dokka-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-source-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>
