-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #227 from eranl/2.16
Add Android Record Module
- Loading branch information
Showing
17 changed files
with
1,334 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
Module that allows deserialization into records on Android, | ||
where java records are supported through desugaring, | ||
and Jackson's built-in support for records doesn't work, | ||
since the desugared classes have a non-standard super class, | ||
and record component-related reflection methods are missing. | ||
|
||
See [Android Developers Blog article](https://android-developers.googleblog.com/2023/06/records-in-android-studio-flamingo.html) | ||
|
||
Note: this module is a no-op when no Android-desugared records are being deserialized, | ||
so it is safe to use in code shared between Android and non-Android platforms. | ||
|
||
## Usage | ||
|
||
Functionality can be used by registering the module and then just deserializing things | ||
using regular API: | ||
|
||
```java | ||
ObjectMapper mapper = JsonMapper.builder() // or mapper for other dataformats | ||
.addModule(new AndroidRecordModule()) | ||
// add other modules, configure, etc | ||
.build(); | ||
``` | ||
|
||
Maven information for jar is: | ||
|
||
* Group id: `com.fasterxml.jackson.module` | ||
* Artifact id: `jackson-module-android-record` | ||
|
||
## Other | ||
|
||
For Javadocs, Download, see: [Wiki](../../wiki). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<!-- This module was also published with a richer model, Gradle metadata, --> | ||
<!-- which should be used instead. Do not delete the following line which --> | ||
<!-- is to indicate to Gradle or any Gradle module metadata file consumer --> | ||
<!-- that they should prefer consuming it instead. --> | ||
<!-- do_not_remove: published-with-gradle-metadata --> | ||
<parent> | ||
<groupId>com.fasterxml.jackson.module</groupId> | ||
<artifactId>jackson-modules-base</artifactId> | ||
<version>2.16.0-SNAPSHOT</version> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<artifactId>jackson-module-android-record</artifactId> | ||
<name>Jackson module: Android Record Support</name> | ||
<packaging>bundle</packaging> | ||
|
||
<description>Support deserialization into records on Android</description> | ||
<url>https://github.com/FasterXML/jackson-modules-base</url> | ||
|
||
<licenses> | ||
<license> | ||
<name>The Apache Software License, Version 2.0</name> | ||
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url> | ||
<distribution>repo</distribution> | ||
</license> | ||
</licenses> | ||
|
||
<properties> | ||
<!-- Generate PackageVersion.java into this directory. --> | ||
<packageVersion.dir>com/fasterxml/jackson/module/androidrecord</packageVersion.dir> | ||
<packageVersion.package>com.fasterxml.jackson.module.androidrecord</packageVersion.package> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>com.fasterxml.jackson.core</groupId> | ||
<artifactId>jackson-core</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.fasterxml.jackson.core</groupId> | ||
<artifactId>jackson-annotations</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.fasterxml.jackson.core</groupId> | ||
<artifactId>jackson-databind</artifactId> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<inherited>true</inherited> | ||
<configuration> | ||
<compilerArgs> | ||
<!-- Module uses constructor parameter names (and types) to identify the canonical constructor --> | ||
<arg>-parameters</arg> | ||
</compilerArgs> | ||
<fork>true</fork> | ||
<useIncrementalCompilation>true</useIncrementalCompilation> | ||
</configuration> | ||
</plugin> | ||
|
||
<plugin> | ||
<!-- Inherited from oss-base. Generate PackageVersion.java.--> | ||
<groupId>com.google.code.maven-replacer-plugin</groupId> | ||
<artifactId>replacer</artifactId> | ||
<executions> | ||
<execution> | ||
<id>process-packageVersion</id> | ||
<phase>generate-sources</phase> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
|
||
<plugin> | ||
<groupId>org.moditect</groupId> | ||
<artifactId>moditect-maven-plugin</artifactId> | ||
</plugin> | ||
|
||
<plugin> | ||
<groupId>org.codehaus.mojo</groupId> | ||
<artifactId>animal-sniffer-maven-plugin</artifactId> | ||
<version>${version.plugin.animal-sniffer}</version> | ||
<configuration> | ||
<signature> | ||
<groupId>com.toasttab.android</groupId> | ||
<artifactId>gummy-bears-api-${version.android.sdk}</artifactId> | ||
<version>${version.android.sdk.signature}</version> | ||
</signature> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
166 changes: 166 additions & 0 deletions
166
...-record/src/main/java/com/fasterxml/jackson/module/androidrecord/AndroidRecordModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
package com.fasterxml.jackson.module.androidrecord; | ||
|
||
import com.fasterxml.jackson.annotation.JacksonInject; | ||
import com.fasterxml.jackson.databind.AnnotationIntrospector; | ||
import com.fasterxml.jackson.databind.BeanDescription; | ||
import com.fasterxml.jackson.databind.DeserializationConfig; | ||
import com.fasterxml.jackson.databind.JavaType; | ||
import com.fasterxml.jackson.databind.PropertyName; | ||
import com.fasterxml.jackson.databind.cfg.MapperConfig; | ||
import com.fasterxml.jackson.databind.deser.CreatorProperty; | ||
import com.fasterxml.jackson.databind.deser.SettableBeanProperty; | ||
import com.fasterxml.jackson.databind.deser.ValueInstantiator; | ||
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator; | ||
import com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy; | ||
import com.fasterxml.jackson.databind.introspect.AnnotatedClass; | ||
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor; | ||
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; | ||
import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; | ||
import com.fasterxml.jackson.databind.introspect.BasicClassIntrospector; | ||
import com.fasterxml.jackson.databind.introspect.DefaultAccessorNamingStrategy; | ||
import com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector; | ||
import com.fasterxml.jackson.databind.module.SimpleModule; | ||
import com.fasterxml.jackson.databind.util.ClassUtil; | ||
|
||
import java.lang.reflect.Field; | ||
import java.lang.reflect.Modifier; | ||
import java.lang.reflect.Parameter; | ||
import java.lang.reflect.Type; | ||
import java.util.Arrays; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
|
||
/** | ||
* Module that allows (de)serialization of records using the canonical constructor and accessors on Android, | ||
* where java records are supported through desugaring, and Jackson's built-in support for records doesn't work, | ||
* since the desugared classes have a non-standard super class, | ||
* and record component-related reflection methods are missing. | ||
* | ||
* <p> | ||
* See <a href="https://android-developers.googleblog.com/2023/06/records-in-android-studio-flamingo.html"> | ||
* Android Developers Blog article</a> | ||
* | ||
* <p> | ||
* An attempt was made to make this module as consistent with Jackson's built-in support for records as possible, | ||
* but gaps exist when using some of Jackson's advanced mapping features. | ||
* | ||
* <p> | ||
* Note: this module is a no-op when no Android-desugared records are being (de)serialized, | ||
* so it is safe to use in code shared between Android and non-Android platforms. | ||
* | ||
* <p> | ||
* Note: the canonical record constructor is found through matching of parameter names and types with fields. | ||
* Therefore, this module doesn't allow a deserialized desugared record to have a custom constructor | ||
* with the same set of parameter names and types as the canonical one. | ||
* | ||
* @author Eran Leshem | ||
**/ | ||
public class AndroidRecordModule extends SimpleModule { | ||
private static final class AndroidRecordNaming | ||
extends DefaultAccessorNamingStrategy | ||
{ | ||
/** | ||
* Names of actual Record components from definition; auto-detected. | ||
*/ | ||
private final Set<String> _componentNames; | ||
|
||
private AndroidRecordNaming(MapperConfig<?> config, AnnotatedClass forClass) { | ||
super(config, forClass, | ||
// no setters for (immutable) Records: | ||
null, | ||
"get", "is", null); | ||
_componentNames = getDesugaredRecordComponents(forClass.getRawType()).map(Field::getName) | ||
.collect(Collectors.toSet()); | ||
} | ||
|
||
@Override | ||
public String findNameForRegularGetter(AnnotatedMethod am, String name) | ||
{ | ||
// By default, field names are un-prefixed, but verify so that we will not | ||
// include "toString()" or additional custom methods (unless latter are | ||
// annotated for inclusion) | ||
if (_componentNames.contains(name)) { | ||
return name; | ||
} | ||
// but also allow auto-detecting additional getters, if any? | ||
return super.findNameForRegularGetter(am, name); | ||
} | ||
} | ||
|
||
private static class AndroidRecordClassIntrospector extends BasicClassIntrospector { | ||
@Override | ||
protected POJOPropertiesCollector collectProperties(MapperConfig<?> config, JavaType type, MixInResolver r, | ||
boolean forSerialization) { | ||
if (isDesugaredRecordClass(type.getRawClass())) { | ||
AnnotatedClass classDef = _resolveAnnotatedClass(config, type, r); | ||
AccessorNamingStrategy accNaming = new AndroidRecordNaming(config, classDef); | ||
return constructPropertyCollector(config, classDef, type, forSerialization, accNaming); | ||
} | ||
|
||
return super.collectProperties(config, type, r, forSerialization); | ||
} | ||
} | ||
|
||
@Override | ||
public void setupModule(SetupContext context) { | ||
super.setupModule(context); | ||
context.addValueInstantiators(AndroidRecordModule::findValueInstantiator); | ||
context.setClassIntrospector(new AndroidRecordClassIntrospector()); | ||
} | ||
|
||
static boolean isDesugaredRecordClass(Class<?> raw) { | ||
return raw.getSuperclass() != null && raw.getSuperclass().getName().equals("com.android.tools.r8.RecordTag"); | ||
} | ||
|
||
private static ValueInstantiator findValueInstantiator(DeserializationConfig config, BeanDescription beanDesc, | ||
ValueInstantiator defaultInstantiator) { | ||
Class<?> raw = beanDesc.getType().getRawClass(); | ||
if (! defaultInstantiator.canCreateFromObjectWith() && defaultInstantiator instanceof StdValueInstantiator | ||
&& isDesugaredRecordClass(raw)) { | ||
Map<String, Type> components = getDesugaredRecordComponents(raw) | ||
.collect(Collectors.toMap(Field::getName, Field::getGenericType)); | ||
boolean found = false; | ||
for (AnnotatedConstructor constructor: beanDesc.getConstructors()) { | ||
Parameter[] parameters = constructor.getAnnotated().getParameters(); | ||
Map<String, Type> parameterTypes = Arrays.stream(parameters) | ||
.collect(Collectors.toMap(Parameter::getName, Parameter::getParameterizedType)); | ||
if (! parameterTypes.equals(components)) { | ||
continue; | ||
} | ||
|
||
if (found) { | ||
throw new IllegalArgumentException(String.format( | ||
"Multiple constructors match set of components for record %s", raw.getName())); | ||
} | ||
|
||
AnnotationIntrospector intro = config.getAnnotationIntrospector(); | ||
SettableBeanProperty[] properties = new SettableBeanProperty[parameters.length]; | ||
for (int i = 0; i < parameters.length; i++) { | ||
AnnotatedParameter parameter = constructor.getParameter(i); | ||
JacksonInject.Value injectable = intro.findInjectableValue(parameter); | ||
PropertyName name = intro.findNameForDeserialization(parameter); | ||
if (name == null || name.isEmpty()) { | ||
name = PropertyName.construct(parameters[i].getName()); | ||
} | ||
|
||
properties[i] = CreatorProperty.construct(name, parameter.getType(), | ||
null, null, parameter.getAllAnnotations(), parameter, i, injectable, null); | ||
} | ||
|
||
((StdValueInstantiator) defaultInstantiator).configureFromObjectSettings(null, null, null, null, | ||
constructor, properties); | ||
ClassUtil.checkAndFixAccess(constructor.getAnnotated(), false); | ||
found = true; | ||
} | ||
} | ||
|
||
return defaultInstantiator; | ||
} | ||
|
||
private static Stream<Field> getDesugaredRecordComponents(Class<?> raw) { | ||
return Arrays.stream(raw.getDeclaredFields()).filter(field -> ! Modifier.isStatic(field.getModifiers())); | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
...id-record/src/main/java/com/fasterxml/jackson/module/androidrecord/PackageVersion.java.in
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package @package@; | ||
|
||
import com.fasterxml.jackson.core.Version; | ||
import com.fasterxml.jackson.core.Versioned; | ||
import com.fasterxml.jackson.core.util.VersionUtil; | ||
|
||
/** | ||
* Automatically generated from PackageVersion.java.in during | ||
* packageVersion-generate execution of maven-replacer-plugin in | ||
* pom.xml. | ||
*/ | ||
public final class PackageVersion implements Versioned { | ||
public final static Version VERSION = VersionUtil.parseVersion( | ||
"@projectversion@", "@projectgroupid@", "@projectartifactid@"); | ||
|
||
@Override | ||
public Version version() { | ||
return VERSION; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
This copy of Jackson JSON processor `jackson-module-android-record` module is licensed under the | ||
Apache (Software) License, version 2.0 ("the License"). | ||
See the License for details about distribution rights, and the | ||
specific rights regarding derivative works. | ||
|
||
You may obtain a copy of the License at: | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Jackson JSON processor | ||
|
||
Jackson is a high-performance, Free/Open Source JSON processing library. | ||
It was originally written by Tatu Saloranta ([email protected]), and has | ||
been in development since 2007. | ||
It is currently developed by a community of developers, as well as supported | ||
commercially by FasterXML.com. | ||
|
||
## Licensing | ||
|
||
Jackson core and extension components may licensed under different licenses. | ||
To find the details that apply to this artifact see the accompanying LICENSE file. | ||
For more information, including possible other licensing options, contact | ||
FasterXML.com (http://fasterxml.com). | ||
|
||
## Credits | ||
|
||
A list of contributors may be found from CREDITS file, which is included | ||
in some artifacts (usually source distributions); but is always available | ||
from the source code management (SCM) system project uses. |
1 change: 1 addition & 0 deletions
1
android-record/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
com.fasterxml.jackson.module.androidrecord.AndroidRecordModule |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
module com.fasterxml.jackson.module.androidrecord { | ||
|
||
requires com.fasterxml.jackson.core; | ||
requires com.fasterxml.jackson.annotation; | ||
requires com.fasterxml.jackson.databind; | ||
|
||
exports com.fasterxml.jackson.module.androidrecord; | ||
|
||
provides com.fasterxml.jackson.databind.Module with | ||
com.fasterxml.jackson.module.androidrecord.AndroidRecordModule; | ||
} |
9 changes: 9 additions & 0 deletions
9
android-record/src/test/java/com/android/tools/r8/RecordTag.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.android.tools.r8; | ||
|
||
/** | ||
* Simulates the super class of Android-desugared records. | ||
* | ||
* @author Eran Leshem | ||
**/ | ||
public class RecordTag { | ||
} |
Oops, something went wrong.