Klaxon has different API's depending on your needs:
An object binding API to bind JSON documents directly to your objects, and vice versa.
A streaming API to process your JSON documents as they're being read.
A low level API to manipulate JSON objects and use queries on them.
A JSON path query API to extract specific parts of your JSON document while streaming.
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:
classPerson(valname:String, valage:Int)
Classes with default parameters are supported as well:
classPerson (valname:String, varage: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 classPerson(
@Json(name ="the_name")
valname: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.
classIgnored(valname: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:
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:
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.
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 {
overridefuntoJson(fieldName:String) =FieldRenamer.camelToUnderscores(fieldName)
overridefunfromJson(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:
interfaceConverter {
funcanConvert(cls:Class<*>) : BooleanfuntoJson(value:Any): StringfunfromJson(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:
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:
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:
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:
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:
interfacePropertyStrategy {
/** * @return true if this property should be mapped.*/funaccept(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 {
overridefunaccept(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:
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:
openclassShapedata classRectangle(valwidth:Int, valheight:Int): Shape()
data classCircle(valradius:Int): Shape()
We then define the class that the objects of this array are instances of:
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:
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 asRectangle).isEqualTo(Rectangle(100, 50))
assertThat(shapes[1].shape asCircle).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?=nullvar age:Int?=nullvar flag:Boolean?=nullvar array:List<Any> = arrayListOf<Any>()
var obj1:JsonObject?=nullwhile (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 classPerson(valname:String, valage:Int)
val array ="""[ { "name": "Joe", "age": 23 }, { "name": "Jill", "age": 35 } ]"""funstreamingArray() {
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.
请发表评论