GithubHelp home page GithubHelp logo

klaxon's Introduction

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.

If you don't want to apply this option to every attribute, you can also set it as an instance-wide setting for Klaxon:

val settings = KlaxonSettings(serializeNull = false)

This saves you the hassle of setting these attributes onto every single field.

data class User(
    val username: String, val email: String, // mandatory
    val phone: String?, val fax: String?, val age: Int? // optional
)

Klaxon(settings)
  .toJsonString(User("user", "[email protected]", null, null, null))

// displays {}

You may still set settings with the @Json annotation onto specific fields. They will take precedence over global settings of the Klaxon instance.

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.

klaxon's People

Contributors

aiya000 avatar alfonso-eusebio avatar barsoosayque avatar cbeust avatar cy6ergn0m avatar damphat avatar fboldog avatar firionus avatar fmeum avatar jlleitschuh avatar kgilmer avatar kopilov avatar lpar avatar msrd0 avatar nikbucher avatar octogonapus avatar oellan avatar paplorinc avatar pgreze avatar phhusson avatar plannigan avatar ppamorim avatar prfss avatar rmadlal avatar s4kibs4mi avatar sanderv avatar sebastianaigner avatar shartte avatar vladrassokhin avatar wrimle avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

klaxon's Issues

Are there any Proguard considerations?

This is just a question. When I build an APK with a minifyEnabled true I get warnings:

Note: the configuration keeps the entry point 'letstwinkle.com.twinkle.model.ConnectionProfile { ConnectionProfile(com.beust.klaxon.JsonObject); }', but not the descriptor class 'com.beust.klaxon.JsonObject'

I am very green with Proguard. When I expose types of a library such as klaxon in my API. Is it up to me to manage Proguard rules for this? Are there any specific rules you can recommend?

SHould I just keep everything in com.beust.klaxon, what would a rule for that look like? I'd like to keep it simple, I'm sure this library does not have code bloat ;)

Unable to parse simple Data Class with a Double

When I run the code below I get the following error:

com.beust.klaxon.KlaxonException: Couldn't find a suitable constructor to initialize with {}

data class Balance(val amount: Double)

class JsonCodecTest {

    @Test
    fun mockClientWorks() {
        val json = "{\"amount\": 1000.23}"
        val signal = Klaxon().parse<Balance>(json)
        assertEquals(signal, Balance(1000.23))
    }
}

Am I doing something wrong or is this a genuine issue?

Many thanks for your time.

Remove optional fields

Optional fields are not necessary on this library, would be great to unwrap it on the backend and leave the developer to choose the default parameter. Today to have a default parameter the developer needs to do this:

jsonObject.int("value") ?: 0 //Return Int?

This provides a inconsistent and complexbility, something like this would be better:

jsonObject.int("value", default = 0) //Return Int

I think to maintain both methods, if the user wants to use the default reader, there is no problem.

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.

Refactor `toJsonString`

Using a boolean parameter to switch the behaviour of a method is widely regarded as an anti-pattern.

So instead of

toJsonString(true)

it would be much better to have a dedicated method

toPrettyJsonString()

New release needed

Hey guys,

this is a great library!
Unfortunately the latest release was a while ago and critical fixes like #47 are yet to be released.
Can you please publish a new version?

Thanks!

getting array from empty value

{
"from":"inpatient",
"department":"ADT",
"phototype":"PATHO",
"bodypart":" ",
"time":"2016/12/2 15:33:39",
"piclink":"",
"reportlink":["/BGData/DJ201612J/DJ20161201J0515/BG20161202004800.jpg","/BGData/DJ201612J/DJ20161201J0515/BG20161202004801.jpg"]
}

I think when getting the piclink as an array of strings, it should return an empty array instand of throwing an error,(test on ver 0.30)

Can someone write an updated exemple code ?

Hello,
I'm trying out the exemple that's written in the read.md but I get errors everywhere
"Parser is deprecated"
"Type mismatch, found Any? required : Any"
"A 'return' expression required in a function with a block body ('{...}')"
and so on

can someone help ?

Field renaming does not work for serialization

For Klaxon 2.0.6, consider the following test cases:

import com.beust.klaxon.Json
import com.beust.klaxon.Klaxon
import com.beust.klaxon.KlaxonException
import org.hamcrest.CoreMatchers
import org.junit.Assert
import org.junit.Test

class KlaxonTest {
    data class TestClass(@Json(name="first_name") val firstName: String)
    private val mapper = Klaxon()
    private val obj = TestClass("John")

    /**
     * Attempt to serialize an object with a renamed field.
     * The serialized object should contain the renamed field.
     */
    @Test
    fun serialize() {
        Assert.assertThat(
            mapper.toJsonString(obj),
            CoreMatchers.containsString("first_name")
        )
    }

    /**
     * Attempt to deserialize an object with a "first_name" field.
     */
    @Test
    fun deserializeFormatted() {
        val jv = "{\"first_name\":\"John\"}"
        Assert.assertThat(
            mapper.parse<TestClass>(jv),
            CoreMatchers.equalTo(obj)
        )
    }

    /**
     * Attempt to deserialize an object with a "firstName" field
     * This should fail.
     */
    @Test(expected=KlaxonException::class)
    fun deserializeNonFormatted() {
        val jv = "{\"firstName\":\"John\"}"
        mapper.parse<TestClass>(jv)
    }
}

The serialize test FAILS, and the other two pass. It appears that field renaming is not honoured for serialization.

Adding public variables without JSON equivalent causes NoSuchFieldException

Adding variables that do not map to JSON fields causes a NoSuchFieldException, as shown in these files.

Exception in thread "main" java.lang.NoSuchFieldException: change
	at java.lang.Class.getDeclaredField(Class.java:2070)
	at com.beust.klaxon.Klaxon$fromJsonObject$1.invoke(Klaxon.kt:232)
	at com.beust.klaxon.Klaxon$fromJsonObject$2.invoke(Klaxon.kt:264)
	at com.beust.klaxon.Klaxon.fromJsonObject(Klaxon.kt:301)
	at StockHelper.data(Stocky.kt:41)
	at MainKt.main(Main.kt:3)

Parse String is missing.

Currently this library parse a StringBuilderfun parse(rawValue: StringBuilder): Any? {}. Why? If it uses the reference as a internal reader, it needs to be completed internal (the reference). It also needs to accept a String.

modelVersion missing in version 0.25

When I do a build, I see the following warning:

Processing of /home/rob/.gradle/caches/modules-2/files-2.1/com.beust/klaxon/0.25/df8c5ede36248f9e5b2a66d88e8661be4c60d59d/klaxon-0.25.pom failed:
    'modelVersion' is missing. in com.beust:klaxon:0.25

UTF-8 streams will be butchered by lexer

Hi,

Your lexer assumes that the input stream is URL encoded (with /uxxxx and so on). I'm not really a JSON expert, but I haven't ever seen such JSON! All streams I've seen were just UTF-8 encoded and that causes the output to be hopelessly messed (due to char-byte-char encoding) with any non-ansi character. I think I will fix it just by changing your code to operate on chars instead of bytes. But you might look into this issue.

Maven distribution

Hi,

I could not find a maven repository with klaxon releases. Is distribution planned ?

Thanks

Randomly smashes Int into String when serializing generic map.

Using the following code

fun main(args: Array<String>) {
    val test = mapOf(
            "test" to mapOf<String, String>(),
            "2" to mapOf<String, Int>("test" to 22)
    )
    println(json {
        JsonObject(test)
    }.toJsonString(true))

}

It will smash the integer value into strings giving

{
  "test": {},
  "2": {
    "test": "22"
  }
}

Where it should be giving

{
  "test": {},
  "2": {
    "test": 22
  }
}

Update jcenter package, please

Hello. I need the proper int support which is only available in the git repo at the moment. Could you update the jcenter package to reflect github?

I tried to build it myself locally but I had some problems with different kotlin versions clashing.

Klaxon cannot deserialize JSON array at root of the document

It is completely valid for a JSON array to be at the root of a JSON document. jsonlint.com confirms this, and the standard at json.org does not forbid it. However, Klaxon 2.0.10 seems to require that documents start with an object.

For example, this is valid json:
[1, 2, 3]

Here's a test example:

    /**
     *
    java.lang.ClassCastException: com.beust.klaxon.JsonArray cannot be cast to com.beust.klaxon.JsonObject

    at com.prontoforms.pcns.auth.authorizer.MethodArnUnitTest.serializeList(MethodArnUnitTest.kt:102)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
     */
    @Test
    fun serializeList() {
        val obj = listOf(1,2,3)
        val mapper = Klaxon()

        val jv = mapper.toJsonString(obj)
        Assert.assertThat(jv, CoreMatchers.equalTo("[1, 2, 3]"))
        Assert.assertThat(mapper.parse(jv), CoreMatchers.equalTo(obj))
    }

Can't map array of JSON objects to a class containing primitives

Attempting to map a JSON file containing an array of objects results in this error:

Exception in thread "main" kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Call is not yet supported for this function: private constructor Double() defined in kotlin.Double[DeserializedClassConstructorDescriptor@3232a28a] (member = null)
	at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:89)
	at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:34)
	at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:93)
	at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32)
	at kotlin.reflect.jvm.internal.KFunctionImpl.getCaller(KFunctionImpl.kt)
	at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:107)
	at kotlin.reflect.jvm.internal.KCallableImpl.callDefaultMethod(KCallableImpl.kt:149)
	at kotlin.reflect.jvm.internal.KCallableImpl.callBy(KCallableImpl.kt:111)
	at com.beust.klaxon.Klaxon$fromJsonObject$2.invoke(Klaxon.kt:277)
	at com.beust.klaxon.Klaxon.fromJsonObject(Klaxon.kt:294)
	at com.beust.klaxon.DefaultConverter.convertValue(DefaultConverter.kt:110)
	at com.beust.klaxon.DefaultConverter.fromJson(DefaultConverter.kt:13)
	at com.beust.klaxon.Klaxon$fromJsonObject$1.invoke(Klaxon.kt:241)
	at com.beust.klaxon.Klaxon$fromJsonObject$2.invoke(Klaxon.kt:263)
	at com.beust.klaxon.Klaxon.fromJsonObject(Klaxon.kt:294)
	at MainKt.main(Main.kt:29)

Here are the source files that I used to set up this situation.

JSON file in question:

[
  {
    "date": "2018/01/10",
    "close": 0.25,
    "volume": 500000,
    "open": 0.5,
    "high": 0.5,
    "low": 0.25
  }
]

Splicing/Templating

This is really more of a suggestion.

So I am using Klaxon to generate digital passes for Wallets. The format of the pass has a bunch of boilerplate in it. Probably the best way to do this would be to have the basic pass template in a file, then replace some of the fields. I was thinking it might be nice to have a way to basically say 'parse this file into a new structure, then graft these new nodes to it, and replace these couple fields.'

Seems like that could be done with what is here. Just wondering if there's any interest in making that part of the lib.

DSL doesn't work like it is shown in the doc?

I'm trying to use the DSL with kt 1.0.4 and I get compile error with use of obj:
val json: JsonObject = com.beust.klaxon.json { obj("id", "1") }

Error:(49, 64) Type mismatch: inferred type is String but Pair<String, *> was expected

Maybe it doesn't work like that anymore?

Exception when serializing Map

In Klaxon 2.0.7, trying to serialize a map throws an exception. Perhaps this is a missing feature instead of a bug, but I think that it is a very important one.

/**
     * java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at kotlin.reflect.jvm.internal.FunctionCaller$Method.callMethod(FunctionCaller.kt:98)
    at kotlin.reflect.jvm.internal.FunctionCaller$InstanceMethod.call(FunctionCaller.kt:115)
    at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:107)
    at com.beust.klaxon.Klaxon.toJsonString(Klaxon.kt:213)
    at com.prontoforms.pcns.auth.auth.KlaxonTest.serializeMap(KlaxonTest.kt:83)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    Caused by: kotlin.reflect.full.IllegalCallableAccessException: Class kotlin.reflect.jvm.internal.FunctionCaller$FieldGetter can not access a member of class java.util.Collections$SingletonMap with modifiers "private final"
    at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:208)
    at com.beust.klaxon.DefaultConverter.toJson(DefaultConverter.kt:26)
    ... 31 more
    Caused by: java.lang.IllegalAccessException: Class kotlin.reflect.jvm.internal.FunctionCaller$FieldGetter can not access a member of class java.util.Collections$SingletonMap with modifiers "private final"
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
    at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
    at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
    at java.lang.reflect.Field.get(Field.java:390)
    at kotlin.reflect.jvm.internal.FunctionCaller$FieldGetter.call(FunctionCaller.kt:164)
    at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:107)
    ... 32 more
     */
    @Test
    fun serializeMap() {
        val data = mapOf("firstName" to "John")
        Klaxon().toJsonString(data)
    }

double accessor not numeric-friendly like int/long accessors

I have a need for a floating point field in a JSON object.

In my mind, '0' is a valid double as well, and certainly may be returned from my backend along with '1.2345'. However, JsonObject::double does not play nicely with other Number types like JsonObject::long for some reason.

Does it make sense to change this?

Can't parse "Long" type

Hi guys,
is parsing to the "Long" type not supported in the high level API?

The following does not work with 2.0.0-RC1:

Klaxon().parse<TestObj>(""" {"id": 53147483640 } """)
data class TestObj(var id: Long? = null)  

not even with a 32bit number:

Klaxon().parse<TestObj>(""" {"id": 234 } """)
data class TestObj(var id: Long? = null)  

Thanks!

Kotlin 1.0.0-rc

What is the last version? Is it compatible with Kotlin 1.0.0-rc?

Unexpected character at position 2: '< (60)'

I am using klaxon with my android app (proguard enabled) This error coming with latest android version 8.0. Any Idea what causing ?

Fatal Exception: java.lang.RuntimeException
Unexpected character at position 2: '< (60)'


com.beust.klaxon.Parser.parse (Unknown Source:24)
cc.cc.api.JsonParser$Companion.parse (Unknown Source:15)
cc.cc.api.JsonParser$Companion$processResponse$1$2.onCompleted (Unknown Source:52)
com.koushikdutta.async.future.SimpleFuture.getResultOrThrow (Unknown Source:10)
com.koushikdutta.async.future.SimpleFuture.cancel (Unknown Source:22)
com.koushikdutta.ion.IonRequestBuilder$EmitterTransform$1.onCompleted (Unknown Source:25)
com.koushikdutta.async.future.SimpleFuture.getResultOrThrow (Unknown Source:10)
com.koushikdutta.async.future.SimpleFuture.cancel (Unknown Source:22)
com.koushikdutta.ion.IonRequestBuilder$1.run (Unknown Source:55)
com.koushikdutta.async.AsyncServer$RunnableWrapper.run (Unknown Source:14)
android.os.Handler.handleCallback (Handler.java:789)
android.os.Handler.dispatchMessage (Handler.java:98)
android.os.Looper.loop (Looper.java:164)
android.app.ActivityThread.main (ActivityThread.java:6541)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.Zygote$MethodAndArgsCaller.run (Zygote.java:240)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:767)


Ignored fields not handled correctly

In Klaxon 2.0.7, the documentation states that you can ignore a field by either setting it to private, or adding @Json(ignored=true) to the field. However, neither of these methods work. I am using Oracle JDK 1.8.0_152.

// KlaxonUnitTest.kt
import com.beust.klaxon.Json
import com.beust.klaxon.Klaxon
import org.hamcrest.CoreMatchers
import org.junit.Assert
import org.junit.Test

class KlaxonUnitTest {

    /**
     *
     * Serializing a private val throws an exception.
     *
    java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at kotlin.reflect.jvm.internal.FunctionCaller$Method.callMethod(FunctionCaller.kt:98)
	at kotlin.reflect.jvm.internal.FunctionCaller$InstanceMethod.call(FunctionCaller.kt:115)
	at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:107)
	at com.beust.klaxon.Klaxon.toJsonString(Klaxon.kt:213)
	at com.prontoforms.pcns.auth.auth.KlaxonTest.testPrivateVal(KlaxonTest.kt:64)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: kotlin.reflect.full.IllegalCallableAccessException: Class kotlin.reflect.jvm.internal.FunctionCaller$FieldGetter can not access a member of class com.prontoforms.pcns.auth.auth.KlaxonTest$testPrivateVal$Person with modifiers "private final"
	at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:208)
	at com.beust.klaxon.DefaultConverter.toJson(DefaultConverter.kt:26)
	... 31 more
Caused by: java.lang.IllegalAccessException: Class kotlin.reflect.jvm.internal.FunctionCaller$FieldGetter can not access a member of class com.prontoforms.pcns.auth.auth.KlaxonTest$testPrivateVal$Person with modifiers "private final"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
	at java.lang.reflect.Field.get(Field.java:390)
	at kotlin.reflect.jvm.internal.FunctionCaller$FieldGetter.call(FunctionCaller.kt:164)
	at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:107)
	... 32 more
     */
    @Test
    fun serializePrivateVal() {
        data class Person(private val id: String, val firstName: String)
        val obj = Person("1", "John")
        Klaxon().toJsonString(obj)
    }

    /**
     * Ignoring a field with the @Json annotation does nothing
     * Test fails.  Serialized output is actually "{\"firstName\" : \"John\", \"id\" : \"1\"}"
     */
    @Test
    fun serializeIgnoredVal() {
        data class Person(@Json(ignored=true) val id: String, val firstName: String)
        val obj = Person("1", "John")
        val jv = Klaxon().toJsonString(obj)
        Assert.assertThat(jv, CoreMatchers.not(CoreMatchers.containsString("id")))
    }
}

Null objects causes crashes

When receiving objects that have null values e.g.

{
"foobar" : null
}

the code would throw a runtime exception stating that the 'n' character is unexpected.

StringBuilder version release

I'm trying to use the parser with the StringBuilder instead of an InputStream, but it would seem that a version has not been released to bintray since that code was merged. Is there any plan to release a new version soon?

Simplify README

Currently the README looks a little bit polluted with information. Would be good to create a Wiki explaining how to heavily use the library and leave a simple, short and self-explained implementation.

JsonArray varargs constructor taking precedence

I'm not sure if this is due to a specific version of Kt, but when I try to create a JsonArray explicitly passing it a list, it turns into a nested list. I'm finding that the varargs constructor is getting called.

Add ability to output ordered json

For a custom project utilizing json I would appreciate having an operation that outputs alphabetically ordered json. This is needed to add a signature to it and to verify that signature afterwards.
It is therefore important that underlying maps are sorted recursively. I think the easiest solution to achieve that is to use Kotlin's standard operation toSortedMap() which, unfortunately, does not sort recursively.

Bad method signature for string parsing.

I found the signatures for parsing raw strings to be a little confusing.
When I try to parse a raw string I kind of expect to use the parser like that:

Parser().parse(myString)

The problem is that the parse method which accepts a string uses it to load a file instead of directly processing it.

fun parse(fileName: String)

So when I want to parse a raw string i have to wrap it in a StringBuilder instance, which is not nice.

Parser().parse(StringBuilder(myString))

From my perspective parse() should treat a string argument as a raw string value and directly parse it.
I know it might be problematic to change the behaviour of fun parse(fileName: String), but the current implementation is far from ideal.

Custom Converter is ignored when serializing objects in list

Custom Converters not used for items in lists. This does not seem to apply to nested objects, which work as intended.

/**
     * This test passes.
     */
    @Test
    fun serializeNestedInstant() {
        data class Person(val firstName: String, val dob: Instant)

        class EpochMilliInstantConverter: Converter<Instant> {
            override fun toJson(value: Instant) = value.toEpochMilli().toString()
            override fun fromJson(jv: JsonValue) = throw NotImplementedError()
        }

        // When no custom converted is provided, empty value for dob.  This used to throw an exception.  Not sure what intended operation is
        val obj = Person("John", Instant.ofEpochMilli(9001))
        Assert.assertThat(Klaxon().toJsonString(obj), CoreMatchers.not(CoreMatchers.containsString("9001")))
        // Actual: {"dob" : {}, "firstName" : "John"}

        // When custom converted is provided, dob is serialized as expected
        val mapper = Klaxon().converter(EpochMilliInstantConverter())
        Assert.assertThat(mapper.toJsonString(obj), CoreMatchers.containsString("9001"))
        // Actual: {"dob" : 9001, "firstName" : "John"}
    }

    /**
     * This test does not pass.
     */
    @Test
    fun serializeListOfInstants() {
        val dates = listOf(Instant.ofEpochMilli(9001))

        class EpochMilliInstantConverter: Converter<Instant> {
            override fun toJson(value: Instant) = value.toEpochMilli().toString()
            override fun fromJson(jv: JsonValue) = throw NotImplementedError()
        }

        // despite custom converter being provided, instant is not serialized.  Empty value in list
        val mapper = Klaxon().converter(EpochMilliInstantConverter())
        Assert.assertThat(mapper.toJsonString(dates), CoreMatchers.containsString("9001"))
        // Actual: [{}]
    }

I appreciate all of your quick resolutions so far! 👍

Interface Parser does not have constructors

I have copied and pasted the function for converting to JSON and I get the above error.

Interface Parser does not have constructors

I have pasted the following code from Reademe.md

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

what is bad?

val stringBuilder: StringBuilder = StringBuilder(request.toString())
and I see Expected a value type Any!

String gets trimmed?

My Json String in RecyclerView - CardView is getting trimmed and I can't figure out why.

Json:
[ ... {
"witz":"Fritzchen: \"Darf ich ins Schwimmbad? Das 3m-Brett wird eröffnet.\"\n\nMutter: \"Ja.\"\n\nFritzchen kommt mit einem gebrochenem Bein zurück.\n\nAm nächstem Tag: \"Mutti darf ich ins Schwimmbad? Das 10m-Brett wird eröffnet.\"\n\nMutter: \"Ja.\"\n\nFritzchen kommt mit zwei gebrochenen Beinen zurück.\n\nAm nächsten Tag: \"Mutti darf ich ins Schwimmbad? Das Wasser wird eingelassen.\""
}, ... ]

Code:
val array = Parser().parse(resources.openRawResource(id)) as JsonArray<*>

recyclerView = view.find(R.id.recycler_view)
recyclerView?.layoutManager = LinearLayoutManager(context)
recyclerView?.adapter = MainAdapter(
context,
(0 until array.size).map { """${array["witz"][it]}""" },
(0 until array.size).map { """${getString(R.string.id_count)} ${it + 1}/${array.size}""" }
)

Output:
Fritzchen: "Darf ich ins Schwimmbad? Das 3m-Brett wird eröffnet."

Mutter: "Ja."

Empty StringBuilder causes No state found: INIT EOF

Hi guys

I was doing unit test on my application and I discovered that sending a empty StringBuilder causes a crash. I think that you be good to detect it before start the parsing and then, return a empty JsonObject or JsonArray.

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.

Klaxon cannot deserialize to or from raw strings

In Klaxon 2.0.10:

  • If an object serializes to a string, it cannot be deserialized
  • cannot deserialize a list of strings
    data class Person(val id: String, val firstName: String)

    /** This converter serializes a person to a string **/
    class PersonConverter: Converter<Person> {
        override fun toJson(value: Person) = "\"${value.id}:${value.firstName}\""
        override fun fromJson(jv: JsonValue) = jv.string?.let {
            val (id, firstName) = it.split(":")
            Person(id, firstName)
        } ?: throw KlaxonException("Invalid Person: $jv")
    }

    @Test
    fun deserializeStringListToObjectList() {
        val mapper = Klaxon().converter(PersonConverter())
        val obj = listOf(Person("1", "John"))

        val jv = mapper.toJsonString(obj)
        Assert.assertThat(jv, CoreMatchers.equalTo("[1:John]")) // Passes
        Assert.assertThat(mapper.parse(jv), CoreMatchers.equalTo(obj)) // Fails
    }

    @Test
    fun deserializeStringToObject() {
        val mapper = Klaxon().converter(PersonConverter())
        val obj = Person("1", "John")

        val jv = mapper.toJsonString(obj)
        Assert.assertThat(jv, CoreMatchers.equalTo("1:John")) // Passes
        Assert.assertThat(mapper.parse(jv), CoreMatchers.equalTo(obj))  // Fails
    }

    @Test
    fun deserializeStringListToStringList() {
        val obj = listOf("foo", "bar", "baz")
        val mapper = Klaxon()

        val jv = mapper.toJsonString(obj)
        Assert.assertThat(jv, CoreMatchers.equalTo("[\"foo\", \"bar\", \"baz\"]")) // Passes
        Assert.assertThat(mapper.parse(jv), CoreMatchers.equalTo(obj)) // Fails
    }

Now I get:

java.lang.ClassCastException: java.lang.String cannot be cast to com.beust.klaxon.JsonObject

    at com.prontoforms.pcns.auth.authorizer.MethodArnUnitTest.deserializeStringToObject(MethodArnUnitTest.kt:181)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

or

java.lang.ClassCastException: com.beust.klaxon.JsonArray cannot be cast to com.beust.klaxon.JsonObject

    at com.prontoforms.pcns.auth.authorizer.MethodArnUnitTest.deserializeStringListToObjectList(MethodArnUnitTest.kt:180)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Official release is built with pre-release version of Kotlin

Adding com.beust:klaxon:0.29 to my build.gradle I get this error:

Kotlin: Class 'com.beust.klaxon.JsonKt' is compiled by a pre-release version of Kotlin and cannot be loaded by this version of the compiler

I am using 1.1.1-release-IJ2017.1-1. Now that Kotlin 1.1 is out and stable, could you recompile against that?

Thanks!

Klaxon does not serialize enums

Usually, an enum is serialized by calling its toString, but in Klaxon 2.0.10, it becomes empty. I'm not sure if it's the intention to leave the conversion to the user, but I'll make this issue for you anyway.

enum class Colour { Red, Green, Blue }

    @Test
    fun serializeEnum() {
        val obj = Colour.Red

        Assert.assertThat(Klaxon().toJsonString(obj), CoreMatchers.equalTo("Red"))
    } 

Klaxon with Anko's doAsync fails to execute

Hello !
I'm trying to parse an url whenever I press a button :

    button.setOnClickListener {
doAsync{
    val result = URL("http://date.jsontest.com/").readText()
    val parser: Parser = Parser()
    val stringBuilder: StringBuilder = StringBuilder(result)
    val json: JsonObject = parser.parse(stringBuilder) as JsonObject
    val time = "Time : ${json.string("time")}"
uiThread { textView.setText(time) }}}

But I'm getting an error :

Error pruning approved print services. java.util.concurrent.TimeoutException: Cannot get spooler!

What is wrong ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.