A JSON parser for Kotlin

Related tags

JSON kotlin json
Overview

Klaxon logo

Klaxon is a library to parse JSON in Kotlin

Install

dependencies {
    implementation 'com.beust:klaxon:5.5'
}

Community

Join the #klaxon Slack channel.

Use

Klaxon has different API's depending on your needs:

These four API's cover various scenarios and you can decide which one to use based on whether you want to stream your document and whether you need to query it.

Streaming Query Manipulation
Object binding API No No Kotlin objects
Streaming API Yes No Kotlin objects and JsonObject/JsonArray
Low level API No Yes Kotlin objects
JSON Path query API Yes Yes JsonObject/JsonArray

Object binding API

General usage

To use Klaxon's high level API, you define your objects inside a class. Klaxon supports all the classes you can define in Kotlin:

  • Regular and data classes.
  • Mutable and immutable classes.
  • Classes with default parameters.

For example:

class Person(val name: String, val age: Int)

Classes with default parameters are supported as well:

class Person (val name: String, var age: Int = 23)

Once you've specified your value class, you invoke the parse() function, parameterized with that class:

val result = Klaxon()
    .parse<Person>("""
    {
      "name": "John Smith",
    }
    """)

assert(result?.name == "John Smith")
assert(result.age == 23)

The @Json annotation

The @Json annotation allows you to customize how the mapping between your JSON documents and your Kotlin objects is performed. It supports the following attributes:

name

Use the name attribute when your Kotlin property has a different name than the field found in your JSON document:

data class Person(
    @Json(name = "the_name")
    val name: String
)
val result = Klaxon()
    .parse<Person>("""
    {
      "the_name": "John Smith", // note the field name
      "age": 23
    }
""")

assert(result.name == "John Smith")
assert(result.age == 23)

ignored

You can set this boolean attribute to true if you want certain properties of your value class not to be mapped during the JSON parsing process. This is useful if you defined additional properties in your value classes.

class Ignored(val name: String) {
   @Json(ignored = true)
   val actualName: String get() = ...
}

In this example, Klaxon will not try to find a field called actualName in your JSON document.

Note that you can achieve the same result by declaring these properties private:

class Ignored(val name: String) {
   private val actualName: String get() = ...
}

Additionally, if you want to declare a property private but still want that property to be visible to Klaxon, you can annotate it with @Json(ignored = false).

index

The index attribute allows you to specify where in the JSON string the key should appear. This allows you to specify that certain keys should appear before others:

class Data(
    @Json(index = 1) val id: String,
    @Json(index = 2) val name: String
)
println(Klaxon().toJsonString(Data("id", "foo")))

// displays { "id": "id", "name": "foo" }

whereas

class Data(
    @Json(index = 2) val id: String,
    @Json(index = 1) val name: String
)
println(Klaxon().toJsonString(Data("id", "foo")))

// displays { "name": "foo" , "id": "id" }

Properties that are not assigned an index will be displayed in a non deterministic order in the output JSON.

serializeNull

By default, all properties with the value null are serialized to JSON, for example:

class Data(
    val id: Int?
)
println(Klaxon().toJsonString(Data(null)))

// displays { "id": null }

If you instead want the properties with a null value to be absent in the JSON string, use @Json(serializeNull = false):

class Data(
    @Json(serializeNull = false)
    val id: Int?
)
println(Klaxon().toJsonString(Data(null)))

// displays {}

If serializeNull is false, the Kotlin default values for this property will be ignored during parsing. Instead, if the property is absent in the JSON, the value will default to null.

Renaming fields

On top of using the @Json(name=...) annotation to rename fields, you can implement a field renamer yourself that will be applied to all the fields that Klaxon encounters, both to and from JSON. You achieve this result by passing an implementation of the FieldRenamer interface to your Klaxon object:

    val renamer = object: FieldRenamer {
        override fun toJson(fieldName: String) = FieldRenamer.camelToUnderscores(fieldName)
        override fun fromJson(fieldName: String) = FieldRenamer.underscoreToCamel(fieldName)
    }

    val klaxon = Klaxon().fieldRenamer(renamer)

Custom types

Klaxon will do its best to initialize the objects with what it found in the JSON document but you can take control of this mapping yourself by defining type converters.

The converter interface is as follows:

interface Converter {
    fun canConvert(cls: Class<*>) : Boolean
    fun toJson(value: Any): String
    fun fromJson(jv: JsonValue) : Any
}

You define a class that implements this interface and implement the logic that converts your class to and from JSON. For example, suppose you receive a JSON document with a field that can either be a 0 or a 1 and you want to convert that field into your own type that's initialized with a boolean:

class BooleanHolder(val flag: Boolean)

val myConverter = object: Converter {
    override fun canConvert(cls: Class<*>)
        = cls == BooleanHolder::class.java

    override fun toJson(value: Any): String
        = """{"flag" : "${if ((value as BooleanHolder).flag == true) 1 else 0}"""

    override fun fromJson(jv: JsonValue)
        = BooleanHolder(jv.objInt("flag") != 0)

}

Next, you declare your converter to your Klaxon object before parsing:

val result = Klaxon()
    .converter(myConverter)
    .parse<BooleanHolder>("""
        { "flag" : 1 }
    """)

assert(result.flag)

JsonValue

The Converter type passes you an instance of the JsonValue class. This class is a container of a Json value. It is guaranteed to contain one and exactly one of either a number, a string, a character, a JsonObject or a JsonArray. If one of these fields is set, the others are guaranteed to be null. Inspect that value in your converter to make sure that the value you are expecting is present, otherwise, you can cast a KlaxonException to report the invalid JSON that you just found.

Field conversion overriding

It's sometimes useful to be able to specify a type conversion for a specific field without that conversion applying to all types of your document (for example, your JSON document might contain various dates of different formats). You can use field conversion types for this kind of situation.

Such fields are specified by your own annotation, which you need to specify as targetting a FIELD:

@Target(AnnotationTarget.FIELD)
annotation class KlaxonDate

Next, annotate the field that requires this specific handling in the constructor of your class. Do note that such a constructor needs to be annotated with @JvmOverloads:

class WithDate @JvmOverloads constructor(
    @KlaxonDate
    val date: LocalDateTime
)

Define your type converter (which has the same type as the converters defined previously). In this case, we are converting a String from JSON into a LocalDateTime:

val dateConverter = object: Converter {
    override fun canConvert(cls: Class<*>)
        = cls == LocalDateTime::class.java

    override fun fromJson(jv: JsonValue) =
        if (jv.string != null) {
            LocalDateTime.parse(jv.string, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
        } else {
            throw KlaxonException("Couldn't parse date: ${jv.string}")
        }

    override fun toJson(o: Any)
            = """ { "date" : $o } """
}

Finally, declare the association between that converter and your annotation in your Klaxon object before parsing:

val result = Klaxon()
    .fieldConverter(KlaxonDate::class, dateConverter)
    .parse<WithDate>("""
    {
      "theDate": "2017-05-10 16:30"
    }
""")

assert(result?.date == LocalDateTime.of(2017, 5, 10, 16, 30))

Property strategy

You can instruct Klaxon to dynamically ignore properties with the PropertyStrategy interface:

interface PropertyStrategy {
    /**
     * @return true if this property should be mapped.
     */
    fun accept(property: KProperty<*>): Boolean
}

This is a dynamic version of @Json(ignored = true), which you can register with your Klaxon instance with the function propertyStrategy():

val ps = object: PropertyStrategy {
    override fun accept(property: KProperty<*>) = property.name != "something"
}
val klaxon = Klaxon().propertyStrategy(ps)

You can define multiple PropertyStrategy instances, and in such a case, they all need to return true for a property to be included.

Polymorphism

JSON documents sometimes contain dynamic payloads whose type can vary. Klaxon supports two different use cases for polymorphism: polymorphic classes and polymorphic fields. Klaxon gives you control on polymorphism with the annotation @TypeFor, which can be placed either on a class or on a field.

Polymorphic classes

A polymorphic class is a class whose actual type is defined by one of its own properties. Consider this JSON:

[
    { "type": "rectangle", "width": 100, "height": 50 },
    { "type": "circle", "radius": 20}
]

The content of the field type determines the class that needs to be instantiated. You would model this as follows with Klaxon:

@TypeFor(field = "type", adapter = ShapeTypeAdapter::class)
open class Shape(val type: String)
data class Rectangle(val width: Int, val height: Int): Shape("rectangle")
data class Circle(val radius: Int): Shape("circle")

The type adapter is as follows:

class ShapeTypeAdapter: TypeAdapter<Shape> {
    override fun classFor(type: Any): KClass<out Shape> = when(type as String) {
        "rectangle" -> Rectangle::class
        "circle" -> Circle::class
        else -> throw IllegalArgumentException("Unknown type: $type")
    }
}

Polymorphic fields

Klaxon also allows a field to determine the class to be instantiated for another field. Consider the following JSON document:

[
    { "type": 1, "shape": { "width": 100, "height": 50 } },
    { "type": 2, "shape": { "radius": 20} }
]

This is an array of polymorphic objects. The type field is a discriminant which determines the type of the field shape: if its value is 1, the shape is a rectangle, if 2, it's a circle.

To parse this document with Klaxon, we first model these classes with a hierarchy:

open class Shape
data class Rectangle(val width: Int, val height: Int): Shape()
data class Circle(val radius: Int): Shape()

We then define the class that the objects of this array are instances of:

class Data (
    @TypeFor(field = "shape", adapter = ShapeTypeAdapter::class)
    val type: Integer,

    val shape: Shape
)

Notice the @TypeFor annotation, which tells Klaxon which field this value is a discriminant for, and also provides a class that will translate these integer values into the correct class:

class ShapeTypeAdapter: TypeAdapter<Shape> {
    override fun classFor(type: Any): KClass<out Shape> = when(type as Int) {
        1 -> Rectangle::class
        2 -> Circle::class
        else -> throw IllegalArgumentException("Unknown type: $type")
    }
}

With this code in place, you can now parse the provided JSON document above the regular way and the the following tests will pass:

val shapes = Klaxon().parseArray<Data>(json)
assertThat(shapes!![0].shape as Rectangle).isEqualTo(Rectangle(100, 50))
assertThat(shapes[1].shape as Circle).isEqualTo(Circle(20))

Streaming API

The streaming API is useful in a few scenarios:

  • When your JSON document is very large and reading it all in memory might cause issues.
  • When you want your code to react as soon as JSON values are being read, without waiting for the entire document to be parsed.

This second point is especially important to make mobile apps as responsive as possible and make them less reliant on network speed.

Note: the streaming API requires that each value in the document be handled by the reader. If you are simply looking to extract a single value the PathMatcher API may be a better fit.

Writing JSON with the streaming API

As opposed to conventional JSON libraries, Klaxon doesn't supply a JsonWriter class to create JSON documents since this need is already covered by the json() function, documented in the Advanced DSL section.

Reading JSON with the streaming API

Streaming JSON is performed with the JsonReader class. Here is an example:

val objectString = """{
     "name" : "Joe",
     "age" : 23,
     "flag" : true,
     "array" : [1, 3],
     "obj1" : { "a" : 1, "b" : 2 }
}"""

JsonReader(StringReader(objectString)).use { reader ->
    reader.beginObject() {
        var name: String? = null
        var age: Int? = null
        var flag: Boolean? = null
        var array: List<Any> = arrayListOf<Any>()
        var obj1: JsonObject? = null
        while (reader.hasNext()) {
            val readName = reader.nextName()
            when (readName) {
                "name" -> name = reader.nextString()
                "age" -> age = reader.nextInt()
                "flag" -> flag = reader.nextBoolean()
                "array" -> array = reader.nextArray()
                "obj1" -> obj1 = reader.nextObject()
                else -> Assert.fail("Unexpected name: $readName")
            }
        }
    }
}

There are two special functions to be aware of: beginObject() and beginArray(). Use these functions when you are about to read an object or an array from your JSON stream. These functions will make sure that the stream is correctly positioned (open brace or open bracket) and once you are done consuming the content of that entity, the functions will make sure that your object is correctly closed (closing brace or closing bracket). Note that these functions accept a closure as an argument, so there are no closeObject()/closeArray() functions.

It is possible to mix both the object binding and streaming API's, so you can benefit from the best of both worlds.

For example, suppose your JSON document contains an array with thousands of elements in them, each of these elements being an object in your code base. You can use the streaming API to consume the array one element at a time and then use the object binding API to easily map these elements directly to one of your objects:

data class Person(val name: String, val age: Int)
val array = """[
        { "name": "Joe", "age": 23 },
        { "name": "Jill", "age": 35 }
    ]"""

fun streamingArray() {
    val klaxon = Klaxon()
    JsonReader(StringReader(array)).use { reader ->
        val result = arrayListOf<Person>()
        reader.beginArray {
            while (reader.hasNext()) {
                val person = klaxon.parse<Person1>(reader)
                result.add(person)
            }
        }
    }
}

JSON Path Query API

The JSON Path specification defines how to locate elements inside a JSON document. Klaxon allows you to define path matchers that can match specific elements in your document and receive a callback each time a matching element is found.

Consider the following document:

{
   "library": {
       "books": [
           {
               "author": "Herman Melville",
               "title": "Moby Dick"
           },
           {
               "author": "Jules Vernes",
               "title": "L'île mystérieuse"
           }
       ]
   }
}

According to the JSON Path spec, the two authors have the following JSON paths:

$.library.books[0].author
$.library.books[1].author

We'll define a PathMatcher that uses a regular expression to filter only the elements we want:

val pathMatcher = object : PathMatcher {
    override fun pathMatches(path: String) = Pattern.matches(".*library.*books.*author.*", path)

    override fun onMatch(path: String, value: Any) {
        println("Adding $path = $value")
    }
}

Klaxon()
    .pathMatcher(pathMatcher)
    .parseJsonObject(document)

Output:

Adding $.library.books[0].author = Herman Melville
Adding $.library.books[1].author = Jules Vernes

Two notes:

  • Klaxon doesn't support the JSON Path expression language, only the element location specification.
  • This API is streaming: your path observers will be notified as soon as a matching element has been detected and its value completely parsed.

Low level API

Values parsed from a valid JSON file can be of the following type:

  • Int
  • Long
  • BigInteger
  • String
  • Double
  • Boolean
  • JsonObject
  • JsonArray

JsonObject behaves like a Map while JsonArray behaves like a List. Once you have parsed a file, you should cast it to the type that you expect. For example, consider this simple file called object.json:

{
    "firstName" : "Cedric",
    "lastName" : "Beust"
}

Since this is a JSON object, we parse it as follows:

fun parse(name: String) : Any? {
    val cls = Parser::class.java
    return cls.getResourceAsStream(name)?.let { inputStream ->
        return Parser.default().parse(inputStream)
    }
}

// ...

val obj = parse("/object.json") as JsonObject

Parse from String value :

val parser: Parser = Parser.default()
val stringBuilder: StringBuilder = StringBuilder("{\"name\":\"Cedric Beust\", \"age\":23}")
val json: JsonObject = parser.parse(stringBuilder) as JsonObject
println("Name : ${json.string("name")}, Age : ${json.int("age")}")

Result :

Name : Cedric Beust, Age : 23

You can also access the JSON content as a file, or any other resource you can get an InputStream from.

Let's query these values:

val firstName = obj.string("firstName")
val lastName = obj.string("lastName")
println("Name: $firstName $lastName")

// Prints: Name: Cedric Beust

JsonObject implements the following methods:

fun int(fieldName: String) : Int?
fun long(fieldName: String) : Long?
fun bigInt(fieldName: String) : BigInteger?
fun string(fieldName: String) : String?
fun double(fieldName: String) : Double?
fun boolean(fieldName: String) : Boolean?
fun obj(fieldName: String) : JsonObject?
fun <T> array(thisType: T, fieldName: String) : JsonArray<T>?

JsonArray implements the same methods, except that they return JsonArrays of the same type. This allows you to easily fetch collections of fields or even sub-objects. For example, consider the following:

[
    {
        "name" : "John",
        "age" : 20
    },
    {
        "name" : "Amy",
        "age" : 25
    },
    {
        "name" : "Jessica",
        "age" : 38
    }
]

We can easily collect all the ages as follows:

val array = parse("/e.json") as JsonArray<JsonObject>

val ages = array.long("age")
println("Ages: $ages")

// Prints: Ages: JsonArray(value=[20, 25, 38])

Since a JsonArray behaves like a List, we can apply closures on them, such as filter:

val oldPeople = array.filter {
    it.long("age")!! > 30
}
println("Old people: $oldPeople")

// Prints: Old people: [JsonObject(map={age=38, name=Jessica})]

Let's look at a more complex example:

[
    {
        "first": "Dale",
        "last": "Cooper",
        "schoolResults" : {
            "scores": [
                { "name": "math", "grade" : 90 },
                { "name": "physics", "grade" : 50 },
                { "name": "history", "grade" : 85 }
            ],
            "location" : "Berkeley"
        }
    },
    {
        "first": "Kara",
        "last": "Thrace",
        "schoolResults" : {
            "scores": [
                { "name": "math", "grade" : 75 },
                { "name": "physics", "grade" : 90 },
                { "name": "history", "grade" : 55 }
            ],
            "location" : "Stanford"
        }
    },
    {
        "first": "Jack",
        "last": "Harkness",
        "schoolResults" : {
            "scores": [
                { "name": "math", "grade" : 40 },
                { "name": "physics", "grade" : 82 },
                { "name": "history", "grade" : 60 }
            ],
            "location" : "Berkeley"
        }
    }
]

Let's chain a few operations, for example, finding the last names of all the people who studied in Berkeley:

println("=== Everyone who studied in Berkeley:")
val berkeley = array.filter {
    it.obj("schoolResults")?.string("location") == "Berkeley"
}.map {
    it.string("last")
}
println("$berkeley")

// Prints:
// === Everyone who studied in Berkeley:
// [Cooper, Harkness]

All the grades over 75:

println("=== All grades bigger than 75")
val result = array.flatMap {
    it.obj("schoolResults")
            ?.array<JsonObject>("scores")?.filter {
                it.long("grade")!! > 75
            }!!
}
println("Result: $result")

// Prints:
// === All grades bigger than 75
// Result: [JsonObject(map={name=math, grade=90}), JsonObject(map={name=history, grade=85}), JsonObject(map={name=physics, grade=90}), JsonObject(map={name=physics, grade=82})]

Note the use of flatMap which transforms an initial result of a list of lists into a single list. If you use map, you will get a list of three lists:

// Using map instead of flatMap
// Prints:
// Result: [[JsonObject(map={name=math, grade=90}), JsonObject(map={name=history, grade=85})], [JsonObject(map={name=physics, grade=90})], [JsonObject(    map={name=physics, grade=82})]]

Pretty printing

You can convert any JsonObject to a valid JSON string by calling toJsonString() on it. If you want to get a pretty-printed version of that string, call toJsonString(true)

DSL

You can easily create JSON objects with Klaxon's DSL. There are two different variants of that DSL: declarative and imperative.

The declarative DSL uses maps and pairs (with the to operator) to declare the associations between your keys and your values:

val obj = json {
    "color" to "red",
    "age" to 23
}

The declarative syntax limits you to only having values in your object, so if you need to use arbitrary pieces of code inside your DSL object, you can use the imperative syntax instead. This syntax doesn't use pairs but lambdas, and you use the function put() to define your fields:

val obj = json {
   repeat(3) {
      put("field$it", it * 2)
   }
}

Output:

{
  "fields": {
    "field0": 0,
    "field1": 2,
    "field2": 4
  }
}

Flattening and path lookup

If we have the following JSON

{
	"users" : [
	    {
	        "email" : "[email protected]"
	    },
	    {
	    	"email" : "[email protected]"
	    }
	]
}

We can find all emails by

(parse("my.json") as JsonObject).lookup<String?>("users.email")

Parsers

There are two parser implementations with official support. The first is written in Kotlin and is accessed by calling Parser.default().

The second is a parser implemented using the FasterXML Jackson mapper. This parser has been found to take 1/2 the time of the default Parser on large JSON payloads.

The Jackson mapper can be found at the coordinates com.beust:klaxon-jackson:[version]. To use this parser, call the extension Parser.jackson().

Implementation

The Kotlin based Parser is implemented as a mutable state machine supported by a simplistic State monad, making the main loop very simple:

val stateMachine = StateMachine()
val lexer = Lexer(inputStream)
var world = World(Status.INIT)
do {
    val token = lexer.nextToken()
    world = stateMachine.next(world, token)
} while (token.tokenType != Type.EOF)

Troubleshooting

Here are a few common errors and how to resolve them.

  • NoSuchMethodException: <init>

You might see the following exception:

Caused by: java.lang.NoSuchMethodException: BindingAdapterTest$personMappingTest$Person.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.newInstance(Class.java:412)

This is typically caused by your object class being defined inside a function (which makes its constructor require an additional parameter that Klaxon doesn't know how to fill).

Solution: move that class definition outside of the function.

Comments
  • Compatibility issue with Java 1.7

    Compatibility issue with Java 1.7

    Hi,

    I tried to use your library in my Android project with this configuration:

    compileSdkVersion 23
        buildToolsVersion "23.0.2"
        defaultConfig {
            applicationId "blah"
            minSdkVersion 21
            targetSdkVersion 23
            versionCode 1
            versionName "1.0"
        }
    

    When I try to build I get the error:

    Error:com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000)
    

    The problem according to Google was some library compiled for Java 1.8. I removed klaxon of my gradle file and my code compiled with no problem.

    Suggestions? Thanks in advance.

    opened by rodrigosalinas 17
  • Unable to deserialize nested collections

    Unable to deserialize nested collections

    Hi,

    Trying to deserialize a json containing nested collections raises a java.lang.ClassCastException with the following error message :

    java.base/sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl cannot be cast to java.base/java.lang.Class

    Here is a code sample to reproduce the issue

    class RootListOfMaps(
            val listOfMaps: List<Map<String, Int>>
    )
    
    Klaxon().parse<RootListOfMaps>("""
            {
                "listOfMaps": [
                    {
                        "Key0": 0,
                        "Key1": 1
                    }
                ]
            }
        """)
    
    opened by adrien-ben 16
  • argument has type java.lang.Double, got java.lang.Integer

    argument has type java.lang.Double, got java.lang.Integer

    Hi there,

    I try to parse some objects from an PHP-JSON-API.

    In my Kotlin class I have val workingPriceNetto: Double,

    But the json could hold 0 as well - 0. seems to be not possible with PHP (unless its a string "0.00")

    The parser should handle a 0 value as a Double

    opened by darkwood-studios 15
  • Adding date field

    Adding date field

    Implementing this the developer is able to parse the date using a lambda function. I was think to implement something like this:

    val obj = parse("/object.json") as JsonObject
    val date: Date? = obj.date("date", { Iso8601Utils.parse(it) })
    

    We can also provide another artifact just to resolve the Iso8601Utils dependency like other libraries used to do. What you think about it?

    Thank you.

    opened by ppamorim 15
  • Embedded discriminator

    Embedded discriminator

    In polymorphic fields discrimintator(type field) and object are 2 separated fields. Is it possible somehow to embed the discriminator? To be possible

    [
        { "type": "rectange", "width": 100, "height": 50 },
        { "type": "circle", "radius": 20 }
    ]
    

    This will allow more cleaner json. discriminator-demo

    opened by raderio 14
  • How to pretty print data class?

    How to pretty print data class?

    I have some code like this:

    data class MyClass(val list: List<String>, i: Int)
    val obj = MyClass(listOf(), 1)
    Klaxon().toJsonString(obj)
    

    However, this results in a string without any whitespace or formatting. Is there another call that applies formatting to the output string? The readme recommends JsonObject.toJsonString(), but I cant find any way to turn my object into a JsonObject with klaxon.

    What is the correct way to do this?

    opened by Nutomic 12
  • DecimalFormat exception

    DecimalFormat exception

    Hi, there is some exception in this line that crashes every toJsonString invoke

    https://github.com/cbeust/klaxon/blob/c5a282e026cb8e13f822ca91cff0e0b43a693aa7/src/main/kotlin/com/beust/klaxon/Render.kt#L36

    java.lang.IllegalArgumentException: Unquoted special character '#' in pattern "0.0####E0##;-0.0####E0##"

    opened by dibenlloch 12
  • Using a model class for parsing

    Using a model class for parsing

    Is this possible with Klaxon?

    Maybe I'm missing something, but from the examples on the README it seems you have to manually parse each property to a variable.

    opened by PierBover 12
  • Null field is not serialized, causing deserialization to fail

    Null field is not serialized, causing deserialization to fail

    I have an object with a nullable field. When serializing it, this field is skipped. When deserializing, it fails, probably because the field is missing.

    This short test reproduces the problem for me (on v5.0.2):

    import com.beust.klaxon.Klaxon
    import org.junit.jupiter.api.Test
    
    data class MyObj(val myAttr: Int?) {}
    
    internal class KlaxonTest {
        @Test
        fun testEncDec() {
            val obj = MyObj(null)
            val json = Klaxon().toJsonString(obj)
            println(json)  // output: {}
            Klaxon().parse<MyObj>(json)
            // fails: Unable to instantiate MyObj with parameters []
        }
    }
    

    If I provide a number instead of null, the test passes. The type does not seem to matter, nor does being a data class.

    opened by mverleg 11
  • Cannot use Jetifier on Klaxon

    Cannot use Jetifier on Klaxon

    Hi,

    when using Klaxon 5.2, i am getting the following error during compilation on Android-Studio:

    Failed to transform '/home/user/.gradle/caches/modules-2/files-2.1/com.beust/klaxon/5.2/7154d737db1fb2b26c0b8e6f1c15a5e39dfef61e/klaxon-5.2.jar' using Jetifier. Reason: null. (Run with --stacktrace for more details.)

    This issue is not apparent on Klaxon 5.0.11

    opened by SoundSonic1 9
  • Performance dramatically slow

    Performance dramatically slow

    Hi there,

    I try to use the Klaxon parser together with android room.

    So I have this example model:

    @Entity
    data class User(
            @PrimaryKey
            @Json(name = "user_id")
            var userID: String,
    
            @Json(name = "salutation")
            var salutation: String? = null,
    
            @Json(name = "company")
            var company: String? = null,
    
            @Json(name = "firstname")
            var firstName: String? = null,
    
            @Json(name = "lastname")
            var lastName: String? = null,
    
            @Json(name = "zip")
            var zip: String? = null
    )
    

    And want to parse it from this json string: {"firstname" : "George", "lastname" : "Clooney", "salutation" : "Herr", "user_id" : "530930", "zip" : "80335"}

    And this is the code which should do all the magic:

    fun getUser(): User {
            val userJson = pref!!.getString(KEY_USER, "") // the preferences stores the json string from above
            return Klaxon().parse<User>(userJson)!! // this method takes 8 seconds in debug mode!
        }
    

    According to the Android Studio Profiler the slow method is "findJsonPaths" called in Klaxon.kt

    I do have the same performance problems at any part where I'm using the klaxon parser. Any ideas for issue?

    Thanks for help.

    opened by darkwood-studios 9
  • Using Klaxon in Gradle Task

    Using Klaxon in Gradle Task

    I am trying to use Klaxon in a Gradle Task that generates some code for me, but when I try to access the parsed objects I get this error:

    class com.beust.klaxon.JsonObject cannot be cast to class Block (com.beust.klaxon.JsonObject and Block are in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader @4717c6b6)

    Might not be fault of Klaxon itself, but I am not sure where else to ask. Any help is appreciated.

    opened by MichaelPriebe 0
  • KlaxonDoc.java got compiled to Java 11 byte code

    KlaxonDoc.java got compiled to Java 11 byte code

    The JAR for Klaxon 5.6 contains a single class file, that was compiled to byte code with version 11, this flags the library as incompatible with Java 8 through byte code analysis.

    Is this an oversight or are you using library features from a newer JDK anyhow?

    opened by alshain 1
  • No accessors or field is found for property var XXXXXX: kotlin.String?

    No accessors or field is found for property var XXXXXX: kotlin.String?

    model= { var id: Int? = null lateinit var name: String lateinit var localPeerId: String var remotePeerId: String? = null @Json(serializeNull = false) var walletAddress: String? = null @Json(serializeNull = false) var avatar: String? = null var isInitialized: Boolean = false var initializeDate: ZonedDateTime? = null var createDate: ZonedDateTime? = null } val queryResult = dataProvider.getData() as MutableList val queryResultJson = JsonArray(queryResult) var result = queryResultJson.toJsonString()

    In the above code sample after installing app-release.apk has null refrence error for fields but in debug on emulator no error. I used implementation "com.beust:klaxon:5.6" in buld.gradle.

    in line "var result = queryResultJson.toJsonString()"

    we have this error: No accessors or field is found for property var ea.c.avatar: kotlin.String?

    b7.a0: No accessors or field is found for property var ea.c.avatar: kotlin.String? at b7.w.b(Unknown Source:259) at b7.w.a(Unknown Source:0) at b7.v$c$a.a(Unknown Source:3) at b7.v$c$a.invoke(Unknown Source:0) at b7.c0$b.invoke(Unknown Source:11) at b7.c0$c.b(Unknown Source:0) at b7.v$c.t(Unknown Source:7) at b7.f.call(Unknown Source:5) at q0.e.a(Unknown Source:310) at q0.w.e(Unknown Source:220) at q0.i.h(Unknown Source:51) at q0.k$a.a(Unknown Source:11) at q0.i.b(Unknown Source:0) at q0.k$a.b(Unknown Source:10) at q0.i.a(Unknown Source:0) at q0.k$a.c(Unknown Source:13) at org.example.app.MainActivity.U(Unknown Source:1517) at org.example.app.MainActivity.T(Unknown Source:15) at org.example.app.MainActivity.L(Unknown Source:0) at da.d.f(Unknown Source:2) at a6.j$a.a(Unknown Source:17) at n5.c.j(Unknown Source:18) at n5.c.k(Unknown Source:20) at n5.c.g(Unknown Source:0) at n5.b.run(Unknown Source:12) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:246) at android.app.ActivityThread.main(ActivityThread.java:8633) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

    opened by freelancerweb1991 0
  • Unable to instantiate MyClass

    Unable to instantiate MyClass

    Hi,

    I'm having this error message com.beust.klaxon.KlaxonException: Unable to instantiate Pdv (class def below) while I try to serialize a string (JSON format).

    Here is a part of the stringified JSON I am working on:

    {
      "pdv": [
        {
          "pop": "R",
          "ville": "Camphin-en-Pévèle",
          "prix": [
            {
              "valeur": "2.208",
              "maj": "2022-03-07 16:29:16",
              "id": "1",
              "nom": "Gazole"
            },
            {
              "valeur": "1.123",
              "maj": "2022-03-07 16:29:17",
              "id": "3",
              "nom": "E85"
            },
            {
              "valeur": "2.105",
              "maj": "2022-03-07 16:29:17",
              "id": "5",
              "nom": "E10"
            },
            {
              "valeur": "2.174",
              "maj": "2022-03-07 16:29:17",
              "id": "6",
              "nom": "SP98"
            }
          ],
          "latitude": "5059477.455",
          "adresse": "RD 93 GRANDE RUE",
          "id": "59780003",
          "services": {
            "service": [
              "Station de gonflage",
              "Laverie",
              "Lavage automatique",
              "Automate CB 24/24"
            ]
          },
          "cp": "59780",
          "longitude": "325781.84717474"
        }
      ]
    }
    

    Here is my entity:

    import com.beust.klaxon.Json
    
    class PdvListeEntity {
        data class Prix(
            val valeur: String? = null,
            val maj: String? = null,
            val id: String? = null,
            val nom: String? = null,
        )
    
        data class Service(
            val service: List<String>? = listOf(),
        )
    
        data class Pdv(
            val id: String? = null,
            val pop: String? = null,
            val adresse: String? = null,
            val ville: String? = null,
            val cp: String? = null,
            val latitude: String? = null,
            val longitude: String? = null,
            @Json(name = "services", ignored = true)
            val listServices: List<Service>? = listOf(),
            @Json(name = "prix")
            val listPrix: List<Prix>? = listOf(),
        )
    
        data class PdvListe(
            @Json(name = "pdv")
            val listPdv: List<Pdv>? = listOf(),
        )
    }
    

    And here is how I parse

    Klaxon().parse<PdvListeEntity.PdvListe>(theUpperJson)
    

    I found similar issues but couldn't find a way to resolve it. I've noticed that when I ignore listPrix, it works fine.

    I am using klaxon 5.5 and kotlin 1.6.10

    Thanks in advance,

    opened by azeroht 0
  • Using a TypeAdapter with a default class for null?

    Using a TypeAdapter with a default class for null?

    Looking in the code, it's not possible to specify a default class for a type adapter where the discriminator is null because we do this:

    val discriminant = jsonObject[discriminantFieldName] as Any
    polymorphicInfo.adapter.createInstance().classFor(discriminant)
    

    If the field is not present, this blows up - we could support this in a back-compatible way by doing this instead:

    val discriminant = jsonObject[discriminantFieldName]
    polymorphicInfo.adapter.createInstance().classForNullable(discriminant)
    

    In the TypeAdapter interface, we can add a default method like this:

    interface TypeAdapter<Output> where Output: Any {
        fun classFor(type: Any): KClass<out Output>
        fun classForNullable(type: Any?): KClass<out Output>{
            return classFor(type as Any)
        }
    }
    

    For people who want the current behavior, no change is required - the as Any in the default method will throw the same exception, and existing implementations of the interface are still valid because the new methods has a default.

    For people who want to allow null, they can do so by implementing the new classForNullable(type: Any?) method.

    I would be happy to provide a PR for this, if desired.

    opened by lmeadors 0
  • Failure parsing json into class with primary constructor with parameters not specifying var or val

    Failure parsing json into class with primary constructor with parameters not specifying var or val

    Hello,

    A json fragment like the following:

    {
        "aSymbol": "SYMBOL",
        "aType": 22
    }    
    

    cannot be parsed into any of the following kotlin clases (Example2 or Example3):

    private class Example2(aSymbol: String, aType: Int) {
        val symbol = aSymbol
        val type = aType
    }
    

    or

    class Example3(aSymbol: String, aType: Int) {
        val symbol: String
        val type: Int
    
        init {
            symbol = aSymbol
            type = aType
        }
    }
    

    while parsing into the following Example1 class will succeed:

    private class Example1(val aSymbol: String, val aType: Int)
    

    The parsing into Example2 or Example3 fail with a message like:

    Unable to instantiate Example2:No argument provided for a required parameter: "parameter #0 aSymbol of fun (kotlin.String, kotlin.Int)

    The parsing into Example2 or Example3 will succeed if a custom converter is configured.

    If I understood correctly, Klaxon is supposed to use the primary constructor (or even secondary ones, possibly?) with their defined parameters to instantiate the required class, in this case then triggering the init logic that may be defined in field initializers or in the init block(s).

    My environment:

    • Klaxon v5.5
    • Kotlin plugin v1.6.0
    • JVM v11
    • WIndows 10

    Thanks for your support, GP

    opened by gpaglia 0
Owner
Cedric Beust
Creator of Android Gmail.
Cedric Beust
A modern JSON library for Kotlin and Java.

Moshi Moshi is a modern JSON library for Android and Java. It makes it easy to parse JSON into Java objects: String json = ...; Moshi moshi = new Mos

Square 8.7k Jan 2, 2023
A lightweight Kotlin DSL to render DSL style Json to String.

A lightweight Kotlin DSL to render DSL style Json to String.

null 4 May 5, 2021
Modern JSON processor with readable Kotlin syntax.

Kq Modern cross-platform JSON processor with readable Kotlin syntax. cat ~/Desktop/bdb.ndjson | kq '.filter{it.bool("muted")}.sortedBy{it.long("size")

Daniel Demidko 6 Jan 25, 2022
Manager of progress using Lottie JSON, compatible for Java and Kotlin.

Progress Lottie IGB Manager of progress using Lottie JSON, compatible for Java and Kotlin. Int Top In Bottom Important Info: To create a raw folder: R

Isaac G. Banda 21 Sep 16, 2022
Customizable JSON Schema-based forms for Kotlin and Compose

Kotlin JSON Forms Customizable JSON Schema-based forms for Kotlin and Compose This project aims to reimplement JSON Forms in Kotlin for use in Compose

Copper Leaf 3 Oct 1, 2022
A Java serialization/deserialization library to convert Java Objects into JSON and back

Gson Gson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to a

Google 21.7k Jan 5, 2023
xls2json - Read in Excel file (.xls, .xlsx, .xlsm) and output JSON

xls2json Read in Excel file (.xls, .xlsx, .xlsm) and output JSON. Evaluates formulas where possible. Preserve type information from Excel via JSON typ

Tammo Ippen 39 Oct 19, 2022
Simple CLI app to convert XML retrieved from a configurable URI to JSON and back

XmlToJsonUtility Simple CLI app written in Kotlin (1.5.31) on Java 11, using Spring Boot. Queries a URI (default) as an XML source. Attempts to valida

Darius Washington 2 Oct 20, 2021
ktlint JSON Lines reporter

ktlint JSON Lines reporter Usage Download the jar and run: ktlint --reporter=jsonlines,artifact=ktlint-jsonlines-reporter.jar Download Either downloa

Anton Musichin 1 Nov 24, 2021
Generate a JSON bookmarks document from a GitHub user

Github to bookmarks This little webapp will generate a JSON bookmarks document from a GitHub user. This is intended to be used with bbt. An instance i

Benoit Lubek 2 Nov 8, 2021
Kotlin extensions for Moshi, Make every thing you want with Moshi in just one line.

Kotlin extensions for Moshi, Make every thing with square / Moshi in one line.

Mazen Rashed 15 Nov 21, 2021
Pojson provides Kotlin DSL for building complex jsons in declarative manner.

Pojson is a kotlin library for json prototyping. It brings DSL for building JsonObjectPrototype and JsonArrayPrototype. Prototypes don't reference to any json object/array models.

Alexander Mironychev 5 Jan 4, 2022
🚀Plugin for Android Studio And IntelliJ Idea to generate Kotlin data class code from JSON text ( Json to Kotlin )

JsonToKotlinClass Hi, Welcome! This is a plugin to generate Kotlin data class from JSON string, in another word, a plugin that converts JSON string to

Seal 2.8k Jan 3, 2023
Android Material Json Form Wizard is a library for creating beautiful form based wizards within your app just by defining json in a particular format.

Android Json Wizard Android Json Wizard is a library for creating beautiful form based wizards within your app just by defining json in a particular f

Vijay Rawat 355 Nov 11, 2022
Images grid JSON | Сетка изображений JSON

Images grid JSON | Сетка изображений JSON Задача Разработать приложение: Приложение должно получать JSON-список ссылок на изображения с сервера по адр

Sefo Notasi 2 Apr 25, 2022
Dynamic-UI-From-JSON - A Sample Android app to show dynamic UI generation from Json

Dynamic UI from JSON Functionality The app's functionality includes: The app gen

Rafsan Ahmad 12 Dec 16, 2022
SSU u-saint parser with Kotlin-Multiplatform and Ktor.

kusaint Soongsil University(SSU) u-Saint Parser with Kotlin Multiplatform. Prerequisites JVM !!IMPORTANT!! To run kusaint as a library in JVM environm

Hyomin Koo 3 Mar 23, 2022
Kotlin parser library with an easy-to-use DSL

Pratt Library for parsing expressions and a beautiful Kotlin DSL Just define your operators and operands with the Kotlin DSL and the parser is ready!

furetur 9 Oct 17, 2022
A TOML 1.0 parser library for Kotlin

4koma A small, stand-alone, easy to use TOML parser library for Kotlin. 4koma supports an array of convenient features, such as full TOML 1.0 complian

Anton Ekblad 53 Dec 12, 2022
ZATAC Scanner is Android Kotlin-based QR code scanner and parser which de-crypt TLV qr codes and parse them into their values.

ZATAC Scanner is Android Kotlin-based QR code scanner and parser which de-crypt TLV qr codes and parse them into their values.

Enozom 12 Apr 23, 2022