serj-lotutovici / moshi-lazy-adapters Goto Github PK
View Code? Open in Web Editor NEWA collection of simple JsonAdapters for Moshi.
License: Apache License 2.0
A collection of simple JsonAdapters for Moshi.
License: Apache License 2.0
This code will generate fallbackİnt
instead of fallbackInt
since we have a capital i
in Turkish :) and that will throw an exception
I realise that FallbackOnNull seems to be geared towards primitives, but supporting any type would be great.
Maybe a separate adapter (DefaultOnNull) could work, where a default value for any type when it is null.
Hey,
I have the following issue and maybe I am just missing something:
I want to filter out null
in an enum list. It works great in Java, but fails in Kotlin.
The following test reproduces the "wrong" behaviour:
import com.serjltt.moshi.adapters.DefaultOnDataMismatchAdapter
import com.serjltt.moshi.adapters.FilterNulls
import com.squareup.moshi.Moshi
import org.junit.Test
class AnimalTest {
enum class Animal {
Dog,
Cat
}
data class Animals(
@FilterNulls val animals: List<Animal> = arrayListOf()
)
@Test
fun testMoshiEnumNullable() {
val moshi = Moshi.Builder()
.add(FilterNulls.ADAPTER_FACTORY)
.add(DefaultOnDataMismatchAdapter.newFactory(Animal::class.java, null))
.add(com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory())
.build()
val response = moshi.adapter(Animals::class.java).fromJson("""{"animals" : [
"Dog",
"Unknown"
]}""")
assert(response != null)
// Gets [Dog, null] instead of just [Dog]
assert(response == Animals(animals = arrayListOf(Animal.Dog)))
}
}
Makes sense for clarity. and structure. may be something like com.serjltt.moshi.adapters.annotations
?
What do you think?
cc @vanniktech
Placeholder issue, for discussions regarding changes in 2.0.
Api changes:
@Wrapped
make failOnNotFound default true
.@Wrapped
rename value()
to path()
ADAPTER_FACTORY
).How do you feel making failOnDefault by default true in @wrapped as part of a breaking change for 2.0?
Wrapped adapter is extremely convenient when deserializing unnecessarily wrapped json responses without having to create wrapper classes.
But it doesn't seem to work when serializing POST
data.
Endpoint: POST api/{surveyId}/answers
Example Body:
{ //SurveyAnswerPayload.class
"answers": [
{ //SurveyAnswer.class
"questionId": "1",
"answer": "Foo"
}, {
"questionId": "2",
"answer": "Bar"
}
]
}
Trying to use Wrapped adapter to avoid creating a wrapper object SurveyAnswerPayload
like so:
public class SurveyAnswer {
@Json(name = "questionId")
public String questionId;
@Json(name = "answer")
public String answer;
}
public interface SurveyService{
@POST("surveys/{surveyId}/answers")
@Wrapped(path = {"answers"})
Call<Void> postAnswers(@Path("surveyId") String surveyId, @Body List<SurveyAnswer> answers);
}
Sadly this doesn't work. It serializes into [{"questionId":"1","answer":"A"},{"questionId":"2","answer":"B"}]
Wrapper class SurveyAnswerPaylod
is needed here to get it wrapped in a parent "{"answers":[{"questionId":"1","answer":"A"},{"questionId":"2","answer":"B"}]}
"
class SurveyAnswerPayload{
@Json(name = "answers")
public List<SurveyAnswer> answers;
}
I am using this adapter with the wrapped annotation. But I have difficulty on serializing a specific list item inside another list (categories). You will find the json below with the needed list commented.
API service :
@GET("venues/categories") @Wrapped(path = { "response", "categories" })
Json file:
{
"meta": {
"code": 200,
"requestId": "5ba633244c1f670803edce43"
},
"response": {
"categories": [
{},
{},
{},
{
"id": "4d4b7105d754a06374d81259",
"name": "Food",
"pluralName": "Food",
"shortName": "Food",
"icon": {
"prefix": "https://ss3.4sqi.net/img/categories_v2/food/default_",
"suffix": ".png"
},
"categories": [ //want to serialize this
{
"id": "503288ae91d4c4b30a586d67",
"name": "Afghan Restaurant",
...
Thanks.
Hi, the idea for this lib is really awesome. The only problem now is that it doesn't seem to work with newer codegen moshi, the generated adapter for @wrapped member still complains about the original name not being found in the json. Do you plan to work on this issue?
Since your PR got merged we can now test the entire string.
For the new functionality in @Wrapped
and also @SerializeOnlyNonEmpty
. What do you think?
Hi,
I used you example in LazyAdaptersRetrofitTest but ended up with the following issue: if 'item' or 'item2' in the test "unwrapNestedJsonAdapter" contain more than one element, then in the "Nested" class you have to do something like:
@Json(name = "item") @Wrapped(path = "foo1") String foo1;
@Json(name = "item") @Wrapped(path = "foo2") String foo2;
But then you get an exception from Moshi saying there are conflicting fields because of the 2 "item".
Am I missing something ?
The entire purpose would be to be able to exclude some fields when deserializing or serializing.
I'm thinking about:
class Foo {
int bar;
@SerializeOnly int number; // Will only get serialized but not deserialized
}
and
class Bar {
int foo;
@DeserializeOnly int number; // Will only get deserialized but not serialized
}
There's serializeonly and deserializeonly (which is the most similar), but maybe for semantics and clarity it would be useful to have a transient one that just exludes a field from any serialization.
App is not compiling when I updated the moshi to 1.13.0 throwing the following exception.
Specs -
AGP - 7.0.4
Kotlin - 1.6.10
org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
File being compiled: /Users/pavanpm/P1/Projects/Freelance/Nakkalites-Android/app/build/generated/source/kapt/debug/in/nakkalites/mediaclient/data/user/LoginUserEntityJsonAdapter.kt
The root cause java.lang.NullPointerException was thrown at: org.jetbrains.kotlin.backend.common.lower.AnnotationImplementationTransformer.implementAnnotationProperties(AnnotationImplementationTransformer.kt:127)
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:239)
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:235)
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:68)
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:55)
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:41)
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:96)
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:29)
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:96)
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:22)
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:96)
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:43)
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.doGenerateFilesInternal(JvmIrCodegenFactory.kt:195)
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.generateModule(JvmIrCodegenFactory.kt:60)
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:35)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.generate(KotlinToJVMBytecodeCompiler.kt:331)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:123)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:58)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:52)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:92)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:434)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:120)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally(IncrementalCompilerRunner.kt:357)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally$default(IncrementalCompilerRunner.kt:299)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl$rebuild(IncrementalCompilerRunner.kt:118)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:169)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:80)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:622)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:100)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1713)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359)
at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.NullPointerException
at org.jetbrains.kotlin.backend.common.lower.AnnotationImplementationTransformer.implementAnnotationProperties(AnnotationImplementationTransformer.kt:127)
at org.jetbrains.kotlin.backend.common.lower.AnnotationImplementationTransformer.createAnnotationImplementation(AnnotationImplementationTransformer.kt:105)
at org.jetbrains.kotlin.backend.common.lower.AnnotationImplementationTransformer.visitConstructorCall(AnnotationImplementationTransformer.kt:57)
at org.jetbrains.kotlin.backend.jvm.lower.JvmAnnotationImplementationTransformer.visitConstructorCall(JvmAnnotationImplementationTransformer.kt:48)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitConstructorCall(IrElementTransformerVoid.kt:202)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitConstructorCall(IrElementTransformerVoid.kt:24)
at org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl.accept(IrConstructorCallImpl.kt:28)
at org.jetbrains.kotlin.ir.expressions.IrExpression.transform(IrExpression.kt:33)
at org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression.transformChildren(IrFunctionAccessExpression.kt:48)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitExpression(IrElementTransformerVoid.kt:131)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitMemberAccess(IrElementTransformerVoid.kt:192)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitFunctionAccess(IrElementTransformerVoid.kt:195)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitCall(IrElementTransformerVoid.kt:198)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitCall(IrElementTransformerVoid.kt:199)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitCall(IrElementTransformerVoid.kt:24)
at org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl.accept(IrCallImpl.kt:47)
at org.jetbrains.kotlin.ir.expressions.IrExpression.transform(IrExpression.kt:33)
at org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression.transformChildren(IrFunctionAccessExpression.kt:48)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitExpression(IrElementTransformerVoid.kt:131)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitMemberAccess(IrElementTransformerVoid.kt:192)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitFunctionAccess(IrElementTransformerVoid.kt:195)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitCall(IrElementTransformerVoid.kt:198)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitCall(IrElementTransformerVoid.kt:199)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitCall(IrElementTransformerVoid.kt:24)
at org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl.accept(IrCallImpl.kt:47)
at org.jetbrains.kotlin.ir.expressions.IrExpression.transform(IrExpression.kt:33)
at org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl.transformChildren(IrTypeOperatorCallImpl.kt:47)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitExpression(IrElementTransformerVoid.kt:131)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitTypeOperator(IrElementTransformerVoid.kt:244)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitTypeOperator(IrElementTransformerVoid.kt:245)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitTypeOperator(IrElementTransformerVoid.kt:24)
at org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl.accept(IrTypeOperatorCallImpl.kt:40)
at org.jetbrains.kotlin.ir.expressions.IrExpression.transform(IrExpression.kt:33)
at org.jetbrains.kotlin.ir.expressions.IrExpressionBody.transformChildren(IrBody.kt:46)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitBody(IrElementTransformerVoid.kt:108)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitExpressionBody(IrElementTransformerVoid.kt:114)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitExpressionBody(IrElementTransformerVoid.kt:115)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitExpressionBody(IrElementTransformerVoid.kt:24)
at org.jetbrains.kotlin.ir.expressions.IrExpressionBody.accept(IrBody.kt:36)
at org.jetbrains.kotlin.ir.expressions.IrExpressionBody.transform(IrBody.kt:39)
at org.jetbrains.kotlin.ir.declarations.IrField.transformChildren(IrField.kt:41)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitDeclaration(IrElementTransformerVoid.kt:57)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitField(IrElementTransformerVoid.kt:81)
at org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext.visitFieldNew(IrElementTransformerVoidWithContext.kt:123)
at org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext.visitField(IrElementTransformerVoidWithContext.kt:61)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitField(IrElementTransformerVoid.kt:82)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitField(IrElementTransformerVoid.kt:24)
at org.jetbrains.kotlin.ir.declarations.IrField.accept(IrField.kt:34)
at org.jetbrains.kotlin.ir.IrElement$DefaultImpls.transform(IrElement.kt:32)
at org.jetbrains.kotlin.ir.IrElementBase.transform(IrElementBase.kt:19)
at org.jetbrains.kotlin.ir.declarations.IrProperty.transformChildren(IrProperty.kt:58)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitDeclaration(IrElementTransformerVoid.kt:57)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitProperty(IrElementTransformerVoid.kt:78)
at org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext.visitPropertyNew(IrElementTransformerVoidWithContext.kt:119)
at org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext.visitProperty(IrElementTransformerVoidWithContext.kt:54)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitProperty(IrElementTransformerVoid.kt:79)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitProperty(IrElementTransformerVoid.kt:24)
at org.jetbrains.kotlin.ir.declarations.IrProperty.accept(IrProperty.kt:49)
at org.jetbrains.kotlin.ir.IrElement$DefaultImpls.transform(IrElement.kt:32)
at org.jetbrains.kotlin.ir.IrElementBase.transform(IrElementBase.kt:19)
at org.jetbrains.kotlin.ir.util.TransformKt.transformInPlace(transform.kt:35)
at org.jetbrains.kotlin.ir.declarations.IrClass.transformChildren(IrClass.kt:66)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitDeclaration(IrElementTransformerVoid.kt:57)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitClass(IrElementTransformerVoid.kt:66)
at org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext.visitClassNew(IrElementTransformerVoidWithContext.kt:111)
at org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext.visitClass(IrElementTransformerVoidWithContext.kt:47)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitClass(IrElementTransformerVoid.kt:67)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitClass(IrElementTransformerVoid.kt:24)
at org.jetbrains.kotlin.ir.declarations.IrClass.accept(IrClass.kt:55)
at org.jetbrains.kotlin.ir.IrElement$DefaultImpls.transform(IrElement.kt:32)
at org.jetbrains.kotlin.ir.IrElementBase.transform(IrElementBase.kt:19)
at org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl.transformChildren(IrFileImpl.kt:89)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoidKt.transformChildrenVoid(IrElementTransformerVoid.kt:330)
at org.jetbrains.kotlin.backend.common.lower.AnnotationImplementationLowering.lower(AnnotationImplementationTransformer.kt:41)
at org.jetbrains.kotlin.backend.common.phaser.FileLoweringPhaseAdapter.invoke(PhaseBuilders.kt:120)
at org.jetbrains.kotlin.backend.common.phaser.FileLoweringPhaseAdapter.invoke(PhaseBuilders.kt:116)
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:96)
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
... 46 more
When I commented out the @FallbackOnNull
annotation, it started compiling properly
I have a use case where a data mismatch occurs for A<B>
. With the current implementation of DefaultOnDataMismatchAdapter#newFactory
this isn't possible to parse as the first parameter of the method is Class<T> type
. Ideally this method would accept Type
instead of Class<T>
and then my usage would be:
Type type = Types.newParameterizedType(A.class, B.class);
A<B> defaultValue = new A<>(new B());
DefaultOnDataMismatchAdapter.newFactory(type, defaultValue);
Imagine the Retrofit Interface with the following method:
@Wrapped("query", "results", "channel") @GET("path") Single<Something> doSomething();
If I get a response that can be unwrapped I get my Single<Something>
, for instance:
{
"query": {
"results": {
"channel": {
...
}
}
}
}
}
However if I get something back that can't be:
{
"query": {
"results": null
}
}
I end up with a Single that errors NoSuchElementException. My guess is, that our WrappedAdapter does not return the deserialized class which will end up in an Observable that only completes and then this line will convert the Observable to Single with NoSuchElementException.
Is this behavior wanted?
For instance, when my json looks like:
{
"data" : [
{
"book_id" : "doe",
"author_data" : [
{
"name" : "John Doe",
"id" : "john_doe"
}
],
"title" : "Doe The John Doe",
}
],
"index_searched" : "book_id"
}
Could it be annotated like this?
@GET("/book/{key}")
@Wrapped("data") @FirstElement fun aBook(@Path("key") key: String): Observable<Book>
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.