Configurable #SerializedName in Gson - java

Could I get #SerializedName value from config file etc.?
I mean like:
#SerializedName(value = configProfider.getJsonFieldName())
private String myField
Thanks.

I was particularly wrong in my comment: this is possible in Gson, but not with #SerializedName (due to how Gson works internally) and non-compile time expressions (due to Java restrictions). Sorry for the confusion. Gson supports a custom field naming strategy that you can supply while configuring your Gson instance.
Consider the following custom annotation:
#Retention(RUNTIME)
#Target(FIELD)
#interface DynamicSerializedName {
String value();
}
Now just implement your strategy:
final class DynamicSerializedNameStrategy
implements FieldNamingStrategy {
private final Function<? super String, String> translator;
private DynamicSerializedNameStrategy(final Function<? super String, String> translator) {
this.translator = translator;
}
static FieldNamingStrategy getDynamicSerializedNameStrategy(final Function<? super String, String> translator) {
return new DynamicSerializedNameStrategy(translator);
}
#Override
public String translateName(final Field field) {
final DynamicSerializedName annotation = field.getAnnotation(DynamicSerializedName.class);
if ( annotation == null ) {
// Taking the default naming strategy
// #SerializedName takes higher priority in ReflectiveTypeAdapterFactory.Adapter anyway
return FieldNamingPolicy.IDENTITY.translateName(field);
}
final String key = annotation.value();
final String resolvedName = translator.apply(key);
if ( resolvedName == null ) {
throw new IllegalArgumentException("Cannot resolve name by " + key + " for " + field);
}
return resolvedName;
}
}
Create an annotation:
final class Model {
#DynamicSerializedName("gson.model.field")
final String field = null;
}
How it can be used (system properties-based example):
private static final Gson gson = new GsonBuilder()
.setFieldNamingStrategy(getDynamicSerializedNameStrategy(System::getProperty))
.create();
public static void main(final String... args)
throws IOException {
try ( final Reader reader = getPackageResourceReader(Q43517297.class, "arbitrary.json") ) {
final Model model = gson.fromJson(reader, Model.class);
System.out.println(model.field);
}
}
This example will fail unless you do:
either define the gson.model.field system property programmatically like System.setProperty("gson.model.field", "dynamic");;
or define the system property using the -D parameter while starting the JVM: -Dgson.model.field=dynamic.
Consider the following JSON (referenced as arbitrary.json above):
{
"static": "BY-STATIC-NAME",
"dynamic": "BY-DYNAMIC-NAME"
}
Once the #DynamicSerializedName annotation is set and the corresponding property is properly configured the output will be:
BY-DYNAMIC-NAME
Once you remove the #DynamicSerializedName annotation from the DTO field, or annotate the field with #SerializedName (it has higher priority as of Gson 2.8.0), the output will be:
BY-STATIC-NAME

Related

Gson - how to special processing like masking

How to configure Gson to do additional processing on the value for toJson?
public class MyClass{
#SerializedName("qwerty")
#Mask(exposeFront=2, exposeRear=2, mask="*")
private String qwerty
}
Assuming MyClass#qwerty has a value of 1234567890, how to set Gson to output {"qwerty":"12******90"}?
Gson ReflectiveTypeAdapterFactory, that is responsible for "plain" objects serialization and deserialization, is not possible to enhance to support any other annotations like #Masked. It can only use annotations like #Expose (indirectly via an exclusion strategy), #SerializedName and a few others like #Since and #Until (exclusion strategy too). Note these annotations are documented and supported by default. In general, Gson suggests using a type adapter for the declaring class, MyClass, but this also means that you must manage all fields and make sure the corresponding type adapter is updated once your class is changed. Even worse, adding a custom type adapter makes these annotations support lost.
As an another way of working around it is injecting a special string type adapter factory that can do the trick, but due to the mechanics of how it is injected, this is both limited and requires duplicating the #Masked annotation values (if you're using the annotation elsewhere in your code) and the type adapter factory configuration in #JsonAdapter.
public abstract class MaskedTypeAdapterFactory
implements TypeAdapterFactory {
private final int exposeFront;
private final int exposeRear;
private final char mask;
private MaskedTypeAdapterFactory(final int exposeFront, final int exposeRear, final char mask) {
this.exposeFront = exposeFront;
this.exposeRear = exposeRear;
this.mask = mask;
}
// must be "baked" into the class (name only represents the configuration)
public static final class _2_2_asterisk
extends MaskedTypeAdapterFactory {
private _2_2_asterisk() {
super(2, 2, '*');
}
}
#Override
#Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( typeToken.getRawType() != String.class ) {
return null;
}
#SuppressWarnings("unchecked")
final TypeAdapter<String> delegate = (TypeAdapter<String>) gson.getAdapter(typeToken);
final TypeAdapter<String> typeAdapter = new TypeAdapter<String>() {
#Override
public void write(final JsonWriter out, final String value)
throws IOException {
// mask the value
final int length = value.length();
final char[] buffer = value.toCharArray();
for ( int i = exposeFront; i < length - exposeRear; i++ ) {
buffer[i] = mask;
}
out.value(new String(buffer));
}
#Override
public String read(final JsonReader in)
throws IOException {
return delegate.read(in);
}
}
.nullSafe();
#SuppressWarnings("unchecked")
final TypeAdapter<T> adapter = (TypeAdapter<T>) typeAdapter;
return adapter;
}
}
#NoArgsConstructor
#AllArgsConstructor
final class MyClass {
#SerializedName("qwerty")
#Mask(exposeFront = 2, exposeRear = 2, mask = "*")
// unfortunately, this must duplicate the #Mask annotation values
// since type adapter (factories) do not accept supplemental information
// and Java annotations can only accept compile-time constants
#JsonAdapter(MaskedTypeAdapterFactory._2_2_asterisk.class)
#SuppressWarnings("unused")
private String qwerty;
}
Test:
public final class MaskedTypeAdapterFactoryTest {
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.disableInnerClassSerialization()
.create();
#Test
public void test() {
final String actual = gson.toJson(new MyClass("1234567890"));
final String expected = "{\"qwerty\":\"12******90\"}";
Assertions.assertEquals(expected, actual);
}
}
This is probably the most robust way of doing that in Gson.

Graphql SPQR customize object serialization / deserialization

I have the following data model with custom attributes:
class Foo {
private Long id;
private Set<AdditionalAttribute> attributes;
}
class AdditionalAttribute {
private Key key;
private String value;
}
class Key {
private String name;
private Class<?> type;
}
My model produces this json:
{"id":123, "attributes": [{"key1":12345}, {"key2":"value2"}]}
My expected json is:
{"id":123, "key1":12345, "key2":"value2"}
How can I achieve a such serialization / deserialization using graphql spqr?
FYI, currently I can do it in REST API with jackson (BeanSerializerModifier for serialization and #JsonAnySetter for deserialization) as follow:
// Serialization using BeanSerializerModifier
class FooModifier extends BeanSerializerModifier {
#Override
public List<BeanPropertyWriter> changeProperties(
SerializationConfig config, BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties) {
for (int i = 0; i < beanProperties.size(); i++) {
BeanPropertyWriter writer = beanProperties.get(i);
if (Foo.class.isAssignableFrom(beanDesc.getBeanClass()) && "attributes".equals(writer.getName())) {
beanProperties.set(i, new FooAttributesWriter(writer));
}
}
return beanProperties;
}
}
class FooAttributesWriter extends BeanPropertyWriter {
public HasAttributesWriter(BeanPropertyWriter w) {
super(w);
}
#Override
public void serializeAsField(Object bean, JsonGenerator gen,
SerializerProvider prov) throws Exception {
if(Foo.class.isAssignableFrom(bean.getClass())) {
Set<AdditionalAttribute> set = ((Foo) bean).getAttributes();
for (AdditionalAttribute a : set) {
gen.writeStringField(a.getKey().getName(), a.getValue());
}
}
}
}
// Deserilization using #JsonAnySetter
class Foo {
private Long id;
private Set<AdditionalAttribute> attributes;
// Deserialization of custom properties
#JsonAnySetter
public void set(String name, Object value) {
attributes.add(new AdditionalAttribute(buildKey(name,value), value));
}
}
The problem here is not JSON (de)serialization. With GraphQL, the shape of all your inputs and outputs is defined by the schema, and the schema can not normally have dynamic parts (object types where the fields are unknown ahead of time). Because your Set<AdditionalAttribute> can contain anything at all at runtime, it means your Foo type would have to have unknown fields. This is highly antithetical to how GraphQL is designed.
The only way to achieve a dynamic structure is to have an object scalar which effectively is a JSON blob that can not be validated, or sub-selected from. You could turn Foo into such a scalar by adding #GraphQLScalar to it. Then all input would be valid, {"id":123, "key1":12345 "key2":"value2"} but also {"whatever": "something"}. And it would be your logic's job to ensure correctness. Additionally, if you ever return Foo, the client would not be able to sub-select from it. E.g. {foo} would be possible but {foo { id }} would not, because the schema would no longer know if the id field is present.
To recap, you options are:
Leaving it as it is (the dynamic stuff is a list nested under attributes)
Turning Set<AdditionalAttribute> into a type (a new class or EnumMap) with known structure with all the possible keys as fields. This is only possible if the keys aren't totally dynamic
Making the whole enclosing object an object scalar by using #GraphQLScalar
Thanks a lot for your time and the proposed options.
Currently, we have found another way (maybe option 4 :) ) to generate a "similar" json to the expected output (We lost the type information in the generated output, but we have another logic that helps us to retrieve the type).
Here an example :
class Foo {
private Long id;
private Set<AdditionalAttribute> attributes;
#GraphQLQuery
public String get(#GraphQLArgument(name = "key") String key) {
for (AdditionalAttribute a : attributes) {
if (a.getConfigurationKey().getKey().equalsIgnoreCase(key)) {
return a.getAttributeValue();
}
}
return null;
}
and we can sub-select Foo as follow:
foo {
id
key1: get(key: "key1")
key2: get(key: "key2")
}
And this return
{"id":123, "key1":"12345", "key2":"value2"}

Code Design: JSON from Socket to custom handler class

I have an Socket-Server in Java. This socket will receive json-strings with an specific structure.
{
"command": "test",
"name": "Hallo Welt"
}
I can not change this structure. The value of "command" will declare the type of content.
After I receive this from the socket, I would like to call different handlers, to handle these different commands:
command "test" > TestHandler implements CommandHandler
command "foo" > FooHandler implements CommandHandler
How can I convert the json into a object and bind the object to the specific handler?
This is my current approach:
I have an model class called BaseCommand which contains a enum command field.
class BaseCommand {
public CommandType command;
}
class TestCommand extends BaseCommand {
public String name;
}
With GSON I parse the JSON to BaseCommand class.
After that I can read the command type.
I declare a ENUM to map the command types to the Handler:
enum CommandType {
test(TestHandler.class),
foo(FooHandler.class);
public final Class<? extends CommandHandler> handlerClass;
public CommandTypes(Class<? extends CommandHandler> handlerClass) {
this.handlerClass = handlerClass;
}
}
My handler's are implementing this interface:
public interface CommandHandler<T extends BaseCommand> {
void handle(T command);
}
Now I have the command type enum and through Google Guices MapBinder I can get the Handler instance to handle request. This works
// in class ...
private final Map<CommandType, CommandHandler> handlers;
#Inject ClassName(Map<CommandType, CommandHandler> handlers) {
this.handlers = handlers;
}
// in converter method
private void convert(String json) {
BaseCommand baseCommand = GSONHelper().fromJson(json, BaseCommand.class);
// How can I get the CommandModel?
// If the commandType is "test" how can I parse TestCommand automatically?
??? commandModel = GSONHelper().fromJson(json, ???);
handlers.get(baseCommand.command).handle(commandModel);
}
Does anyone know a solution for my problem?
Or a complete different approach for this?
best regards, Michael
How can I get the CommandModel?
If the commandType is "test" how can I parse TestCommand automatically?
You can use a TypeAdapterFactory to get the most appropriate type adapter in the most accurate and flexible way. The example below slightly differs from your classes naming, but I think it's not a big issue to you. So, let's assume you have the following command arguments DTO declarations:
abstract class AbstractCommandDto {
final String command = null;
}
final class HelloCommandDto
extends AbstractCommandDto {
final String name = null;
}
Now you can make a special TypeAdapterFactory to make a sort of looking-ahead to determine the incoming command by command arguments name. It may look complicated, but in fact TypeAdapterFactoryies are not that hard to implement. Note that JsonDeserializer might be another option for you, but then you lose automatic deserializing unless you delegate its deserialize() method to another backing Gson instance.
final class AbstractCommandDtoTypeAdapterFactory
implements TypeAdapterFactory {
// The factory handles no state and can be instantiated once
private static final TypeAdapterFactory abstractCommandDtoTypeAdapterFactory = new AbstractCommandDtoTypeAdapterFactory();
// Type tokens are used to define type information and are perfect value types so they can be instantiated once as well
private static final TypeToken<CommandProbingDto> abstractCommandProbingDtoTypeToken = new TypeToken<CommandProbingDto>() {
};
private static final TypeToken<HelloCommandDto> helloCommandDtoTypeToken = new TypeToken<HelloCommandDto>() {
};
private AbstractCommandDtoTypeAdapterFactory() {
}
static TypeAdapterFactory getAbstractCommandDtoTypeAdapterFactory() {
return abstractCommandDtoTypeAdapterFactory;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// First, check if the incoming type is AbstractCommandDto
if ( AbstractCommandDto.class.isAssignableFrom(typeToken.getRawType()) ) {
// If yes, then build a special type adapter for the concrete type
final TypeAdapter<AbstractCommandDto> abstractCommandDtoTypeAdapter = new AbstractCommandDtoTypeAdapter(
gson,
gson.getDelegateAdapter(this, abstractCommandProbingDtoTypeToken),
(commandName, jsonObject) -> deserialize(gson, commandName, jsonObject),
dto -> getTypeAdapter(gson, dto)
);
// Some cheating for javac...
#SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) abstractCommandDtoTypeAdapter;
return typeAdapter;
}
// If it's something else, just let Gson pick up the next type adapter
return null;
}
// Create an AbstractCommandDto instance out of a ready to use JsonObject (see the disadvantages about JSON trees below)
private AbstractCommandDto deserialize(final Gson gson, final String commandName, final JsonObject jsonObject) {
#SuppressWarnings("unchecked")
final TypeToken<AbstractCommandDto> typeToken = (TypeToken<AbstractCommandDto>) resolve(commandName);
final TypeAdapter<AbstractCommandDto> typeAdapter = gson.getDelegateAdapter(this, typeToken);
return typeAdapter.fromJsonTree(jsonObject);
}
private TypeAdapter<AbstractCommandDto> getTypeAdapter(final Gson gson, final AbstractCommandDto dto) {
#SuppressWarnings("unchecked")
final Class<AbstractCommandDto> clazz = (Class<AbstractCommandDto>) dto.getClass();
return gson.getDelegateAdapter(this, TypeToken.get(clazz));
}
// Or any other way to resolve the class. This is just for simplicity and can be even extract elsewhere from the type adapter factory class
private static TypeToken<? extends AbstractCommandDto> resolve(final String commandName)
throws IllegalArgumentException {
switch ( commandName ) {
case "hello":
return helloCommandDtoTypeToken;
default:
throw new IllegalArgumentException("Cannot handle " + commandName);
}
}
private static final class AbstractCommandDtoTypeAdapter
extends TypeAdapter<AbstractCommandDto> {
private final Gson gson;
private final TypeAdapter<CommandProbingDto> probingTypeAdapter;
private final BiFunction<? super String, ? super JsonObject, ? extends AbstractCommandDto> commandNameToCommand;
private final Function<? super AbstractCommandDto, ? extends TypeAdapter<AbstractCommandDto>> commandToTypeAdapter;
private AbstractCommandDtoTypeAdapter(
final Gson gson,
final TypeAdapter<CommandProbingDto> probingTypeAdapter,
final BiFunction<? super String, ? super JsonObject, ? extends AbstractCommandDto> commandNameToCommand,
final Function<? super AbstractCommandDto, ? extends TypeAdapter<AbstractCommandDto>> commandToTypeAdapter
) {
this.gson = gson;
this.probingTypeAdapter = probingTypeAdapter;
this.commandNameToCommand = commandNameToCommand;
this.commandToTypeAdapter = commandToTypeAdapter;
}
#Override
public void write(final JsonWriter out, final AbstractCommandDto dto)
throws IOException {
// Just pick up a delegated type adapter factory and use it
// Or just throw an UnsupportedOperationException if you're not going to serialize command arguments
final TypeAdapter<AbstractCommandDto> typeAdapter = commandToTypeAdapter.apply(dto);
typeAdapter.write(out, dto);
}
#Override
public AbstractCommandDto read(final JsonReader in) {
// Here you can two ways:
// * Either "cache" the whole JSON tree into memory (JsonElement, etc,) and simplify the command peeking
// * Or analyze the JSON token stream in a more efficient and sophisticated way
final JsonObject jsonObject = gson.fromJson(in, JsonObject.class);
final CommandProbingDto commandProbingDto = probingTypeAdapter.fromJsonTree(jsonObject);
// Or just jsonObject.get("command") and even throw abstractCommandDto, AbstractCommandProbingDto and all of it gets away
final String commandName = commandProbingDto.command;
return commandNameToCommand.apply(commandName, jsonObject);
}
}
// A synthetic class just to obtain the command field
// Gson cannot instantiate abstract classes like what AbstractCommandDto is
private static final class CommandProbingDto
extends AbstractCommandDto {
}
}
And how it's used:
public static void main(final String... args) {
// Build a command DTO-aware Gson instance
final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getAbstractCommandDtoTypeAdapterFactory())
.create();
// Build command registry
final Map<Class<?>, Consumer<?>> commandRegistry = new LinkedHashMap<>();
commandRegistry.put(HelloCommandDto.class, new HelloCommand());
// Simulate and accept a request
final AbstractCommandDto abstractCommandDto = gson.fromJson("{\"command\":\"hello\",\"name\":\"Welt\"}", AbstractCommandDto.class);
// Resolve a command
final Consumer<?> command = commandRegistry.get(abstractCommandDto.getClass());
if ( command == null ) {
throw new IllegalArgumentException("Cannot handle " + abstractCommandDto.command);
}
// Dispatch
#SuppressWarnings("unchecked")
final Consumer<AbstractCommandDto> castCommand = (Consumer<AbstractCommandDto>) command;
castCommand.accept(abstractCommandDto);
// Simulate a response
System.out.println(gson.toJson(abstractCommandDto));
}
private static final class HelloCommand
implements Consumer<HelloCommandDto> {
#Override
public void accept(final HelloCommandDto helloCommandDto) {
System.out.println("Hallo " + helloCommandDto.name);
}
}
The output:
Hallo Welt

Parsing Graphite JSON Response with Gson

I am writing a Java library for interacting with metrics from Graphite.
A typical JSON response looks like this (taken from the official docs):
[{
"target": "entries",
"datapoints": [
[1.0, 1311836008],
[2.0, 1311836009],
[3.0, 1311836010],
[5.0, 1311836011],
[6.0, 1311836012]
]
}]
where the first element of the "datapoints" array is the value and the second one the timestamp. I have modelled a GraphiteDataset class as follows
class GraphiteDataset {
private String target;
private List<GraphiteDatapoint> datapoints;
....
}
and the GraphiteDatapoint class
class GraphiteDatapoint {
private Long timestamp;
private Double value;
...
}
Now I need to parse the response (see above) into the GraphiteDataset
class using Gson. Unfortunately, the elements of "datapoints" are not named objects (e.g. {timestamp: 1234, value: 1.0} but a 2 dimensional array so I cannot directly deserialize it into some class. Currently my solution is to have an intermediate class
class GraphiteIntermediateDataset {
private String target;
private List<String> datapoints;
...
}
which has the datapoints as Strings and then I parse them into the appropriate GraphiteDatapoint instance. I think that I cannot work around a custom deserializer. Do you have any suggestions or tricks how to make this a little more convenient?
The JSON [1.2, 123456] is a array of a Double and a Long, but they are both Number, so try this:
class GraphiteDataset {
private String target;
private List<List<Number>> datapoints;
....
}
Then convert datapoints into your type after parsing, with something like:
List<GraphiteDatapoint> points = datapoints.stream().
.map(nums -> new GraphiteDatapoint(nums.get(0).doubleValue(), nums.get(1).intValue()))
.collect(Collectors.toList());
assuming a constructor like:
class GraphiteDatapoint {
private Long timestamp;
private Double value;
public GraphiteDatapoint(Double value, Long timestamp) {
this.value = value;
this.timestamp = timestamp;
}
...
}
The final solution is to introduce an intermediate class GraphiteIntermediateDataset which looks as follows:
class GraphiteIntermediateDataset {
private String target;
private List<List<Number>> datapoints;
}
and the deserializer code looks like this
List<GraphiteIntermediateDataset> intermediateDatasetList = GSON.fromJson(raw, new TypeToken<List<GraphiteIntermediateDataset>>(){}.getType());
GraphiteIntermediateDataset intermediateDataset = intermediateDatasetList.get(0);
... check if empty (which can happen), when true return an empty GraphiteDataset
List<GraphiteDatapoint> gDatapoints = intermediateDataset
.stream()
.map(ds -> {
return new GraphiteDatapoint(ds.get(0).longValue(),
ds.get(1).doubleValue())
}
.collect(Collectors.toList());
return new GraphiteDataset()
.setDatapoints(gDatapoints);
Type safety and proper data binding are your friends. Gson has several methods to accomplish what you need. For example, declare data transfer objects:
final class GraphiteDataset {
final String target;
// The incoming DTO has property `datapoints`, however Java conventions suggest dataPoints (as far as I understand English).
#SerializedName("datapoints")
final List<GraphiteDataPoint> dataPoints;
// Actually, Gson does not need this constructor, and the DTO can even have a single private default one.
// But in order to make it consistent with the next class just making it programmatically instantiable...
// Also, but may be opinion-based, hiding constructors is really a good idea since one can hide the instantiation strategy whilst constructors cannot.
private GraphiteDataset(final String target, final List<GraphiteDataPoint> dataPoints) {
this.target = target;
this.dataPoints = dataPoints;
}
}
final class GraphiteDataPoint {
final double value;
final long timestamp;
private GraphiteDataPoint(final double value, final long timestamp) {
this.value = value;
this.timestamp = timestamp;
}
// Instantiation must be accessible programmatically somehow
static GraphiteDataPoint graphiteDataPoint(final double value, final long timestamp) {
return new GraphiteDataPoint(value, timestamp);
}
}
And then implement either a GraphiteDataPoint JSON deserializer:
// In Gson serializers and deserializers can only deal with intermediate Gson JSON tree representation of objects (JsonElement-s).
// For some cases it's quite simple, if the given data to serialize/deserialize does not consume much memory
final class GraphiteDataPointJsonDeserializer
implements JsonDeserializer<GraphiteDataPoint> {
private static final JsonDeserializer<GraphiteDataPoint> graphiteDataPointJsonDeserializer = new GraphiteDataPointJsonDeserializer();
private GraphiteDataPointJsonDeserializer() {
}
// Not letting to instantiate a stateless (so it's thread-safe) deserializer twice or more
static JsonDeserializer<GraphiteDataPoint> getGraphiteDataPointJsonDeserializer() {
return graphiteDataPointJsonDeserializer;
}
#Override
public GraphiteDataPoint deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
final JsonArray asJsonArray = jsonElement.getAsJsonArray();
final double value = asJsonArray.get(0).getAsJsonPrimitive().getAsDouble();
final long timestamp = asJsonArray.get(1).getAsJsonPrimitive().getAsLong();
return graphiteDataPoint(value, timestamp);
}
}
Or a type adapter:
// Type adapters, unlike serializers and deserializers, are designed to work with streams.
// They may look too low-level and tedious/hard to implement, but for some cases they can be useful in both serialization and deserialization.
// For the case #1: no need to serialize nested objects recursively to transform them to JSON trees that can be important for large objects.
// For the case #2: intermediate JSON trees are not necessary (but internal buffers are).
final class GraphiteDataPointTypeAdapter
extends TypeAdapter<GraphiteDataPoint> {
private static final TypeAdapter<GraphiteDataPoint> graphiteDataPointTypeAdapter = new GraphiteDataPointTypeAdapter();
private GraphiteDataPointTypeAdapter() {
}
static TypeAdapter<GraphiteDataPoint> getGraphiteDataPointTypeAdapter() {
return graphiteDataPointTypeAdapter;
}
#Override
public void write(final JsonWriter out, final GraphiteDataPoint value) {
throw new UnsupportedOperationException("not implemented");
}
#Override
public GraphiteDataPoint read(final JsonReader in)
throws IOException {
in.beginArray();
final double value = in.nextDouble();
final long timestamp = in.nextLong();
in.endArray();
return graphiteDataPoint(value, timestamp);
}
}
Both implementations are essentially the same, but may be crucial for you dependening on data (de)serialization strategies and costs. Example use:
private static final String JSON = "[{\"target\":\"entries\",\"datapoints\":[[1.0,1311836008],[2.0,1311836009],[3.0,1311836010],[5.0,1311836011],[6.0,1311836012]]}]";
// Gson is thread-safe and can be shared between threads, so no need to instantiate it every time it's needed
private static final Gson gsonWithDeserializers = new GsonBuilder()
.registerTypeAdapter(GraphiteDataPoint.class, getGraphiteDataPointJsonDeserializer())
.create();
private static final Gson gsonWithTypeAdapters = new GsonBuilder()
.registerTypeAdapter(GraphiteDataPoint.class, getGraphiteDataPointTypeAdapter())
.create();
private static final TypeToken<List<GraphiteDataset>> graphiteDatasetsTypeToken = new TypeToken<List<GraphiteDataset>>() {
};
public static void main(final String... args) {
dumpGraphiteDatasets(gsonWithDeserializers.fromJson(JSON, graphiteDatasetsTypeToken.getType()));
dumpGraphiteDatasets(gsonWithTypeAdapters.fromJson(JSON, graphiteDatasetsTypeToken.getType()));
}
private static void dumpGraphiteDatasets(final Iterable<GraphiteDataset> graphiteDatasets) {
graphiteDatasets.forEach(graphiteDataset -> {
out.println(graphiteDataset.target);
graphiteDataset.dataPoints.forEach(graphiteDataPoint -> {
out.print(" ");
out.print(graphiteDataPoint.value);
out.print(" ");
out.println(graphiteDataPoint.timestamp);
});
});
}
The output:
entries
1.0 1311836008
2.0 1311836009
3.0 1311836010
5.0 1311836011
6.0 1311836012
entries
1.0 1311836008
2.0 1311836009
3.0 1311836010
5.0 1311836011
6.0 1311836012

class A declares multiple JSON fields

i have a class A which has some private fields and the same class extends another class B which also has some private fields which are in class A.
public class A extends B {
private BigDecimal netAmountTcy;
private BigDecimal netAmountPcy;
private BigDecimal priceTo;
private String segment;
private BigDecimal taxAmountTcy;
private BigDecimal taxAmountPcy;
private BigDecimal tradeFeesTcy;
private BigDecimal tradeFeesPcy;
// getter and setter for the above fields
}
and class B has got some private fiedls which are in class A
now when i try to create JSON string from above class A i get the following exception :
class com.hexgen.ro.request.A declares multiple JSON fields named netAmountPcy
How to fix this?
Since they are private fields there should not be any problem while creating json string i guess but i am not sure.
i create json string like the following :
Gson gson = new Gson();
tempJSON = gson.toJson(obj);
here obj is the object of class A
Since they are private fields there should not be any problem while creating json string
I don't think this statement is true, GSON looks up at the object's private fields when serializing, meaning all private fields of superclass are included, and when you have fields with same name it throws an error.
If there's any particular field you don't want to include you have to mark it with transient keyword, eg:
private transient BigDecimal tradeFeesPcy;
This is a bit late, but I ran into this exact same problem as well. The only thing was that I wasn't able to modify the superclass as that code wasn't mine. The way that I resolved this was by creating an exclusion strategy that skipped any field that had a field of the same name present in a superclass. Here is my code for that class:
public class SuperclassExclusionStrategy implements ExclusionStrategy
{
public boolean shouldSkipClass(Class<?> arg0)
{
return false;
}
public boolean shouldSkipField(FieldAttributes fieldAttributes)
{
String fieldName = fieldAttributes.getName();
Class<?> theClass = fieldAttributes.getDeclaringClass();
return isFieldInSuperclass(theClass, fieldName);
}
private boolean isFieldInSuperclass(Class<?> subclass, String fieldName)
{
Class<?> superclass = subclass.getSuperclass();
Field field;
while(superclass != null)
{
field = getField(superclass, fieldName);
if(field != null)
return true;
superclass = superclass.getSuperclass();
}
return false;
}
private Field getField(Class<?> theClass, String fieldName)
{
try
{
return theClass.getDeclaredField(fieldName);
}
catch(Exception e)
{
return null;
}
}
}
I then set the Serialization and Deserialization exclusion strategies in the builder as follows:
builder.addDeserializationExclusionStrategy(new SuperclassExclusionStrategy());
builder.addSerializationExclusionStrategy(new SuperclassExclusionStrategy());
Hopefully this helps someone!
The same error message also happens if you have different fields, but they have the same #SerializedName.
#SerializedName("date_created")
private Date DateCreated;
#SerializedName("date_created")
private Integer matchTime;
Doing copy/paste you can simply make such mistake. So, look into the the class and its ancestors and check for that.
You cannot have two fields with the same name.
You cannot have two fields with the same serialized name.
Types are irrelevant for these rules.
I used GsonBuilder and ExclusionStrategy to avoid the redundant fields as below, it is simple and straight forward.
Gson json = new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
if(f.getName().equals("netAmountPcy")){
return true;
}
return false;
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}).create();
Add following lines at the bottom of proguard.config
(if you are using proguard in project)
-keepclassmembers class * {
private <fields>;
}
I don't think you should make the members transient, this might lead to errors because members that you might need in the future might be hidden.
How I solved this problem is to use a custom naming strategy and append the full class name to the Json, the downside of this is that it would lead to larger Json and if you need it for something like a Rest Api it would be weird for clients to name the fields that way, but I only needed to serialize to write to disk on android.
So here is an implementation of a custom naming strategy in Kotlin
import com.google.gson.FieldNamingStrategy
import java.lang.reflect.Field
class GsonFieldNamingStrategy : FieldNamingStrategy {
override fun translateName(field: Field?): String? {
return "${field?.declaringClass?.canonicalName}.${field?.name}"
}
}
So for all fields, the full canonical name would be appended, this would make the child class have a different name from the parent class, but when deserializing, the child class value would be used.
In kotlin adding the #Transient annotation for the variable on the parent class did the trick for me on a sealed class with open variables.
Solution for Kotlin, as suggested #Adrian-Lee, you have to tweak some Null Checks
class SuperclassExclusionStrategy : ExclusionStrategy {
override fun shouldSkipClass(clazz: Class<*>?): Boolean {
return false
}
override fun shouldSkipField(f: FieldAttributes?): Boolean {
val fieldName = f?.name
val theClass = f?.declaringClass
return isFieldInSuperclass(theClass, fieldName)
}
private fun isFieldInSuperclass(subclass: Class<*>?, fieldName: String?): Boolean {
var superclass: Class<*>? = subclass?.superclass
var field: Field?
while (superclass != null) {
field = getField(superclass, fieldName)
if (field != null)
return true
superclass = superclass.superclass
}
return false
}
private fun getField(theClass: Class<*>, fieldName: String?): Field? {
return try {
theClass.getDeclaredField(fieldName)
} catch (e: Exception) {
null
}
}
}
In my case I was dumb enough to register an adapter with X class, and try to serialize fromJson with Y class:
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Game.class, new TournamentSerializer());
final Gson gson = gsonBuilder.create();
createdTournament = gson.fromJson(jsonResponse.toString(), Tournament.class);
For Kotlin-er:
val fieldsToExclude = listOf("fieldToExclude", "otherFieldToExclude")
GsonBuilder()
.setExclusionStrategies(object : ExclusionStrategy {
override fun shouldSkipField(f: FieldAttributes?) = f?.let { fieldsToExclude.contains(it.name) } ?: false
override fun shouldSkipClass(clazz: Class<*>?) = false
})
.create()

Categories

Resources