I have classes which are automatically created from a JSON schema. In my Example one of these classes is called AvroPropertie.
Declaring and initializing objects from this class looks like this:
AvroPropertie aPropertie = AvroPropertie.newBuilder()
.setName(properties.get("name").asText())
.setDate(properties.get("date").asText())
.build();
If the JSON schema differs the variables like name and date differ as well. There could be more, less or totally different ones. It's always only these setters and the build() method at the end.
Is there a way to generate this code automatically? Maybe reflection?
More context: I use the ApacheAvro Serializing and deserializing with code generation.
Ty!
Using Reflection you can:
Invoke static newBuilder method to create builder.
Find all fields/setters for given POJO class. If in all cases setters on builder and setters on POJO have the same name like in your example it should be easy to convert.
Invoke build on builder instance to create new POJO.
Using basic Java Reflection and Stream API-s it could look like below:
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Properties;
import java.util.StringJoiner;
import java.util.function.Function;
import java.util.stream.Stream;
public class ReflectionApp {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
properties.put("name", "John");
properties.put("date", "today");
AvroPropertie manualInstance = AvroPropertie.newBuilder()
.setName(properties.getProperty("name"))
.setDate(properties.getProperty("date"))
.build();
Object dynamicInstance = AvroAutoCoder.createAndSet(AvroPropertie.class, properties::getProperty);
System.out.println(manualInstance);
System.out.println(dynamicInstance);
System.out.println("manualInstance == dynamicInstance => " + manualInstance.equals(dynamicInstance));
}
}
class AvroAutoCoder {
public static Object createAndSet(Class clazz, Function<String, String> dataSupplier) throws Exception {
Object builderInstance = findMethod(clazz, "newBuilder")
.invoke(null);
Class<?> builderClass = builderInstance.getClass();
getSetters(clazz).forEach(setter -> {
try {
String fieldName = setter.getName().substring(3).toLowerCase();
findMethod(builderClass, setter.getName())
.invoke(builderInstance, dataSupplier.apply(fieldName));
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
return findMethod(builderClass, "build")
.invoke(builderInstance);
}
private static Method findMethod(Class clazz, String methodName) {
return Arrays.stream(clazz.getDeclaredMethods())
.filter(method -> method.getName().equals(methodName))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
private static Stream<Method> getSetters(Class clazz) {
return Arrays.stream(clazz.getDeclaredMethods())
.filter(method -> method.getParameterCount() == 1 && method.getReturnType() == Void.TYPE)
.filter(method -> method.getName().startsWith("set"));
}
}
Above code prints:
AvroPropertie[name='John', date='today']
AvroPropertie[name='John', date='today']
manualInstance == dynamicInstance => true
Comment added by the Author of question:
In this specific example class APropertie has a static inner class named Builder which holds the wanted setters. In this case the getSetters() methods needs to be changed a little bit:
// Find all setter of class
private static Stream<Method> getSetters2(Class clazz) {
Optional<Class> first = Arrays.stream(clazz.getDeclaredClasses())
.findFirst();
return Arrays.stream(first.get().getDeclaredMethods())
.filter(method -> method.getName().startsWith("set"));
}
Related
Question
How can I reference this top-level function from within the data class? Or is Java's encapsulation of a class restrictive to the point that you cannot reach beyond the current class?
Code
def String branchName() {
return ((env.GIT_BRANCH ?: 'master') =~ /(?i)^(?:origin\/)?(.*)/)[0][1];
}
public DeployConfig implements IDeployConfig {
public DeployConfig(IDeployConfig config) {
this._appName = config.app;
this._gitUrl = config.gitUrl;
// ... et cetera
}
public String getBranchName() {
return branchName()
}
}
Background
I'm trying to define a data class that represents our standard Jenkinsfile configuration, in an attempt to make our pipeline more testable, and less "cross your fingers and hope it didn't break anything". Toward that goal, here is a snippet of that implementation.
Now, the property getter I'm trying to write doesn't know the actual branch being built when the object is constructed, because that's derived from the Map<String, String> returned by checkout scm which gets instantiated at runtime. We assign the GIT_BRANCH out to the global environment env.GIT_BRANCH so that it can be referenced elsewhere.
Miscellaneous
To the would-be suggestion of putting the target branch in the Jenkinsfile, that defeats the purpose of the Jenkinsfile being an instruction set for a job with Git configurations assigned, such as a multi-branch job with a shared Jenkinsfile.
Other Code
To give some context about what I mean about the checkout scm command happening after the construction of DeployConfig, the pipeline roughly resembles this:
// ./pipeline-library/vars/deploy.groovy
#!/usr/bin/groovy
def call(Closure body) {
def config = [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = config
body()
environmentVariables(config) // assigns certain keys to global env
if (env.IS_PROD) {
deployProd(config)
}
else {
deployNonProd(config)
}
}
// ./pipeline-library/vars/deployNonProd.groovy
#!/usr/bin/groovy
def call(Map config) {
// local variable declarations
pipeline {
agent {
label 'some-configuration-name'
}
environment {
// shared environment variables
}
options {
// configured options, like timestamps and log rotation
}
stages {
stage('Checkout') {
steps {
def gitInfo = checkout scm
env.GIT_BRANCH = gitInfo.GIT_BRANCH
}
}
// additional stages
}
}
}
Edits
Edit: The idea behind the property that calls the top-level function is a computed property that gets called later in the pipeline, after the checkout scm command has been executed. The DeployConfig would be constructed before the pipeline runs, and so the branch is not known at that time.
So I solved the problem for myself, but it's arguably a less than ideal solution to the problem. Basically, here's what I had to do:
First, I created a getter getGetBranchName and setter setGetBranchName on the class of type Closure<String> and it had a backing field _getBranchName. I also created a property getBranchName of type String that returned the result of this._getBranchName().
Second, if the incoming Map has a property branchName, then I set the value this._getBranchName = () -> { return config.branchName } so that I am referencing the getter of an outer object.
Third, as a final check, I assign the global function signature from Jenkins after constructing the DeployConfig object. That all looks like the below code (Note: ellipses are used to indicate more code unrelated to the specific solution):
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import com.domain.jenkins.data.DeployConfig
import com.domain.jenkins.data.GitUrl
import com.domain.jenkins.exceptions.InterruptException
import com.domain.jenkins.io.FileSystem
import com.domain.jenkins.pipeline.PipelineJob
import com.domain.jenkins.pipeline.PipelineStage
class Program {
static FileSystem fs
static PipelineJob pipeline
static Map<String, String> env
static Map jenkinsfile
static {
fs = new FileSystem()
pipeline = new PipelineJob()
env = [:]
jenkinsfile = [ ... ]
}
static String branchName() {
return ((env.GIT_BRANCH ?: 'master') =~ /(?i)^(?:origin\/)?(.*)/)[0][1]
}
static void main(String[] args) {
println 'Initialize pipeline'
pipeline = new PipelineJob()
println 'Initialize configuration'
DeployConfig config = new DeployConfig(jenkinsfile)
println new JsonBuilder(config.toMap()).toPrettyString()
println 'Assign static method as getBranchName method'
config.getBranchName = () -> { return branchName() }
println 'Assign environment variables to global env'
env << config.environmentVariables
...
}
}
And the DeployConfig class accepts that using the following (Note: most of the related code is not included for brevity's sake):
package com.domain.jenkins.data
import com.domain.jenkins.interfaces.IDeployConfig
import com.domain.jenkins.interfaces.IMappable
import com.domain.jenkins.interfaces.Mappable
class DeployConfig extends Mappable implements IDeployConfig, IMappable {
private Closure<String> _getBranchName
DeployConfig() {
this.branchName = 'master'
}
DeployConfig(Map options) {
this.branchName = options.branchName
}
String getBranchName() {
return this._getBranchName()
}
void setBranchName(String value) {
if(this._getBranchName == null) {
this._getBranchName = () -> { return 'master' }
}
if(value) {
this._getBranchName = () -> { return value }
}
}
void setGetBranchName(Closure<String> action) {
this._getBranchName = action
}
}
I'm working on a model factory that create business models from test data stores in Katalon Studio. It's been years since I seriously touched Java, especially to do any sort of generic programming.
How I'm trying to do it
I'll have some BaseModelFactory, defined to be:
import java.util.stream.Collectors
import com.kms.katalon.core.testdata.TestData
public class BaseModelFactory<T> {
public static List ModelsFrom(TestData testData) {
return testData.getAllData()
.stream()
.map { row -> this.ModelFromRow(row) }
.collect(Collectors.toList())
}
public static <T> T ModelFrom(TestData testData, int rowNum) {
return ModelFromRow(testData.getAllData().get(rowNum))
}
private static <T> T ModelFromRow(List<Object> row) {
return null
}
}
and then, whenever we have a data store that we want to create business models from, we simply create a derived factory that implements ModelsFrom. For example:
import java.text.SimpleDateFormat
import com.xxx.models.contract.ContractModel
public class ContractModelFactory extends BaseModelFactory<ContractModel> {
private static ContractModel ModelFromRow(List<Object> row) {
SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy");
return new ContractModel(
Integer.parseInt(row[0]),
row[1],
dateFormat.parse(row[2]),
dateFormat.parse(row[3]),
//...
)
}
}
OK, so what's the problem?
The problem is that, whenever I run a Test Case, with
WebUI.verifyNotEqual(
ContractModelFactory.ModelFrom(findTestData("whatever"), 1),
null
)
, where "whatever" points to a non-empty data file, it fails! When I go to debug it, I see that BaseModelFactory.ModelFromRow is getting hit, instead of the hiding static method ContractModelFactory.ModelFromRow !
How do I fix this?
DISCLAIMER: In case you didn't see it in the start of the paragraph, I am doing this in Katalon Studio environment.
My applications needs to convert data between Java and XML.
When converting the data, I need to distinguish whether or not the value was present, the value was set explicitly to null or the value had a value.
XML example:
<person><name>Bob</name></person> <-- element 'name' contains value "Bob"
<person><name nil="true"/></person> <-- element 'name' was set explicitly to 'nil'/null
<person></person> <-- element 'name' is missing
As Java types like 'String' only knows two states (null or not null), I tried to use Java Optionals to solve this.
A mapping between XML and Java Optionals could look like this:
<person></person> <=> Optional<String> name = null;
<person><name>Bob</name></person> <=> Optional<String> name = Optional.of("Bob");
<person><name nil="true"/></person> <=> Optional<String> name = Optional.empty();
I tried to use JAXB for the marshalling and unmarshalling. The idea was that the setter of a field only gets invoked when a value needs to be set explicitly to an value. That means that a value is absent implicitly.
I had a look on other stackoverflow questions like the following, but all of them were incomplete handling the behaviour I need to achieve:
How to generate JaxB-Classes with java.util.Optional?
Using generic #XmlJavaTypeAdapter to unmarshal wrapped in Guava's Optional
Using Guava's Optional with #XmlAttribute
I've been struggling with this problem for two days now. I tried to use the XMLAdapter and GenericAdapter, tried several ways how to annotate the fields and getter/setter with #XmlElement, tried to use #XmlAnyElment with and without lax, but all of them only led to a partial success. Either the nil value was not handeld correctly, the lists were not printed out correctly, ...
I think every Java webservice with a properly implemented patch operation should have had this problem. (not talking about the "json patch approach" (RFC 6902))
Is there a common way to solve my problem?
The following code is able to distinguish empty name from null name. To make the solution work, I created a PersonList element to contain all of the person elements. Each Person contains a Name that will have isNil() return true if the element was explicitly set to null by the XML:
Person.java:
import java.util.Optional;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
#XmlType(propOrder = {"name"})
#XmlRootElement(name = "person")
public class Person {
private Optional<Name> optionalName;
public Person() {
optionalName = Optional.<Name>empty();
}
public Optional<Name> getOptionalName() {
return optionalName;
}
public Name getName() {
return (optionalName.isPresent()) ? (optionalName.get()) : (null);
}
#XmlElement(name = "name", required = false)
public void setName(Name name) {
optionalName = Optional.ofNullable(name);
}
#Override
public String toString() {
return String.format("Person(optionalName.isPresent() = %s, name = %s)",
Boolean.toString(optionalName.isPresent()),
((getName() == null) ? ("null") : (getName().toString())));
}
}
Name.java:
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlValue;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "name")
public class Name {
#XmlAttribute(name = "nil")
private boolean nil;
#XmlValue
private String value;
public Name() {
nil = false;
value = null;
}
public boolean isNil() {
return nil;
}
public void setNil(boolean torf) {
this.nil = torf;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
#Override
public String toString() {
return String.format("Name(nil = %s, value = %s)",
Boolean.toString(nil),
(value == null) ? ("null"):("\""+getValue()+"\""));
}
}
PersonList.java:
import java.util.Iterator;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "PersonList")
public class PersonList {
private List<Person> persons;
public PersonList() {
persons = null;
}
#XmlElement(name = "person")
public List<Person> getPersons() {
return persons;
}
public void setPersons(List<Person> persons) {
this.persons = persons;
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder("PersonList(persons = ");
if(persons == null) {
sb.append("null");
}
else {
sb.append("[");
Iterator<Person> iterator = persons.iterator();
while(iterator.hasNext()) {
sb.append(iterator.next().toString());
if(iterator.hasNext()) {
sb.append(", ");
}
}
sb.append("]");
}
sb.append(")");
return sb.toString();
}
}
Main class to demonstrate the solution:
import java.io.ByteArrayInputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
public class XmlOptional {
public static final int STATUS_OKAY = 0;
public static final int STATUS_ERROR = -1;
public static final String XML_DATA = "<PersonList>" +
"<person><name>Bob</name></person>" +
"<person><name nil=\"true\" /></person>" +
"<person></person>" +
"</PersonList>";
private XmlOptional() {
}
private static PersonList loadXml() {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(XML_DATA.getBytes());
JAXBContext context = JAXBContext.newInstance(PersonList.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
PersonList personList = (PersonList)unmarshaller.unmarshal(bais);
return personList;
}
catch(Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
int status = STATUS_OKAY;
try {
PersonList personList = loadXml();
System.out.format("Xml contained: %s%n", personList);
}
catch (Throwable thrown) {
status = STATUS_ERROR;
thrown.printStackTrace();
}
finally {
System.exit(status);
}
}
}
Sample output:
Xml contained: PersonList(persons = [Person(optionalName.isPresent() = true, name = Name(nil = false, value = "Bob")), Person(optionalName.isPresent() = true, name = Name(nil = true, value = "")), Person(optionalName.isPresent() = false, name = null)])
Since I was not able to solve the problem completely by solely using and configuring JAXB properly, I decided to solve it as follows:
(The main goal was to write a subsystem to communicate with an external system based on XML)
As a starting point, I used the XSD schema provided by the target system to communicate with and generated the corresponding (XML)Java classes using JAXB and the XSD file. All the fields in those generated classes were of type JAXBElement<>, in order to be able to hold the 3 states needed (absent, null, someValue).
On the business model side, I used Java classes with Optional<> field types in order to hold the 3 states.
For the mapping, I wrote a mapper which uses reflection to recursively map from JAXB to Java and vice versa. When mapping from Java to JAXB, the mapper used the ObjectFactory to create the JAXBElement objects. (Mapper itself just had about 300 lines of code).
The fields were mapped based on the matching field names.
The most ugly and challenging part was, that the XSD schema file needed to be altered, in order to make JAXB generated classes that uses JAXBElement field types. Therefore I had to manually add the attribute minOccurs="0" nillable="true" to the XML elements, if not already set.
With that solution above, I finally managed to map the XML to Java and vice versa considering the 3 states needed, easily.
Of course, this solution has its drawbacks.
One is the manual modification of the XSD file. Usually bad practice to alter the XSD file provided by the external system, which acts as an interface contract.
For my requirements at the time, the solution worked perfectly. Even changes to the interface contract of the external system could be implemented very easily.
You can use some validation in your java class like #NotNull, #Size and so on. Or you can put default value , to be sure , that it will be not null. After that you can create DTOs (Data transfer object) with the recommended Xml annotations and mapped it with the ModelMapper.
I am trying to create a List of errors with different codes and converting them to JSON using gson.
String jsonString = gson.toJson(Errors.getErrors());
And here is the class:
public class Errors {
private static List<SingleError> errors = new ArrayList<>();
public static List<SingleError> getErrors() {
return errors;
}
public static void addError(SingleError singleError) {
errors.add(singleError);
}
}
The output I get:
[
{
"code": "bad_signal"
}
]
The output I need:
{
"errors": [
{
"code": "bad_signal"
}
]
}
What am I missing in the Errors class the get the output I need?
It would be better if it would be added in the class without just adding string to json conversion.
EDIT
As schomsel suggested, I should use this line to get the output I need.
gson.toJson(Collections.singletonMap("errors", Errors.getErrors()))
And it did work but I failed to mention that I am also using Servlet to return the String and setting this header, which deletes the "errors".
resp.setHeader("Content-Type", "application/json");
What is the correct header I need to use here?
Obviously, you should understand that desired json representation is for Errors class itself and not contained erros list only so your code is to be tweaked so you can pass Errors class instance as input to - gson.toJson(...)
Two solutions ,
First Solution - make Errors fields and methods non - static and pass on Errors instance instead of errors List to call - gson.toJson(ErrorsInstance);
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
class Errors {
private List<SingleError> errors = new ArrayList<>();
public List<SingleError> getErrors() {
return errors;
}
public void addError(SingleError singleError) {
errors.add(singleError);
}
Second Solution - if fields & methods can't be made static then add a new method to get Errors instance via private constructor and then create Gson object from GsonBuilder so that static fields can be included during serialization.
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
class Errors {
// private constructor
private Errors(List<SingleError> errors) {
Errors.errors = errors;
}
private static List<SingleError> errors = new ArrayList<>();
public static List<SingleError> getErrors() {
return errors;
}
// New method to return Errors instance
public static Errors getErrorsInstance() {
return new Errors(errors);
}
public static void addError(SingleError singleError) {
errors.add(singleError);
}
}
//To include static fields during serialization & ignore only transient fields- if not done then json would be empty
GsonBuilder gsonBuilder = new GsonBuilder();
// Allowing the serialization of static fields
gsonBuilder.excludeFieldsWithModifiers(java.lang.reflect.Modifier.TRANSIENT);
// Creates a Gson instance based on the current configuration
Gson gson = gsonBuilder.create();
Errors errorsInstance = Errors.getErrorsInstance();
String jsonStringTest = gson.toJson(errorsInstance );
EDIT:
For Second solution, you wouldn't need a private constructor & new method - getErrorsInstance() . You can simply feed new Errors() to gson.toJson(...) . What you need is only static field inclusion in deserialization & same would hold true for solution # 1 too. So you don't need to modify any code, just make sure with GsonBuilder that static fields are included & pass on Errors instance instead of contained list.
I am trying to implement a JSON serialization in Java with Genson 1.3 for polymorphic types, including:
Numbers
Arrays
Enum classes
The SSCCE below demonstrates roughly what I am trying to achieve:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.owlike.genson.Genson;
import com.owlike.genson.GensonBuilder;
/**
* A Short, Self Contained, Compilable, Example for polymorphic serialization
* and deserialization.
*/
public class GensonPolymoprhicRoundTrip {
// our example enum
public static enum RainState {
NO_RAIN,
LIGHT_RAIN,
MODERATE_RAIN,
HEAVY_RAIN,
LIGHT_SNOW,
MODERATE_SNOW,
HEAVY_SNOW;
}
public static class Measurement<T> {
public T value;
public int qualityValue;
public String source;
public Measurement() {
}
public Measurement(T value, int qualityValue, String source) {
this.value = value;
this.qualityValue = qualityValue;
this.source = source;
}
}
public static class DTO {
public List<Measurement<?>> measurements;
public DTO(List<Measurement<?>> measurements) {
this.measurements = measurements;
}
}
public static void main(String... args) {
Genson genson = new GensonBuilder()
.useIndentation(true)
.useRuntimeType(true)
.useClassMetadataWithStaticType(false)
.addAlias("RainState", RainState.class)
.useClassMetadata(true)
.create();
DTO dto = new DTO(
new ArrayList(Arrays.asList(
new Measurement<Double>(15.5, 8500, "TEMP_SENSOR"),
new Measurement<double[]>(new double[] {
2.5,
1.5,
2.0
}, 8500, "WIND_SPEED"),
new Measurement<RainState>(RainState.LIGHT_RAIN, 8500, "RAIN_SENSOR")
)));
String json = genson.serialize(dto);
System.out.println(json);
DTO deserialized = genson.deserialize(json, DTO.class);
}
}
Numbers and Arrays worked well out-of-the-box, but the enum class is providing a bit of a challenge. In this case the serialized JSON form would have to be IMO a JSON object including a:
type member
value member
Looking at the EnumConverter class I see that I would need to provide a custom Converter. However I can't quite grasp how to properly register the Converter so that it would be called during deserialization. How should this serialization be solved using Genson?
Great for providing a complete example!
First problem is that DTO doesn't have a no arg constructor, but Genson supports classes even with constructors that have arguments. You just have to enable it via the builder with 'useConstructorWithArguments(true)'.
However this will not solve the complete problem. For the moment Genson has full polymorphic support only for types that are serialized as a json object. Because Genson will add a property called '#class' to it. There is an open issue for that.
Probably the best solution that should work with most situations would be to define a converter that automatically wraps all the values in json objects, so the converter that handles class metadata will be able to generate it. This can be a "good enough" solution while waiting for it to be officially supported by Genson.
So first define the wrapping converter
public static class LiteralAsObjectConverter<T> implements Converter<T> {
private final Converter<T> concreteConverter;
public LiteralAsObjectConverter(Converter<T> concreteConverter) {
this.concreteConverter = concreteConverter;
}
#Override
public void serialize(T object, ObjectWriter writer, Context ctx) throws Exception {
writer.beginObject().writeName("value");
concreteConverter.serialize(object, writer, ctx);
writer.endObject();
}
#Override
public T deserialize(ObjectReader reader, Context ctx) throws Exception {
reader.beginObject();
T instance = null;
while (reader.hasNext()) {
reader.next();
if (reader.name().equals("value")) instance = concreteConverter.deserialize(reader, ctx);
else throw new IllegalStateException(String.format("Encountered unexpected property named '%s'", reader.name()));
}
reader.endObject();
return instance;
}
}
Then you need to register it with a ChainedFactory which would allow you to delegate to the default converter (this way it works automatically with any other type).
Genson genson = new GensonBuilder()
.useIndentation(true)
.useConstructorWithArguments(true)
.useRuntimeType(true)
.addAlias("RainState", RainState.class)
.useClassMetadata(true)
.withConverterFactory(new ChainedFactory() {
#Override
protected Converter<?> create(Type type, Genson genson, Converter<?> nextConverter) {
if (Wrapper.toAnnotatedElement(nextConverter).isAnnotationPresent(HandleClassMetadata.class)) {
return new LiteralAsObjectConverter(nextConverter);
} else {
return nextConverter;
}
}
}).create();
The downside with this solution is that useClassMetadataWithStaticType needs to be set to true...but well I guess it is acceptable as it's an optim and can be fixed but would imply some changes in Gensons code, the rest still works.
If you are feeling interested by this problem it would be great you attempted to give a shot to that issue and open a PR to provide this feature as part of Genson.