diff --git a/DSL.md b/DSL.md new file mode 100644 index 00000000..4c2af5a4 --- /dev/null +++ b/DSL.md @@ -0,0 +1,284 @@ +DSL Platform JSON library +========================= + +DSL-JSON was born as an attempt to show other developers that DSL Platform is not jet another ORM. +When talking about abstract, unfamiliar stuff, some developers can argue all kind of viewpoints, +especially when they don't need to back them up with numbers. + +So, during 2014, as an attempt simplify/resolve such discussions and have concrete numbers, +a new target was added to DSL Platform - generating optimized JSON conversion. + +Its rather unfortunate that to get much better number one had to reinvent all kind of wheels, +in this case JSON library which tried to reduce allocation and branching with several +optimizations on how to improve specific type conversions. +As final touch, replacing reflection with compiled code was supposed to simplify reasoning +(you can just look at generated code and debug it) and performance (too much reflection is not really healthy). + +## Used optimizations + +Various optimizations were used in the initial library to get significant improvement over best libraries at the time. +But it was nothing crazy, just regular simple code with occasional bit shifting and maybe few exotic operations. + +### Work with bytes instead of chars + +Many people consider JSON as string even today. Its human-readable, so obviously it is a string. +But strings are just a sequence of bytes and in case of JSON a sequence of UTF-8 bytes. +So instead of treating it as a string or an array of chars, just treat it as array of bytes +and apply special UTF-8 rules when required (which is not all that often). + +### Converters without "unnecessary" allocations + +By not using strings, but rather bytes one starts to see allocations all around him. +So major improvement comes from specialized converters which work on bytes directly and +will consider all kind of inputs as sequence of bytes which can be processed using just primitive Java types. +For example, JSON number is not a string, but rather somewhat bloated representation of a number with many bytes. +Eg number `123` is represented with 3 bytes in JSON, while it would take 4 bytes in most binary encodings. +Of course, larger numbers, eg: `1234567` will take 7 bytes in JSON, but still only 4 bytes in most binary encodings. +But then... if one wants to parse a date, eg `"2022-02-24"` as long as you know it is a date you can parse it as 3 numbers, +consisting from a year, month and a day. + +### Schema encoded in Schema-less JSON + +If one considers JSON just a transport protocol between two endpoints which are aware of same schema on both sides, +various optimizations become possible. This way `""` does not need to be parsed as string, +especially if different type is expected, such as date or enum. While its kind of futile trying to +optimize JSON if you don't have knowledge about expected schema, there will be vast number of optimizations +to perform if you don't need to parse JSON into generic tokens, only to be replaced into final types. +Just parse JSON into final types and skip over intermediary representation. + +### Reducing JSON size due to knowledge of actual Schema + +Go-to .NET JSON library had a nice optimization of omitting values from JSON which it could infer from object schema. +Eg, there is no need to encode `0` or `false` for `integer` or `boolean` property which is non-nullable. +This wasn't really a standard practice in Java world, so it became significant when useful (even today +this optimization reduces one of my favorite endpoint from 64kB to <1kB for most cases as endpoint +consists from thousands of boolean fields specifying if a field is used or not). + +The most extreme example of this if when you encode Java objects in JSON array notation, +which is valid JSON and very useful as response for table-like responses. +Consider object with 3 properties, eg `int i, bool b and string s`, when returned as a collection +one could send JSON as + + [ + {"i":100,"b":true,"s":"abc"}, + ... + {"i":500,"b":false,"s":"def"} + ] + +or in a more compact format, assuming that you are aware of the schema on both sides: + + [ + [100,true,"abc"], + ... + [500,false,"def"] + ] + +This is rather natural thing to do in many endpoints, but really underused in the wild. + +### Optimize for the common case + +While solving the problem for all the cases is the baseline one should expect, +as applications should first be correct and only then performant if one has a common usage in mind +specific optimizations can be employed with that case in mind. +An example of this would be BigDecimal type which one could argue requires complex parsing +to account for all the edge cases: too big of a number, too many decimals, etc... +But if one has a notion of BigDecimal mostly as a fixed decimal number mostly used for Money, +it becomes clear that most of the time long is sufficient for parsing that kind of JSON input, +as long can hold up to 20 digits, which should be cover most cases for Money. +Even if people want to quote Money, so Javascript does not mess up the actual amount, +by knowing the schema this can still be parsed in an optimized way most of the time. +In case there is more than 20 digits, or exponent is used, it is rather cheap to switch over to general case +of parsing BigDecimal input. + +### Bit-aware String parsing + +If one works with bits a lot, he can find out all kind of cases which could be optimized compared to "common code". +Marko Elezovic contributed one such bit twiddling optimization to common case string parsing which reads as + + if ((bb ^ '\\') < 1) break; + +Its sole purpose is to break out of the common case ascii parsing into a more general case of UTF-8 parsing, +but when most of your strings are ASCII, this has a significant impact. + +### Numbers as bytes + +In C and other unmanaged languages, it is common to employ various optimizations to squeeze more performance out of the code. +One common optimization is encoding several "numbers" as bytes into a single number. +Assuming that we know we are only dealing with a single digit number (0-9) and knowing that we want to store several numbers +at a single location, to store number such as 253 we can store this as a sequence of 4 bytes consisting from `0 2 5 3`. +Then when one wants to write a 4 digit number in JSON it can use this lookup table and: + * divide by 1000 to get the first number + * lookup from a table the other 3 numbers by shifting them at appropriate locations + +In practice this looks like: + + int q = value / 1000; + int v = DIGITS[value - q * 1000]; + buf[pos] = (byte) (q + '0'); + buf[pos + 1] = (byte) (v >> 16); + buf[pos + 2] = (byte) (v >> 8); + buf[pos + 3] = (byte) v; + +where you even avoid to do costly modulo in favor of multiplication and subtraction. + +### Keys are just identifiers + +When you know the schema, you should know all keys upfront, which means that when writing JSON you can just +copy them from pre-defined byte array into output. +But nice optimization can be employed during reading as you don't need to "allocate string" to compare identifiers. +One can just keep identifier in the parsed byte array buffer and compare that. Going further this identifier can be parsed as a pure number, +representing the hash of the relevant identifier. + +Eg, when parsing `{"number":123}`, during start of parsing `"number"` part of the JSON, +since we know this is an identifier, we can pin the location of byte array and start calculating hash of a value inside the string. +If hash values match to the expected one (which we knew before parsing) we can just compare the byte array from the pinned location +(if we even want to do that - sometimes it is just good enough that we have found the same hash). + +### Reflection can be significant + +While there are various other optimizations in the library, one major optimizations is instead of having reflection heavy processing, +when schema is known one can write code optimized for the known schema. +This does contribute significantly to the final numbers, although most of the speedup +comes from the optimized converters and removal of unnecessary allocation. +Still, it was common practice to compile code for performance reasons instead of interpret it; +and this is kind of similar in a sense by having code look like + + instance.field1 = ConverterType1.read(input); + instance.field2 = ConverterType2.read(input); + instance.field3 = ConverterType3.read(input); + +vs more generic solution of + + for (FieldType ft : instanceFields) { + instance.setValue(ft.fieldName, ft.converter.read(input); + } + +And even though more generic solution might be less code, its more unpredictable and thus less optimizable. + +## Current state of DSL Platform integration + +Currently, DSL-JSON targets Java6 for base library (as it was developed in 2014) but due to +unfortunate history and the state of industry, it is not really all that useful to have it as an annotation processor, +especially once DSL-JSON upgrades the baseline to Java8. + +So some time in the future, `dsl-json` and `dsl-json-java8` project will be merged, while `dsl-json-processor` will be deleted. +DSL Platform will still be able to use DSL-JSON, but there will be no point in generating DSL to create optimized converters, +as Java8 project will have superset of that behavior. + +Until then, if one wants to use DSL Platform with DSL-JSON through DSL-JSON (which is not really a natural way to use DSL Platform) +the rest of README provides more information. + +## Schema based serialization + +DSL can be used for defining schema from which POJO classes with embedded JSON conversion are constructed. +This is useful in large, multi-language projects where model is defined outside of Java classes. +More information about DSL can be found on [DSL Platform](https://dsl-platform.com) website. + +## @CompiledJson annotation + +Annotation processor works by analyzing Java classes and its explicit or implicit references. +Processor outputs encoding/decoding code/descriptions at compile time. +This avoids the need for reflection, provides compile time safety and allows for some advanced configurations. +Processor can register optimized converters into `META-INF/services`. +This will be loaded during `DslJson` initialization with `ServiceLoader`. +Converters will be created even for dependent objects which don't have `@CompiledJson` annotation. +This can be used to create serializers for pre-existing classes without annotating them. + +### DSL Platform annotation processor + +DSL Platform annotation processor requires .NET/Mono to create databindings. +It works by translating Java code into equivalent DSL schema and running DSL Platform compiler on it. +Since v1.7.2 Java8 version has similar performance, so the main benefit is ability to target Java6. +Bean properties, public non-final fields and only classes with empty constructor are supported. +Only object format is supported. + +DSL Platform annotation processor can be added as Maven dependency with: + + + com.dslplatform + dsl-json-processor + 1.10.0 + provided + + +For use in Android, Gradle can be configured with: + + dependencies { + compile 'com.dslplatform:dsl-json:1.10.0' + annotationProcessor 'com.dslplatform:dsl-json-processor:1.10.0' + } + +Project examples can be found in [examples folder](examples) + +### Java/DSL property mapping + +| Java type | DSL type | Java type | DSL type | +| ------------------------- | ------------ | ---------------------------------- | ------------ | +| int | int | byte[] | binary | +| long | long | java.util.Map<String,String> | properties? | +| float | float | java.net.InetAddress | ip? | +| double | double | java.awt.Color | color? | +| boolean | bool | java.awt.geom.Rectangle2D | rectangle? | +| java.lang.String | string? | java.awt.geom.Point2D | location? | +| java.lang.Integer | int? | java.awt.geom.Point | point? | +| java.lang.Long | long? | java.awt.image.BufferedImage | image? | +| java.lang.Float | float? | android.graphics.Rect | rectangle? | +| java.lang.Double | double? | android.graphics.PointF | location? | +| java.lang.Boolean | bool? | android.graphics.Point | point? | +| java.math.BigDecimal | decimal? | android.graphics.Bitmap | image? | +| java.time.LocalDate | date? | org.w3c.dom.Element | xml? | +| java.time.OffsetDateTime | timestamp? | org.joda.time.LocalDate | date? | +| java.util.UUID | uuid? | org.joda.time.DateTime | timestamp? | + + +### Java/DSL collection mapping + +| Java type | DSL type | +| --------------------- | ------------- | +| array | Array | +| java.util.List | List | +| java.util.Set | Set | +| java.util.LinkedList | Linked List | +| java.util.Queue | Queue | +| java.util.Stack | Stack | +| java.util.Vector | Vector | +| java.util.Collection | Bag | + +Collections can be used on supported Java types, other POJOs and enums. + +### Other collections/containers + +Java8 supports all kinds of collections, even maps and Java8 specific container such as Optional. + +### Custom types in compile-time databinding + +Types without builtin mapping can be supported the same way as in Java8 version. +The main difference that converters only support the legacy format with `JSON_READER`/`JSON_WRITER`. + +Custom converter for `java.util.Date` can be found in [example project](examples/MavenJava6/src/main/java/com/dslplatform/maven/Example.java#L116) +Annotation processor will check if custom type implementations have appropriate signatures. +Converter for `java.util.ArrayList` can be found in [same example project](examples/MavenJava6/src/main/java/com/dslplatform/maven/Example.java#L38) + +`@JsonConverter` which implements `Configuration` will also be registered in `META-INF/services` which makes it convenient to [setup initialization](examples/MavenJava6/src/main/java/com/dslplatform/maven/ImmutablePerson.java#L48). + +## Dependencies + +Core library (with analysis processor) and DSL Platform annotation processor targets Java6. +Java8 library includes runtime analysis, reflection support, annotation processor and Java8 specific types. When Java8 annotation processor is used Mono/.NET doesn't need to be present on the system. +Android can use Java8 version of the library even on older versions due to lazy loading of types which avoids loading types Android does not support. + +If not sure which version to use, use Java8 version of the library with annotation processor. + +## FAQ + + ***Q***: DslJson is failing with unable to resolve reader/writer. What does it mean? + ***A***: During startup DslJson loads services through `ServiceLoader`. For this to work `META-INF/services/com.dslplatform.json.Configuration` must exist with the content of `dsl_json_Annotation_Processor_External_Serialization` or `dsl_json.json.ExternalSerialization` which is the class crated during compilation step. Make sure you've referenced processor library (which is responsible for setting up readers/writers during compilation) and double check if annotation processor is running. Refer to [example projects](examples) for how to set up environment. As of v1.8.0 Java8 version of the library avoids this issue since services are not used by default anymore in favor of named based convention. Eclipse is known to create problems with annotation processor since it requires manual setup (instead of using pom.xml setup). For Eclipse the best workaround is to build with Maven instead or relying on its build tools. + + ***Q***: Maven/Gradle are failing during compilation with `@CompiledJson` when I'm using DSL Platform annotation processor. What can I do about it? + ***A***: If Mono/.NET is available it *should* work out-of-the-box. But if some strange issue occurs, detailed log can be enabled to see what is causing the issue. Log is disabled by default, since some Gradle setups fail if something is logged during compilation. Log can be enabled with `dsljson.loglevel` [processor option](examples/MavenJava6/pom.xml#L35) + + ***Q***: DSL Platform annotation processor checks for new DSL compiler version on every compilation. How can I disable that? + ***A***: If you specify custom `dsljson.compiler` processor option or put `dsl-compiler.exe` in project root it will use that one and will not check online for updates + + ***Q***: What is this DSL Platform? + ***A***: DSL Platform is a proprietary compiler written in C#. Since v1.7.0 DSL Platform is no longer required to create compile-time databinding. Compiler is free to use, but access to source code is licensed. If you want access to the compiler or need performance consulting [let us know](https://dsl-platform.com) diff --git a/README.md b/README.md index 6068eb54..d0a6fb5e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ DSL-JSON library ================ -Fastest JVM (Java/Android/Scala/Kotlin) JSON library with advanced compile-time databinding support. Compatible with DSL Platform. +Fastest JVM (Java/Android/Scala/Kotlin) JSON library with advanced compile-time databinding support. Java JSON library designed for performance. Built for invasive software composition with DSL Platform compiler. @@ -9,7 +9,6 @@ Java JSON library designed for performance. Built for invasive software composit ## Distinguishing features - * supports external schema - Domain Specification Language (DSL) * works on existing POJO classes via annotation processor * performance - faster than any other Java JSON library. On par with fastest binary JVM codecs * works on byte level - deserialization can work on byte[] or InputStream. It doesn't need intermediate char representation @@ -29,28 +28,27 @@ Java JSON library designed for performance. Built for invasive software composit * Scala types support - Scala collections, primitives and boxed primitives work without any extra annotations or configuration * Kotlin support - annotation processor can be used from Kotlin. NonNull annotation is supported * JsonB support - high level support for JsonB String and Stream API. Only minimal support for configuration - -## Schema based serialization - -DSL can be used for defining schema from which POJO classes with embedded JSON conversion are constructed. -This is useful in large, multi-language projects where model is defined outside of Java classes. -More information about DSL can be found on [DSL Platform](https://dsl-platform.com) website. + * supports external schema - [Domain Specification Language](DSL.md) ## @CompiledJson annotation Annotation processor works by analyzing Java classes and its explicit or implicit references. Processor outputs encoding/decoding code/descriptions at compile time. This avoids the need for reflection, provides compile time safety and allows for some advanced configurations. -Processor can register optimized converters into `META-INF/services`. -This will be loaded during `DslJson` initialization with `ServiceLoader`. -Since v1.8.0 naming conventions will be used for Java8 converters (`package._NAME_DslJsonConverter`) which works even without loading services upfront. -Converters will be created even for dependent objects which don't have `@CompiledJson` annotation. + +By default, library only searches for `@CompiledJson` annotation, but it can be configured to search for `@JacksonCreator` and `@JsonbCreator`. +Converters will be created even for dependent objects which don't have relevant annotation. This can be used to create serializers for pre-existing classes without annotating them. +There are 2 main ways how generated code/manual services are detected: + * with lookups from `META-INF/services` through `ServiceLoader` during `DslJson` initialization + * by probing for name conventions: `package._NAME_DslJsonConverter` when required + ### Java8 annotation processor -Since v1.7.0 DSL-JSON supports compile time databinding without Mono/.NET dependency. -It provides most features and flexibility, due to integration with runtime analysis and combining of various generic analysis. +The recommended way to use library is via Java8 annotation processor. +Alternative, DSL processor is rather specialized way and is not suited for average Java developer. +Java8 processor provides most features and flexibility, due to integration with runtime analysis and combining of various generic analysis. Bean properties, public fields, classes without empty constructor, factories and builder patterns are supported. Package private classes and factory methods can be used. Array format can be used for efficient payload transfer. @@ -60,7 +58,7 @@ To use Java8 annotation processor its sufficient to just reference Java8 version com.dslplatform dsl-json-java8 - 1.9.9 + 1.10.0 For use in Android, Gradle can be configured with: @@ -72,92 +70,22 @@ For use in Android, Gradle can be configured with: } } dependencies { - compile 'com.dslplatform:dsl-json-java8:1.9.9' - annotationProcessor 'com.dslplatform:dsl-json-java8:1.9.9' + compile 'com.dslplatform:dsl-json-java8:1.10.0' + annotationProcessor 'com.dslplatform:dsl-json-java8:1.10.0' provided 'javax.json.bind:javax.json.bind-api:1.0' } -To use DSL-JSON on older Android versions setup should be done without initializing Java8 specific types: - - private final DslJson dslJson = new DslJson(runtime.Settings.basicSetup()); - - -### DSL Platform annotation processor - -DSL Platform annotation processor requires .NET/Mono to create databindings. -It works by translating Java code into equivalent DSL schema and running DSL Platform compiler on it. -Since v1.7.2 Java8 version has similar performance, so the main benefit is ability to target Java6. -Bean properties, public non-final fields and only classes with empty constructor are supported. -Only object format is supported. - -**If you are not sure which annotation processor to use, you should probably use the Java8 version instead of the DSL Platform one**. -DSL Platform annotation processor can be added as Maven dependency with: - - - com.dslplatform - dsl-json-processor - 1.9.9 - provided - - -For use in Android, Gradle can be configured with: - - dependencies { - compile 'com.dslplatform:dsl-json:1.9.9' - annotationProcessor 'com.dslplatform:dsl-json-processor:1.9.9' - } - Project examples can be found in [examples folder](examples) -### Java/DSL property mapping - -| Java type | DSL type | Java type | DSL type | -| ------------------------- | ------------ | ---------------------------------- | ------------ | -| int | int | byte[] | binary | -| long | long | java.util.Map<String,String> | properties? | -| float | float | java.net.InetAddress | ip? | -| double | double | java.awt.Color | color? | -| boolean | bool | java.awt.geom.Rectangle2D | rectangle? | -| java.lang.String | string? | java.awt.geom.Point2D | location? | -| java.lang.Integer | int? | java.awt.geom.Point | point? | -| java.lang.Long | long? | java.awt.image.BufferedImage | image? | -| java.lang.Float | float? | android.graphics.Rect | rectangle? | -| java.lang.Double | double? | android.graphics.PointF | location? | -| java.lang.Boolean | bool? | android.graphics.Point | point? | -| java.math.BigDecimal | decimal? | android.graphics.Bitmap | image? | -| java.time.LocalDate | date? | org.w3c.dom.Element | xml? | -| java.time.OffsetDateTime | timestamp? | org.joda.time.LocalDate | date? | -| java.util.UUID | uuid? | org.joda.time.DateTime | timestamp? | - - -### Java/DSL collection mapping - -| Java type | DSL type | -| --------------------- | ------------- | -| array | Array | -| java.util.List | List | -| java.util.Set | Set | -| java.util.LinkedList | Linked List | -| java.util.Queue | Queue | -| java.util.Stack | Stack | -| java.util.Vector | Vector | -| java.util.Collection | Bag | - -Collections can be used on supported Java types, other POJOs and enums. - -### Other collections/containers - -Java8 supports all kinds of collections, even maps and Java8 specific container such as Optional. - ### Custom types in compile-time databinding -Types without builtin mapping can be supported in three ways: +Types without built-in mapping can be supported in three ways: - * by implementing `JsonObject` and appropriate `JSON_READER` * by defining custom conversion class and annotating it with `@JsonConverter` * by defining custom conversion class and referencing it from property with converter through `@JsonAttribute` + * by implementing `JsonObject` and appropriate `JSON_READER` -Custom converter for `java.util.Date` can be found in [example project](examples/MavenJava6/src/main/java/com/dslplatform/maven/Example.java#L116) +Custom converter for `java.time.LocalTime` can be found in [example project](examples/MavenJava8/src/main/java/com/dslplatform/maven/Example.java#L182) Annotation processor will check if custom type implementations have appropriate signatures. Converter for `java.util.ArrayList` can be found in [same example project](examples/MavenJava6/src/main/java/com/dslplatform/maven/Example.java#L38) @@ -165,6 +93,17 @@ Converter for `java.util.ArrayList` can be found in [same example project](examp All of the above custom type examples work out-of-the-box in Java8 version of the library. +Custom converter for Java8 is a class with 2 static methods, eg: + + public static abstract class LocalTimeConverter { + public static LocalTime read(JsonReader reader) throws IOException { + return LocalTime.parse(reader.readSimpleString()); + } + public static void write(JsonWriter writer, LocalTime value) { + writer.writeString(value.toString()); + } + } + ### @JsonAttribute features @@ -174,11 +113,11 @@ DSL-JSON property annotation supports several customizations/features: * alternativeNames - different incoming JSON attributes can be mapped into appropriate property. This can be used for simple features such as casing or for complex features such as model evolution * ignore - don't serialize specific property into JSON * nullable - tell compiler that this property can't be null. Compiler can remove some checks in that case for minuscule performance boost - * mandatory - mandatory properties must exists in JSON. Even in omit-defaults mode. If property is not found, `IOException` will be thrown + * mandatory - mandatory properties must exist in JSON. Even in omit-defaults mode. If property is not found, `IOException` will be thrown * index - defines index order used during serialization or can be used for array format * hashMatch - DSL-JSON matches properties by hash values. If this option is turned off exact comparison will be performed which will add minor deserialization overhead, but invalid properties with same hash names will not be deserialized into "wrong" property. In case when model contains multiple properties with same hash values, compiler will inject exact comparison by default, regardless of this option value. * converter - custom conversion per property. Can be used for formatting or any other custom handling of JSON processing for specific property - * typeSignature - disable inclusion of $type during abstract type serialization. By default abstract type will include additional information which is required for correct deserialization. Abstract types can be deserialized into a concreted type by defining `deserializeAs` on `@CompiledJson` which allows the removal of $type during both serialization and deserialization + * typeSignature - disable inclusion of $type during abstract type serialization. By default, abstract type will include additional information which is required for correct deserialization. Abstract types can be deserialized into a concreted type by defining `deserializeAs` on `@CompiledJson` which allows the removal of $type during both serialization and deserialization ### @JsonValue enum feature @@ -199,7 +138,9 @@ For existing classes which can't be modified with `@JsonAttribute` alternative e During translation from Java objects into DSL schema, existing type system nullability rules are followed. With the help of non-null annotations, hints can be introduced to work around some Java nullability type system limitations. -List of supported non-null annotations can be found in [processor source code](processor/src/main/java/com/dslplatform/json/CompiledJsonProcessor.java#L88) +List of supported non-null annotations can be found in [processor source code](java8/src/main/java/com/dslplatform/json/processor/CompiledJsonAnnotationProcessor.java#L68) + +Nullable annotations can be disabled via configuration parameter `dsljson.nullable=false`. This might be useful if you want to handle null checks after deserialization. #### Property aliases @@ -217,11 +158,21 @@ Existing bean properties and fields can be ignored using one of the supported an * com.fasterxml.jackson.annotation.JsonIgnore * org.codehaus.jackson.annotate.JsonIgnore -Ignored properties will not be translated into DSL schema. +#### Required/mandatory properties + +Jackson `required = true` can be used to fail if property is missing in JSON. +DSL-JSON has distinct behavior for required (non-null) and mandatory specification. + +Mandatory means that property must be present in JSON. If it's not present an error will be thrown during deserialization. +Non-null means that property cannot be null. But most types have default values and if omitted will assume this default value, eg: -#### Required properties + * String - empty string + * Number - 0 + * Boolean - false + * etc... -Jackson `required = true` can be used to fail if property is missing in JSON: +Some types don't have default value and for them, non-null also implies mandatory. +This behavior can be controlled via several options. ## Serialization modes @@ -246,17 +197,9 @@ Reference benchmark (built by library authors): * [.NET vs JVM JSON](https://github.com/ngs-doo/json-benchmark) - comparison of various JSON libraries -## Dependencies - -Core library (with analysis processor) and DSL Platform annotation processor targets Java6. -Java8 library includes runtime analysis, reflection support, annotation processor and Java8 specific types. When Java8 annotation processor is used Mono/.NET doesn't need to be present on the system. -Android can use Java8 version of the library even on older versions due to lazy loading of types which avoids loading types Android does not support. - -If not sure which version to use, use Java8 version of the library with annotation processor. - ## Runtime analysis -Java8 library has builtin runtime analysis support, so library can be used even without compile time databinding +Java8 library has built-in runtime analysis support, so library can be used even without compile time databinding or it can just add additional runtime support alongside compile-time databinding (default behavior). Runtime analysis is required for some features such as generics which are not known at compile time. Runtime analysis works by lazy type resolution from registered converters, eg: @@ -268,7 +211,7 @@ Runtime analysis works by lazy type resolution from registered converters, eg: ### Reusing reader/writer. -`JsonWriter` It has two modes of operations: +`JsonWriter` has two modes of operations: * populating the entire output into `byte[]` * targeting output stream and flushing local `byte[]` to target output stream @@ -287,7 +230,7 @@ When calling `DslJson` deserialize methods often exists in two flavors: * with `byte[]` argument, in which case a new `JsonReader` will be created, but for best performance `byte[]` should be reused * without `byte[]` argument in which case thread local reader will be reused - + For small messages it's better to use `byte[]` API. When reader is used directly it should be always created via `newReader` method on `DslJson` instance. DslJson json = ... // always reuse @@ -344,38 +287,37 @@ To avoid some Java/Scala conversion issues it's best to use Scala specific API v For SBT dependency can be added as: - libraryDependencies += "com.dslplatform" %% "dsl-json-scala" % "1.9.9" + libraryDependencies += "com.dslplatform" %% "dsl-json-scala" % "1.10.0" ### Kotlin support Kotlin has excellent Java interoperability, so annotation processor can be used as-is. When used with Gradle, configuration can be done via: - apply plugin: 'kotlin-kapt' + plugins { + kotlin("kapt") version "1.8.0" + } dependencies { - compile "com.dslplatform:dsl-json-java8:1.9.9" - kapt "com.dslplatform:dsl-json-java8:1.9.9" + implementation("com.dslplatform:dsl-json-java8:1.10.0") + kapt("com.dslplatform:dsl-json-java8:1.10.0") } ## FAQ ***Q***: What is `TContext` in `DslJson` and what should I use for it? ***A***: Generic `TContext` is used for library specialization. Use `DslJson` when you don't need it and just provide `null` for it. + + ***Q***: How do I use DSL-JSON with String? + ***A***: Don't do that. You should prefer Streams or byte[] instead. If you really, really need it, it is mentioned in this README. ***Q***: Why is DSL-JSON faster than others? - ***A***: Almost zero allocations. Works on byte level. Better algorithms for conversion from `byte[]` -> type and vice-versa. Minimized unexpected branching. Reflection version is comparable with Jackson performance. Extra difference comes from compile-time databinding. - - ***Q***: DslJson is failing with unable to resolve reader/writer. What does it mean? - ***A***: During startup DslJson loads services through `ServiceLoader`. For this to work `META-INF/services/com.dslplatform.json.Configuration` must exist with the content of `dsl_json_Annotation_Processor_External_Serialization` or `dsl_json.json.ExternalSerialization` which is the class crated during compilation step. Make sure you've referenced processor library (which is responsible for setting up readers/writers during compilation) and double check if annotation processor is running. Refer to [example projects](examples) for how to set up environment. As of v1.8.0 Java8 version of the library avoids this issue since services are not used by default anymore in favor of named based convention. Eclipse is known to create problems with annotation processor since it requires manual setup (instead of using pom.xml setup). For Eclipse the best workaround is to build with Maven instead or relying on its build tools. - - ***Q***: Maven/Gradle are failing during compilation with `@CompiledJson` when I'm using DSL Platform annotation processor. What can I do about it? - ***A***: If Mono/.NET is available it *should* work out-of-the-box. But if some strange issue occurs, detailed log can be enabled to see what is causing the issue. Log is disabled by default, since some Gradle setups fail if something is logged during compilation. Log can be enabled with `dsljson.loglevel` [processor option](examples/MavenJava6/pom.xml#L35) + ***A***: Almost zero allocations. Works on byte level. Better algorithms for conversion from `byte[]` -> type and vice-versa. Minimized unexpected branching. Reflection version is "comparable" with Jackson performance. Extra difference comes from compile-time databinding. - ***Q***: DSL Platform annotation processor checks for new DSL compiler version on every compilation. How can I disable that? - ***A***: If you specify custom `dsljson.compiler` processor option or put `dsl-compiler.exe` in project root it will use that one and will not check online for updates + ***Q***: I get compilation error when annotation processor runs. What can I do? + ***A***: Common error is missing dependency on Java 9+ for annotation marker. You can add such dependency on configure compiler arguments to exclude it via `dsljson.generatedmarker`. Otherwise, it's best to inspect the generated code, look if there is some configuration error, like referencing class without sufficient visibility. If there is nothing wrong with the setup, there might be a bug with the DSL-JSON annotation processor in which case it would be helpful to provide a minimal reproducible - ***Q***: I get compilation error when annotation procesor runs. What can I do? - ***A***: Common error is missing dependency on Java 9+ for annotation marker. You can add such dependency on configure compiler arguments to exclude it via `dsljson.generatedmarker`. Otherwise its best to inspect the generated code, look if there is some configuration error, like referencing class without sufficient visibility. If there is nothing wrong with the setup, there might be a bug with the DSL-JSON annotation processor in which case it would be helpful to provide a minimal reproducible + ***Q***: I'm trying to compile library but its failing? + ***A***: Its most likely due to Mono/.NET dependency in processor tests. Please refer to [DSL README](DSL.md). - ***Q***: What is this DSL Platform? - ***A***: DSL Platform is a proprietary compiler written in C#. Since v1.7.0 DSL Platform is no longer required to create compile-time databinding. Compiler is free to use, but access to source code is licensed. If you want access to the compiler or need performance consulting [let us know](https://dsl-platform.com) + ***Q***: Can you please help me out with...? + ***A***: There is only so many hours in a day. If you need to satisfy legalese for your company, contact me for a [support contract](mailto:rikard@ngs.hr?subject=DSL-JSON). diff --git a/examples/AndroidDsl/app/build.gradle b/examples/AndroidDsl/app/build.gradle index 6fdf5b34..e34bc644 100644 --- a/examples/AndroidDsl/app/build.gradle +++ b/examples/AndroidDsl/app/build.gradle @@ -26,6 +26,6 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:28.0.0' - implementation 'com.dslplatform:dsl-json:1.9.9' - annotationProcessor 'com.dslplatform:dsl-json-processor:1.9.9' + implementation 'com.dslplatform:dsl-json:1.10.0' + annotationProcessor 'com.dslplatform:dsl-json-processor:1.10.0' } diff --git a/examples/AndroidDsl/build.gradle b/examples/AndroidDsl/build.gradle index 20a97dc8..cd72a458 100644 --- a/examples/AndroidDsl/build.gradle +++ b/examples/AndroidDsl/build.gradle @@ -2,11 +2,11 @@ buildscript { repositories { - jcenter() + mavenCentral() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.4' + classpath 'com.android.tools.build:gradle:3.6.4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -15,7 +15,7 @@ buildscript { allprojects { repositories { - jcenter() + mavenCentral() google() mavenLocal() } diff --git a/examples/AndroidJava/app/build.gradle b/examples/AndroidJava/app/build.gradle index beec986b..38293bc7 100644 --- a/examples/AndroidJava/app/build.gradle +++ b/examples/AndroidJava/app/build.gradle @@ -39,11 +39,11 @@ dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' //java8 version can work via desugar - implementation 'com.dslplatform:dsl-json-java8:1.9.9' + implementation 'com.dslplatform:dsl-json-java8:1.10.0' //using alternative to java8 time api - implementation 'com.dslplatform:dsl-json-threetenbp:1.9.9' + implementation 'com.dslplatform:dsl-json-threetenbp:1.10.0' //invoke the compile time databinding - annotationProcessor 'com.dslplatform:dsl-json-java8:1.9.9' + annotationProcessor 'com.dslplatform:dsl-json-java8:1.10.0' //just satisfy Jsonb provided dependency api 'javax.json.bind:javax.json.bind-api:1.0' } diff --git a/examples/AndroidJava/app/src/main/java/dslplatform/com/androidexample/MainActivity.java b/examples/AndroidJava/app/src/main/java/dslplatform/com/androidexample/MainActivity.java index 3d03039b..efd8c98c 100644 --- a/examples/AndroidJava/app/src/main/java/dslplatform/com/androidexample/MainActivity.java +++ b/examples/AndroidJava/app/src/main/java/dslplatform/com/androidexample/MainActivity.java @@ -189,33 +189,33 @@ public JsonObjectReference deserialize(JsonReader reader) throws IOException { }; } public static abstract class FormatDecimal2 { - public static final JsonReader.ReadObject JSON_READER = reader -> { + public static BigDecimal read(JsonReader reader) throws IOException { if (reader.wasNull()) return null; return NumberConverter.deserializeDecimal(reader).setScale(2); - }; - public static final JsonWriter.WriteObject JSON_WRITER = (writer, value) -> { + } + public static void write(JsonWriter writer, BigDecimal value) { if (value == null) { writer.writeNull(); } else { NumberConverter.serializeNullable(value.setScale(2), writer); } - }; + } } } @JsonConverter(target = LocalTime.class) public static abstract class LocalTimeConverter { - public static final JsonReader.ReadObject JSON_READER = reader -> { + public static LocalTime read(JsonReader reader) throws IOException { if (reader.wasNull()) return null; return LocalTime.parse(reader.readSimpleString()); - }; - public static final JsonWriter.WriteObject JSON_WRITER = (writer, value) -> { + } + public static void write(JsonWriter writer, LocalTime value) { if (value == null) { writer.writeNull(); } else { writer.writeString(value.toString()); } - }; + } } @Override diff --git a/examples/AndroidJava/build.gradle b/examples/AndroidJava/build.gradle index f7c24a7f..cd72a458 100644 --- a/examples/AndroidJava/build.gradle +++ b/examples/AndroidJava/build.gradle @@ -2,11 +2,11 @@ buildscript { repositories { - jcenter() + mavenCentral() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:3.6.4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -15,7 +15,7 @@ buildscript { allprojects { repositories { - jcenter() + mavenCentral() google() mavenLocal() } diff --git a/examples/AndroidKotlin/.gitignore b/examples/AndroidKotlin/.gitignore index a4c78382..aa724b77 100644 --- a/examples/AndroidKotlin/.gitignore +++ b/examples/AndroidKotlin/.gitignore @@ -1,9 +1,15 @@ -*.iml -.gradle -/local.properties -/.idea/workspace.xml -/.idea/libraries -.DS_Store -/build -/captures -.externalNativeBuild +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/examples/AndroidKotlin/app/.gitignore b/examples/AndroidKotlin/app/.gitignore index 3543521e..42afabfd 100644 --- a/examples/AndroidKotlin/app/.gitignore +++ b/examples/AndroidKotlin/app/.gitignore @@ -1 +1 @@ -/build +/build \ No newline at end of file diff --git a/examples/AndroidKotlin/app/build.gradle b/examples/AndroidKotlin/app/build.gradle index 01452073..9cec3c8a 100644 --- a/examples/AndroidKotlin/app/build.gradle +++ b/examples/AndroidKotlin/app/build.gradle @@ -1,43 +1,59 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'kotlin-kapt' - -android { - compileSdkVersion 28 - buildToolsVersion "28.0.3" - defaultConfig { - applicationId "com.dslplatform.androidkotlin" - minSdkVersion 26 - targetSdkVersion 28 - versionCode 1 - versionName "1.0" - - javaCompileOptions { - annotationProcessorOptions { - //disable @javax.annotation.Generated("dsl_json") - arguments = ['dsljson.generatedmarker': ''] - } - } - } - buildTypes { - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - lintOptions { - abortOnError false - } -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'com.android.support:appcompat-v7:28.0.0' - implementation 'com.android.support.constraint:constraint-layout:2.0.2' - - implementation 'com.dslplatform:dsl-json-java8:1.9.9' - kapt 'com.dslplatform:dsl-json-java8:1.9.9' - annotationProcessor 'com.dslplatform:dsl-json-java8:1.9.9' -} +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.kotlin.kapt' +} + +android { + namespace 'com.dslplatform.androidkotlin' + compileSdk 32 + + defaultConfig { + applicationId "com.dslplatform.androidkotlin" + minSdk 28 + targetSdk 32 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + viewBinding true + } +} + +kapt { + arguments { + //disable @javax.annotation.Generated("dsl_json") + arg("dsljson.generatedmarker", "") + } +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1' + implementation 'androidx.navigation:navigation-ui-ktx:2.4.1' + implementation 'com.dslplatform:dsl-json-java8:1.10.0' + kapt 'com.dslplatform:dsl-json-java8:1.10.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/examples/AndroidKotlin/app/proguard-rules.pro b/examples/AndroidKotlin/app/proguard-rules.pro index c1c5ce6d..32759462 100644 --- a/examples/AndroidKotlin/app/proguard-rules.pro +++ b/examples/AndroidKotlin/app/proguard-rules.pro @@ -1,30 +1,30 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile - -# DSL-JSON --keep class dsl_json.** { *; } --keep class com.dslplatform.androidkotlin.** { *; } --keep class **_DslJsonConverter { *; } --dontwarn com.dslplatform.json.** --dontwarn dsl_json.java.sql.** --keep @com.dslplatform.json.CompiledJson class * +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +# DSL-JSON +-keep class dsl_json.** { *; } +-keep class com.dslplatform.androidkotlin.** { *; } +-keep class **_DslJsonConverter { *; } +-dontwarn com.dslplatform.json.** +-dontwarn dsl_json.java.sql.** +-keep @com.dslplatform.json.CompiledJson class * -keep @com.dslplatform.json.JsonConverter class * \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/androidTest/java/com/dslplatform/androidkotlin/ExampleInstrumentedTest.kt b/examples/AndroidKotlin/app/src/androidTest/java/com/dslplatform/androidkotlin/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..75d6cd86 --- /dev/null +++ b/examples/AndroidKotlin/app/src/androidTest/java/com/dslplatform/androidkotlin/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.dslplatform.androidkotlin + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.dslplatform.androidkotlin", appContext.packageName) + } +} \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/AndroidManifest.xml b/examples/AndroidKotlin/app/src/main/AndroidManifest.xml index 7a8a067c..9b18705d 100644 --- a/examples/AndroidKotlin/app/src/main/AndroidManifest.xml +++ b/examples/AndroidKotlin/app/src/main/AndroidManifest.xml @@ -1,20 +1,31 @@ + xmlns:tools="http://schemas.android.com/tools"> - + android:theme="@style/Theme.AndroidKotlin" + tools:targetApi="31"> + + + diff --git a/examples/AndroidKotlin/app/src/main/java/com/dslplatform/androidkotlin/DSL.kt b/examples/AndroidKotlin/app/src/main/java/com/dslplatform/androidkotlin/DSL.kt deleted file mode 100644 index 30334399..00000000 --- a/examples/AndroidKotlin/app/src/main/java/com/dslplatform/androidkotlin/DSL.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.dslplatform.androidkotlin - -import com.dslplatform.json.DslJson -import com.dslplatform.json.runtime.Settings - -object DSL { - private var json: DslJson? = null - fun JSON(): DslJson { - var tmp = json - if (tmp != null) return tmp - //during initialization ServiceLoader.load should pick up services registered into META-INF/services - //this doesn't really work on Android so DslJson will fallback to default generated class name - //"dsl_json_Annotation_Processor_External_Serialization" and try to initialize it manually - tmp = DslJson(Settings.withRuntime().includeServiceLoader().allowArrayFormat(true)) - json = tmp - return tmp - } -} \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/java/com/dslplatform/androidkotlin/DslJson.kt b/examples/AndroidKotlin/app/src/main/java/com/dslplatform/androidkotlin/DslJson.kt new file mode 100644 index 00000000..04b5c1aa --- /dev/null +++ b/examples/AndroidKotlin/app/src/main/java/com/dslplatform/androidkotlin/DslJson.kt @@ -0,0 +1,177 @@ +package com.dslplatform.androidkotlin + +import com.dslplatform.json.* +import com.dslplatform.json.runtime.MapAnalyzer +import com.dslplatform.json.runtime.Settings +import java.math.BigDecimal +import java.time.LocalTime +import java.util.* +import kotlin.collections.ArrayList + +object DSL { + private var json: DslJson? = null + fun JSON(): DslJson { + var tmp = json + if (tmp != null) return tmp + //during initialization ServiceLoader.load should pick up services registered into META-INF/services + //this doesn't really work on Android so DslJson will fallback to default generated class name + //"dsl_json_Annotation_Processor_External_Serialization" and try to initialize it manually + tmp = DslJson(Settings.withRuntime().includeServiceLoader().allowArrayFormat(true)) + json = tmp + return tmp + } +} + +@CompiledJson(onUnknown = CompiledJson.Behavior.IGNORE) //ignore unknown properties (default for objects). to disallow unknown properties in JSON set it to FAIL which will result in exception instead +data class Model ( //data classes are supported the same way as immutable objects in Java + val string: String,//not null annotations indicate that field can't be null + val integers: List?, + @JsonAttribute(name = "guids") //use alternative name in JSON + val uuids: Array?, + val decimals: Set?, + val longs: Vector, + @JsonAttribute(hashMatch = false) // exact name match can be forced, otherwise hash value will be used for matching + val number: Int, + @JsonAttribute(alternativeNames = arrayOf("old_nested", "old_nested2")) //several JSON attribute names can be deserialized into this field + val nested: List, + @JsonAttribute(typeSignature = CompiledJson.TypeSignature.EXCLUDE) //$type attribute can be excluded from resulting JSON + val abs: Abstract?,//abstract classes or interfaces can be used which will also include $type attribute in JSON by default + val absList: List, + val iface: Interface?,//interfaces without deserializedAs will also include $type attribute in JSON by default + val inheritance: ParentClass?, + @JsonAttribute(mandatory = true)// mandatory adds check if property exist in JSON and will serialize it even in omit-defaults mode + val states: List?, + val jsonObject: JsonObjectReference?, //object implementing JsonObject manage their own conversion. They must start with '{' + val jsonObjects: List?, + val time: LocalTime?, //LocalTime is not supported, but with the use of converter it will work + val times: List?, //even containers with unsupported type will be resolved + @JsonAttribute(converter = FormatDecimal2::class) + val decimal2: BigDecimal, //custom formatting can be implemented with per property converters + val intList: ArrayList, //most collections are supported through runtime converters + //since this signature has an unknown part (Object), it must be whitelisted + //This can be done via appropriate converter, by registering @JsonConverter for the specified type + //or by enabling support for unknown types in the annotation processor + @JsonAttribute(converter = MapAnalyzer.Runtime::class) + val map: Map, + val person: Person? //immutable objects are supported via builder pattern +) + +@CompiledJson(formats = arrayOf(CompiledJson.Format.ARRAY, CompiledJson.Format.OBJECT)) +data class Person(val firstName: String, val lastName: String, val age: Int) + +//explicitly referenced classes don't require @CompiledJson annotation +class Nested { + var x: Long = 0 + var y: Double = 0.toDouble() + var z: Float = 0.toFloat() +} + +@CompiledJson(deserializeAs = Concrete::class)//without deserializeAs deserializing Abstract would fails since it doesn't contain a $type due to it's exclusion in the above configuration +abstract class Abstract { + var x: Int = 0 +} + +//since this class is not explicitly referenced, but it's an extension of the abstract class used as a property +//it needs to be decorated with annotation +@CompiledJson +class Concrete : Abstract() { + var y: Long = 0 +} + +interface Interface { + fun x(v: Int) + fun x(): Int +} + +@CompiledJson(name = "custom-name")//by default class name will be used for $type attribute +class WithCustomCtor : Interface { + private var x: Int = 0 + var y: Int = 0 + + constructor(x: Int) { + this.x = x + this.y = x + } + + @CompiledJson + constructor(x: Int, y: Int) { + this.x = x + this.y = y + } + + override fun x(v: Int) { + x = v + } + + override fun x(): Int { + return x + } +} + +open class BaseClass { + var a: Int = 0 +} + +class ParentClass : BaseClass() { + var b: Long = 0 +} + +enum class State private constructor(private val value: Int) { + LOW(0), + MID(1), + HI(2) +} + +data class JsonObjectReference(val x: Int, val s: String) : JsonObject { + + override fun serialize(writer: JsonWriter, minimal: Boolean) { + writer.writeAscii("{\"x\":") + NumberConverter.serialize(x, writer) + writer.writeAscii(",\"s\":") + StringConverter.serialize(s, writer) + writer.writeAscii("}") + } + + companion object { + + val JSON_READER = object:JsonReader.ReadJsonObject { + override fun deserialize(reader: JsonReader<*>): JsonObjectReference { + reader.fillName()//"x" + reader.getNextToken()//start number + val x = NumberConverter.deserializeInt(reader) + reader.getNextToken()//, + reader.getNextToken()//start name + reader.fillName()//"s" + reader.getNextToken()//start string + val s = StringConverter.deserialize(reader) + reader.getNextToken()//} + return JsonObjectReference(x, s) + } + } + } +} + +@JsonConverter(target = LocalTime::class) +object LocalTimeConverter { + fun read(reader: JsonReader<*>): LocalTime? { + if (reader.wasNull()) return null + return LocalTime.parse(reader.readSimpleString()) + } + fun write(writer: JsonWriter, value: LocalTime?) { + if (value == null) { + writer.writeNull() + } else { + writer.writeString(value.toString()) + } + } +} + +object FormatDecimal2 { + fun read(reader: JsonReader<*>): BigDecimal { + return NumberConverter.deserializeDecimal(reader).setScale(2) + } + + fun write(writer: JsonWriter, value: BigDecimal) { + NumberConverter.serializeNullable(value.setScale(2), writer) + } +} \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/java/com/dslplatform/androidkotlin/MainActivity.kt b/examples/AndroidKotlin/app/src/main/java/com/dslplatform/androidkotlin/MainActivity.kt index 6306e16c..c846530e 100644 --- a/examples/AndroidKotlin/app/src/main/java/com/dslplatform/androidkotlin/MainActivity.kt +++ b/examples/AndroidKotlin/app/src/main/java/com/dslplatform/androidkotlin/MainActivity.kt @@ -1,225 +1,97 @@ -package com.dslplatform.androidkotlin - -import android.support.v7.app.AppCompatActivity -import android.os.Bundle -import android.widget.TextView -import com.dslplatform.json.* -import com.dslplatform.json.runtime.MapAnalyzer -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.time.LocalTime -import java.math.BigDecimal -import java.util.* - -class MainActivity : AppCompatActivity() { - - @CompiledJson(onUnknown = CompiledJson.Behavior.IGNORE) //ignore unknown properties (default for objects). to disallow unknown properties in JSON set it to FAIL which will result in exception instead - data class Model ( //data classes are supported the same way as immutable objects in Java - val string: String,//not null annotations indicate that field can't be null - val integers: List?, - @JsonAttribute(name = "guids") //use alternative name in JSON - val uuids: Array?, - val decimals: Set?, - val longs: Vector, - @JsonAttribute(hashMatch = false) // exact name match can be forced, otherwise hash value will be used for matching - val number: Int, - @JsonAttribute(alternativeNames = arrayOf("old_nested", "old_nested2")) //several JSON attribute names can be deserialized into this field - val nested: List, - @JsonAttribute(typeSignature = CompiledJson.TypeSignature.EXCLUDE) //$type attribute can be excluded from resulting JSON - val abs: Abstract?,//abstract classes or interfaces can be used which will also include $type attribute in JSON by default - val absList: List, - val iface: Interface?,//interfaces without deserializedAs will also include $type attribute in JSON by default - val inheritance: ParentClass?, - @JsonAttribute(mandatory = true)// mandatory adds check if property exist in JSON and will serialize it even in omit-defaults mode - val states: List?, - val jsonObject: JsonObjectReference?, //object implementing JsonObject manage their own conversion. They must start with '{' - val jsonObjects: List?, - val time: LocalTime?, //LocalTime is not supported, but with the use of converter it will work - val times: List?, //even containers with unsupported type will be resolved - @JsonAttribute(converter = FormatDecimal2::class) - val decimal2: BigDecimal, //custom formatting can be implemented with per property converters - val intList: ArrayList, //most collections are supported through runtime converters - //since this signature has an unknown part (Object), it must be whitelisted - //This can be done via appropriate converter, by registering @JsonConverter for the specified type - //or by enabling support for unknown types in the annotation processor - @JsonAttribute(converter = MapAnalyzer.Runtime::class) - val map: Map, - val person: Person? //immutable objects are supported via builder pattern - ) - - //explicitly referenced classes don't require @CompiledJson annotation - class Nested { - var x: Long = 0 - var y: Double = 0.toDouble() - var z: Float = 0.toFloat() - } - - @CompiledJson(deserializeAs = Concrete::class)//without deserializeAs deserializing Abstract would fails since it doesn't contain a $type due to it's exclusion in the above configuration - abstract class Abstract { - var x: Int = 0 - } - - //since this class is not explicitly referenced, but it's an extension of the abstract class used as a property - //it needs to be decorated with annotation - @CompiledJson - class Concrete : Abstract() { - var y: Long = 0 - } - - interface Interface { - fun x(v: Int) - fun x(): Int - } - - @CompiledJson(name = "custom-name")//by default class name will be used for $type attribute - class WithCustomCtor : Interface { - private var x: Int = 0 - var y: Int = 0 - - constructor(x: Int) { - this.x = x - this.y = x - } - - @CompiledJson - constructor(x: Int, y: Int) { - this.x = x - this.y = y - } - - override fun x(v: Int) { - x = v - } - - override fun x(): Int { - return x - } - } - - open class BaseClass { - var a: Int = 0 - } - - class ParentClass : BaseClass() { - var b: Long = 0 - } - - enum class State private constructor(private val value: Int) { - LOW(0), - MID(1), - HI(2) - } - - data class JsonObjectReference(val x: Int, val s: String) : JsonObject { - - override fun serialize(writer: JsonWriter, minimal: Boolean) { - writer.writeAscii("{\"x\":") - NumberConverter.serialize(x, writer) - writer.writeAscii(",\"s\":") - StringConverter.serialize(s, writer) - writer.writeAscii("}") - } - - companion object { - - val JSON_READER = object:JsonReader.ReadJsonObject { - override fun deserialize(reader: JsonReader<*>): JsonObjectReference { - reader.fillName()//"x" - reader.getNextToken()//start number - val x = NumberConverter.deserializeInt(reader) - reader.getNextToken()//, - reader.getNextToken()//start name - reader.fillName()//"s" - reader.getNextToken()//start string - val s = StringConverter.deserialize(reader) - reader.getNextToken()//} - return JsonObjectReference(x, s) - } - } - } - } - - @JsonConverter(target = LocalTime::class) - object LocalTimeConverter { - val JSON_READER = object:JsonReader.ReadObject { - override fun read(reader: JsonReader<*>): LocalTime? { - if (reader.wasNull()) return null - return LocalTime.parse(reader.readSimpleString()) - } - } - val JSON_WRITER = object:JsonWriter.WriteObject { - override fun write(writer: JsonWriter, value: LocalTime?) { - if (value == null) { - writer.writeNull() - } else { - writer.writeString(value.toString()) - } - } - } - } - - object FormatDecimal2 { - val JSON_READER = object:JsonReader.ReadObject { - override fun read(reader: JsonReader<*>): BigDecimal { - return NumberConverter.deserializeDecimal(reader).setScale(2) - } - } - val JSON_WRITER = object:JsonWriter.WriteObject { - override fun write(writer: JsonWriter, value: BigDecimal) { - NumberConverter.serializeNullable(value.setScale(2), writer) - } - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - - val dslJson = DSL.JSON() - - val concrete = Concrete() - concrete.x = 11 - concrete.y = 23 - val parent = ParentClass() - parent.a = 5 - parent.b = 6 - - val instance = Model( - string = "Hello World!", - number = 42, - integers = listOf(1, 2, 3), - decimals = HashSet(listOf(BigDecimal.ONE, BigDecimal.ZERO)), - uuids = arrayOf(UUID(1L, 2L), UUID(3L, 4L)), - longs = Vector(listOf(1L, 2L)), - nested = listOf(Nested(), null), - inheritance = parent, - iface = WithCustomCtor(5, 6), - person = Person("first name", "last name", 35), - states = Arrays.asList(State.HI, State.LOW), - jsonObject = JsonObjectReference(43, "abcd"), - jsonObjects = Collections.singletonList(JsonObjectReference(34, "dcba")), - time = LocalTime.of(12, 15), - times = listOf(null, LocalTime.of(8, 16)), - abs = concrete, - absList = listOf(concrete, null, concrete), - decimal2 = BigDecimal.TEN, - intList = ArrayList(listOf(123, 456)), - map = mapOf("abc" to 678, "array" to arrayOf(2, 4, 8)) - ) - - val tv = findViewById(R.id.tvHello) - try { - val os = ByteArrayOutputStream() - //serialize into stream - dslJson.serialize(instance, os) - - val stream = ByteArrayInputStream(os.toByteArray()) - //deserialized using stream API - val result = dslJson.deserialize(Model::class.java, stream) - tv.setText(result.string) - } catch (ex: IOException) { - tv.setText(ex.message) - } - } -} +package com.dslplatform.androidkotlin + +import android.os.Bundle +import com.google.android.material.snackbar.Snackbar +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.findNavController +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.navigateUp +import androidx.navigation.ui.setupActionBarWithNavController +import android.view.Menu +import android.view.MenuItem +import android.widget.TextView +import com.dslplatform.androidkotlin.databinding.ActivityMainBinding +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.math.BigDecimal +import java.time.LocalTime +import java.util.* +import kotlin.collections.ArrayList +import kotlin.collections.HashSet + +class MainActivity : AppCompatActivity() { + + private lateinit var binding: ActivityMainBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + setSupportActionBar(binding.toolbar) + + val dslJson = DSL.JSON() + + val concrete = Concrete() + concrete.x = 11 + concrete.y = 23 + val parent = ParentClass() + parent.a = 5 + parent.b = 6 + + val instance = Model( + string = "Hello World!", + number = 42, + integers = listOf(1, 2, 3), + decimals = HashSet(listOf(BigDecimal.ONE, BigDecimal.ZERO)), + uuids = arrayOf(UUID(1L, 2L), UUID(3L, 4L)), + longs = Vector(listOf(1L, 2L)), + nested = listOf(Nested(), null), + inheritance = parent, + iface = WithCustomCtor(5, 6), + person = Person("first name", "last name", 35), + states = Arrays.asList(State.HI, State.LOW), + jsonObject = JsonObjectReference(43, "abcd"), + jsonObjects = Collections.singletonList(JsonObjectReference(34, "dcba")), + time = LocalTime.of(12, 15), + times = listOf(null, LocalTime.of(8, 16)), + abs = concrete, + absList = listOf(concrete, null, concrete), + decimal2 = BigDecimal.TEN, + intList = ArrayList(listOf(123, 456)), + map = mapOf("abc" to 678, "array" to arrayOf(2, 4, 8)) + ) + + val tv = findViewById(R.id.tvHello) + try { + val os = ByteArrayOutputStream() + //serialize into stream + dslJson.serialize(instance, os) + + val stream = ByteArrayInputStream(os.toByteArray()) + //deserialized using stream API + val result = dslJson.deserialize(Model::class.java, stream) + tv.setText(result.string) + } catch (ex: IOException) { + tv.setText(ex.message) + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + // Inflate the menu; this adds items to the action bar if it is present. + menuInflater.inflate(R.menu.menu_main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + return when (item.itemId) { + R.id.action_settings -> true + else -> super.onOptionsItemSelected(item) + } + } +} \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/java/com/dslplatform/androidkotlin/Person.kt b/examples/AndroidKotlin/app/src/main/java/com/dslplatform/androidkotlin/Person.kt deleted file mode 100644 index e5b1085e..00000000 --- a/examples/AndroidKotlin/app/src/main/java/com/dslplatform/androidkotlin/Person.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.dslplatform.androidkotlin - -import com.dslplatform.json.CompiledJson - - -@CompiledJson(formats = arrayOf(CompiledJson.Format.ARRAY, CompiledJson.Format.OBJECT)) -data class Person(val firstName: String, val lastName: String, val age: Int) \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/examples/AndroidKotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml index ddb26ad7..2b068d11 100644 --- a/examples/AndroidKotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ b/examples/AndroidKotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -1,34 +1,30 @@ - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/res/drawable/ic_launcher_background.xml b/examples/AndroidKotlin/app/src/main/res/drawable/ic_launcher_background.xml index 3a37cf6d..07d5da9c 100644 --- a/examples/AndroidKotlin/app/src/main/res/drawable/ic_launcher_background.xml +++ b/examples/AndroidKotlin/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,170 +1,170 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/AndroidKotlin/app/src/main/res/layout/activity_main.xml b/examples/AndroidKotlin/app/src/main/res/layout/activity_main.xml index da53d9ac..362f1f8a 100644 --- a/examples/AndroidKotlin/app/src/main/res/layout/activity_main.xml +++ b/examples/AndroidKotlin/app/src/main/res/layout/activity_main.xml @@ -1,18 +1,25 @@ - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/res/layout/content_main.xml b/examples/AndroidKotlin/app/src/main/res/layout/content_main.xml new file mode 100644 index 00000000..311b9b38 --- /dev/null +++ b/examples/AndroidKotlin/app/src/main/res/layout/content_main.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/res/menu/menu_main.xml b/examples/AndroidKotlin/app/src/main/res/menu/menu_main.xml new file mode 100644 index 00000000..6b48861c --- /dev/null +++ b/examples/AndroidKotlin/app/src/main/res/menu/menu_main.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/examples/AndroidKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index a26f6fbc..eca70cfe 100644 --- a/examples/AndroidKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/examples/AndroidKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/examples/AndroidKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index a26f6fbc..eca70cfe 100644 --- a/examples/AndroidKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/examples/AndroidKotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/AndroidKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a2f59082..00000000 Binary files a/examples/AndroidKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/examples/AndroidKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 00000000..c209e78e Binary files /dev/null and b/examples/AndroidKotlin/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/examples/AndroidKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 1b523998..00000000 Binary files a/examples/AndroidKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/examples/AndroidKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 00000000..b2dfe3d1 Binary files /dev/null and b/examples/AndroidKotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/AndroidKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ff10afd6..00000000 Binary files a/examples/AndroidKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/examples/AndroidKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 00000000..4f0f1d64 Binary files /dev/null and b/examples/AndroidKotlin/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/examples/AndroidKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 115a4c76..00000000 Binary files a/examples/AndroidKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/examples/AndroidKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 00000000..62b611da Binary files /dev/null and b/examples/AndroidKotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/AndroidKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index dcd3cd80..00000000 Binary files a/examples/AndroidKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/examples/AndroidKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 00000000..948a3070 Binary files /dev/null and b/examples/AndroidKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/examples/AndroidKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 459ca609..00000000 Binary files a/examples/AndroidKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/examples/AndroidKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..1b9a6956 Binary files /dev/null and b/examples/AndroidKotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/AndroidKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8ca12fe0..00000000 Binary files a/examples/AndroidKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/examples/AndroidKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 00000000..28d4b77f Binary files /dev/null and b/examples/AndroidKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/examples/AndroidKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 8e19b410..00000000 Binary files a/examples/AndroidKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/examples/AndroidKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..9287f508 Binary files /dev/null and b/examples/AndroidKotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/AndroidKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index b824ebdd..00000000 Binary files a/examples/AndroidKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/examples/AndroidKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 00000000..aa7d6427 Binary files /dev/null and b/examples/AndroidKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/examples/AndroidKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 4c19a13c..00000000 Binary files a/examples/AndroidKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/examples/AndroidKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/examples/AndroidKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..9126ae37 Binary files /dev/null and b/examples/AndroidKotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/examples/AndroidKotlin/app/src/main/res/values-land/dimens.xml b/examples/AndroidKotlin/app/src/main/res/values-land/dimens.xml new file mode 100644 index 00000000..22d7f004 --- /dev/null +++ b/examples/AndroidKotlin/app/src/main/res/values-land/dimens.xml @@ -0,0 +1,3 @@ + + 48dp + \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/res/values-night/themes.xml b/examples/AndroidKotlin/app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..a5e606f5 --- /dev/null +++ b/examples/AndroidKotlin/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/res/values-w1240dp/dimens.xml b/examples/AndroidKotlin/app/src/main/res/values-w1240dp/dimens.xml new file mode 100644 index 00000000..d73f4a35 --- /dev/null +++ b/examples/AndroidKotlin/app/src/main/res/values-w1240dp/dimens.xml @@ -0,0 +1,3 @@ + + 200dp + \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/res/values-w600dp/dimens.xml b/examples/AndroidKotlin/app/src/main/res/values-w600dp/dimens.xml new file mode 100644 index 00000000..22d7f004 --- /dev/null +++ b/examples/AndroidKotlin/app/src/main/res/values-w600dp/dimens.xml @@ -0,0 +1,3 @@ + + 48dp + \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/res/values/colors.xml b/examples/AndroidKotlin/app/src/main/res/values/colors.xml index 2a12c47c..f8c6127d 100644 --- a/examples/AndroidKotlin/app/src/main/res/values/colors.xml +++ b/examples/AndroidKotlin/app/src/main/res/values/colors.xml @@ -1,6 +1,10 @@ - - - #3F51B5 - #303F9F - #FF4081 - + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/res/values/dimens.xml b/examples/AndroidKotlin/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000..125df871 --- /dev/null +++ b/examples/AndroidKotlin/app/src/main/res/values/dimens.xml @@ -0,0 +1,3 @@ + + 16dp + \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/res/values/strings.xml b/examples/AndroidKotlin/app/src/main/res/values/strings.xml index a70173da..932c41f4 100644 --- a/examples/AndroidKotlin/app/src/main/res/values/strings.xml +++ b/examples/AndroidKotlin/app/src/main/res/values/strings.xml @@ -1,3 +1,12 @@ - - AndroidKotlin - + + AndroidKotlin + Settings + + First Fragment + Second Fragment + Next + Previous + + Hello first fragment + Hello second fragment. Arg: %1$s + \ No newline at end of file diff --git a/examples/AndroidKotlin/app/src/main/res/values/styles.xml b/examples/AndroidKotlin/app/src/main/res/values/styles.xml deleted file mode 100644 index 6f19b475..00000000 --- a/examples/AndroidKotlin/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/examples/AndroidKotlin/app/src/main/res/values/themes.xml b/examples/AndroidKotlin/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..8548f78c --- /dev/null +++ b/examples/AndroidKotlin/app/src/main/res/values/themes.xml @@ -0,0 +1,25 @@ + + + + + + +