Java Reflection accessing public fields - java

I am writing a custom Serializer (Jackson JSON) for List class, this list could be inferred with different class types, so I'll need to grab object fields values using reflection.
Note, all this classes has public values (no setters and getters), so Invoking the getter will not be an option.
This is what I get so far:
package com.xxx.commons.my.serializer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.List;
public class ListSerializer extends StdSerializer<List> {
public ListSerializer() {
super(List.class);
}
#Override
public void serialize(List aList, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (aList != null) {
jsonGenerator.writeStartObject();
for (int index = 0 ; index < aList.size(); index++) {
try {
Object next = aList.get(index);
List<Field> fields = FieldUtils.getAllFieldsList(next.getClass());
Object object = next.getClass().newInstance();
for (int j = 0 ; j < fields.size(); j ++ ) {
jsonGenerator.writeObjectField(String.format("%s[%s]",fields.get(j).getName(),index) , object);
}
} catch (Exception e) {
e.printStackTrace();
}
}
jsonGenerator.writeEndObject();
}
}
}
MyTest
package com.xxx.commons.my.serializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.junit.Test;
public class ListSerializerTest {
private ObjectMapper mapper = new ObjectMapper();
#Test
public void test() throws Exception {
SimpleModule module = new SimpleModule();
module.addSerializer(new ListSerializer());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(module);
MyTempClassParent parent = new MyTempClassParent();
parent.mylist.add(new MyTempClass("1","2","3"));
String json = mapper.writeValueAsString(parent);
System.out.println(json);
}
}
Example classes:
public class MyTempClass {
public MyTempClass() {
}
public MyTempClass(String value1, String value2, String value3) {
this.valueA = value1;
this.valueB = value2;
this.valueC = value3;
}
public String valueA;
public String valueB;
public String valueC;
}
public class MyTempClassParent {
public List<MyTempClass> mylist = new LinkedList<>();
}
Any ideas or alternatives for writing this ?

Maybe you should just use ObjectMapper with setting property accessor to get access to every field:
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
MyDtoAccessLevel dtoObject = new MyDtoAccessLevel();
String dtoAsString = mapper.writeValueAsString(Arrays.asList(dtoObject));
System.out.println(dtoAsString);
result:
[{"stringValue":null,"intValue":0,"floatValue":0.0,"booleanValue":false}]
dto:
class MyDtoAccessLevel {
private String stringValue;
int intValue;
protected float floatValue;
public boolean booleanValue;
// NO setters or getters
--edit
For getting values from objects by reflection:
#Override
public void serialize(List aList, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (aList != null) {
jsonGenerator.writeStartObject();
for (int index = 0 ; index < aList.size(); index++) {
try {
Object next = aList.get(index);
List<Field> fields = FieldUtils.getAllFieldsList(next.getClass());
for (int j = 0 ; j < fields.size(); j ++ ) {
jsonGenerator.writeObjectField(String.format("%s[%s]",fields.get(j).getName(),index) , fields.get(j).get(next));
}
} catch (Exception e) {
e.printStackTrace();
}
}
jsonGenerator.writeEndObject();
}
}
Please write in question, what do you want to have in output.

Related

Writing a custom Deserializer for homogenuous Collections with unbounded generics

I'm struggling to deserialize a Collection<Collection<?>> using Jackson. When deserializing the serialized object Jackson converts them into a LinkedHashMap instead of Item:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JsonTest {
ObjectMapper mapper = new ObjectMapper();
#Test
void main() throws JsonProcessingException {
Set<Integer> firstSet = Set.of(1, 2, 3);
Set<Item> secondSet = Set.of(new Item("abc"), new Item("123"));
Root root = new Root(List.of(firstSet, secondSet));
String json = mapper.writeValueAsString(root);
System.out.println(json);
Root parsed = mapper.readValue(json, Root.class);
assertEquals(firstSet, parsed.data().get(0));
assertEquals(secondSet, parsed.data().get(1));
// the assertion above fails:
// Expected :[Item[id=abc], Item[id=123]]
// Actual :[{id=abc}, {id=123}]
}
}
record Root(List<Set<?>> data) {}
record Item(String id) {}
// build.gradle
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
test {
useJUnitPlatform()
}
My first idea was to replace the Set<?> with a custom container class that contains an additional type hint and write a custom deserializer:
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JsonTest {
ObjectMapper mapper = new ObjectMapper();
#Test
void main() throws JsonProcessingException {
TypedList<Item> secondSet = new TypedList<>(Item.class, List.of());
Root root = new Root(secondSet);
String json = mapper.writeValueAsString(root);
System.out.println(json);
Root parsed = mapper.readValue(json, Root.class);
assertEquals(secondSet, parsed.data());
}
}
class TypedCollectionDeserializer<T> extends StdDeserializer<TypedList<T>> {
public TypedCollectionDeserializer() {
super(TypedList.class);
}
#Override
public TypedList<T> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
Class<T> valueType = getTypeClass(p);
String dataKey = p.nextFieldName();
if (!dataKey.equals("data")) {
throw new IllegalStateException();
}
List<T> list = deserializeData(p, valueType);
// skip END_ARRAY
p.nextToken();
return new TypedList<>(valueType, list);
}
private Class<T> getTypeClass(JsonParser p) throws IOException {
String typeKey = p.nextFieldName();
if (!typeKey.equals("type")) {
throw new IllegalStateException();
}
String typeValue = p.nextTextValue();
try {
return (Class<T>) Class.forName(typeValue);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("unexpected type " + typeValue, e);
}
}
private List<T> deserializeData(JsonParser p, Class<T> valueType) throws IOException {
List<T> list = new ArrayList<>();
JsonToken jsonToken = p.nextToken();
if (jsonToken != JsonToken.START_ARRAY) {
throw new IllegalArgumentException();
}
JsonToken maybeStart = p.nextToken();
if (maybeStart == JsonToken.START_OBJECT) {
do {
T t = p.readValueAs(valueType);
if (t != null) {
list.add(t);
}
} while (p.nextToken() == JsonToken.START_OBJECT);
}
return list;
}
}
record Root(#JsonDeserialize(using = TypedCollectionDeserializer.class) TypedList<?> data) {}
record Item(String id) {}
#JsonPropertyOrder({"type", "data"})
record TypedList<T>(Class<T> type, List<T> data) {}
But this looks like I'm re-doing Jackson's own code to deserialize Collections. Is there perhaps a more idiomatic way?

Cannot deserialize instance Kafka Streams

What am I doing wrong, My below kafka stream program giving issue while streaming the data, "Cannot deserialize instance of com.kafka.productiontest.models.TimeOff out of START_ARRAY token ".
I have a topic timeOffs2 which contain time offs information with key timeOffID and value is of type object which contain employeeId. I just want to group all time offs for employee key and write to the store.
For store key will be employeeId and value will be list of timeoffs.
Program properties and streaming logic:
public Properties getKafkaProperties() throws UnknownHostException {
InetAddress myHost = InetAddress.getLocalHost();
Properties kafkaStreamProperties = new Properties();
kafkaStreamProperties.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
kafkaStreamProperties.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
kafkaStreamProperties.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, TimeOffSerde.class);
kafkaStreamProperties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
kafkaStreamProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "com.kafka.productiontest.models.TimeOffSerializer");
kafkaStreamProperties.put(StreamsConfig.APPLICATION_ID_CONFIG, application_id );
kafkaStreamProperties.put(StreamsConfig.APPLICATION_SERVER_CONFIG, myHost.getHostName() + ":" + port);
return kafkaStreamProperties;
}
String topic = "timeOffs2";
StreamsBuilder builder = new StreamsBuilder();
KStream<String, TimeOff> source = builder.stream(topic);
KTable<String, ArrayList<TimeOff>> newStore = source.groupBy((k, v) -> v.getEmployeeId())
.aggregate(ArrayList::new,
(key, value, aggregate) -> {
aggregate.add(value);
return aggregate;
}, Materialized.as("NewStore").withValueSerde(TimeOffListSerde(TimeOffSerde)));
final Topology topology = builder.build();
final KafkaStreams streams = new KafkaStreams(topology, getKafkaProperties());
TimeOffSerializer.java
ackage com.kafka.productiontest.models;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.kafka.common.serialization.Serializer;
import java.util.Map;
public class TimeOffSerializer implements Serializer {
#Override
public void configure(Map configs, boolean isKey) {
}
#Override
public byte[] serialize(String topic, Object data) {
byte[] retVal = null;
ObjectMapper objectMapper = new ObjectMapper();
try {
retVal = objectMapper.writeValueAsString(data).getBytes();
} catch (Exception e) {
e.printStackTrace();
}
return retVal;
}
#Override
public void close() {
}
}
TimeOffDeserializer.java
package com.kafka.productiontest.models;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.kafka.common.serialization.Deserializer ;
import java.util.Map;
public class TimeOffDeserializer implements Deserializer {
#Override
public void configure(Map configs, boolean isKey) {
}
#Override
public TimeOff deserialize(String arg0, byte[] arg1) {
ObjectMapper mapper = new ObjectMapper();
TimeOff timeOff = null;
try {
timeOff = mapper.readValue(arg1, TimeOff.class);
} catch (Exception e) {
e.printStackTrace();
}
return timeOff;
}
#Override
public void close() {
}
}
TimeOffSerde.java
package com.kafka.productiontest.models;
import org.apache.kafka.common.serialization.Deserializer;
import org.apache.kafka.common.serialization.Serde;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.common.serialization.Serializer;
import java.util.Map;
public class TimeOffSerde implements Serde<Object> {
private final Serde inner;
public TimeOffSerde(){
inner = Serdes.serdeFrom(new TimeOffSerializer(), new TimeOffDeserializer());
}
#Override
public void configure(Map<String, ?> configs, boolean isKey) {
inner.serializer().configure(configs, isKey);
inner.deserializer().configure(configs, isKey);
}
#Override
public void close() {
inner.serializer().close();
inner.deserializer().close();
}
#Override
public Serializer<Object> serializer() {
return inner.serializer();
}
#Override
public Deserializer<Object> deserializer() {
return inner.deserializer();
}
}
TimeOffListSerializer.java
package com.kafka.productiontest.models;
import org.apache.kafka.common.serialization.Serializer;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
public class TimeOffListSerializer implements Serializer<ArrayList<TimeOff>> {
private Serializer<TimeOff> inner;
public TimeOffListSerializer(Serializer<TimeOff> inner) {
this.inner = inner;
}
#Override
public void configure(Map<String, ?> configs, boolean isKey) {
}
#Override
public byte[] serialize(String topic, ArrayList<TimeOff> data) {
final int size = data.size();
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream dos = new DataOutputStream(baos);
final Iterator<TimeOff> iterator = data.iterator();
try {
dos.writeInt(size);
while (iterator.hasNext()) {
final byte[] bytes = inner.serialize(topic, iterator.next());
dos.writeInt(bytes.length);
dos.write(bytes);
}
}catch (Exception ex) {
}
return baos.toByteArray();
}
#Override
public void close() {
inner.close();
}
}
TimeOffListDeserializer.java
package com.kafka.productiontest.models;
import org.apache.kafka.common.serialization.Deserializer;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
public class TimeOffListDeserializer implements Deserializer<ArrayList<TimeOff>> {
private final Deserializer<TimeOff> valueDeserializer;
public TimeOffListDeserializer(final Deserializer<TimeOff> valueDeserializer) {
this.valueDeserializer = valueDeserializer;
}
#Override
public void configure(Map<String, ?> configs, boolean isKey) {
}
#Override
public ArrayList<TimeOff> deserialize(String topic, byte[] data) {
if (data == null || data.length == 0) {
return null;
}
final ArrayList<TimeOff> arrayList = new ArrayList<>();
final DataInputStream dataInputStream = new DataInputStream(new ByteArrayInputStream(data));
try {
final int records = dataInputStream.readInt();
for (int i = 0; i < records; i++) {
final byte[] valueBytes = new byte[dataInputStream.readInt()];
dataInputStream.read(valueBytes);
arrayList.add(valueDeserializer.deserialize(topic, valueBytes));
}
} catch (IOException e) {
throw new RuntimeException("Unable to deserialize ArrayList", e);
}
return arrayList;
}
#Override
public void close() {
}
}
TimeOffListSerde.java
package com.kafka.productiontest.models;
import org.apache.kafka.common.serialization.Deserializer;
import org.apache.kafka.common.serialization.Serde;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.common.serialization.Serializer;
import java.util.ArrayList;
import java.util.Map;
public class TimeOffListSerde implements Serde<ArrayList<TimeOff>> {
private Serde<ArrayList<TimeOff>> inner;
public TimeOffListSerde() {
}
public TimeOffListSerde(Serde<TimeOff> serde){
inner = Serdes.serdeFrom(new TimeOffListSerializer(serde.serializer()), new TimeOffListDeserializer(serde.deserializer()));
}
#Override
public void configure(Map<String, ?> configs, boolean isKey) {
inner.serializer().configure(configs, isKey);
inner.deserializer().configure(configs, isKey);
}
#Override
public void close() {
inner.serializer().close();
inner.deserializer().close();
}
#Override
public Serializer<ArrayList<TimeOff>> serializer() {
return inner.serializer();
}
#Override
public Deserializer<ArrayList<TimeOff>> deserializer() {
return inner.deserializer();
}
}
I think issue is in this part with withValueSerde. I can not compile with this code. But if I remove withValueSerde, it is giving me this issue "Can not deserialize TimeOff object". Can you please help and guide what I am doing wrong.
KTable<String, ArrayList<TimeOff>> newStore = source.groupBy((k, v) -> v.getEmployeeId())
.aggregate(ArrayList::new,
(key, value, aggregate) -> {
aggregate.add(value);
return aggregate;
}, Materialized.as("NewStore").withValueSerde(TimeOffListSerde(TimeOffSerde)));
Looking at your code I can see several issues:
TimeOffSerde - It should implement Serde<TimeOff> not Serde<Object>
You don't pass types for Key and Value in Materialized, so it assume it is Object
So your streaming part should be something like:
KTable<String, ArrayList<TimeOff>> newStore = source.groupBy((k, v) -> v.getEmployeeId())
.aggregate(ArrayList::new,
(key, value, aggregate) -> {
aggregate.add(value);
return aggregate;
}, Materialized.<String, ArrayList<TimeOff>, KeyValueStore<Bytes, byte[]>>as("NewStore").withValueSerde(new TimeOffListSerde(new TimeOffSerde())));
NOTICE: Rember to clear state store directory after modification.

how to wrap exception on jackson serialization

How to serialize an object with Jackson if one of getters is throwing an exception?
Example:
public class Example {
public String getSomeField() {
//some logic which will throw in example NPE
throw new NullPointerException();
}
}
Ideally I would like to get JSON:
{"someField":"null"}
or
{"someField":"NPE"}
Probably the most generic way would be implementing custom BeanPropertyWriter. You can register it by creating BeanSerializerModifier class. Below example shows how to do that.
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import java.util.List;
import java.util.stream.Collectors;
public class JsonApp {
public static void main(String[] args) throws Exception {
SimpleModule module = new SimpleModule();
module.setSerializerModifier(new BeanSerializerModifier() {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
if (beanDesc.getBeanClass() == Response.class) {
return beanProperties.stream()
.map(SilentExceptionBeanPropertyWriter::new)
.collect(Collectors.toList());
}
return super.changeProperties(config, beanDesc, beanProperties);
}
});
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
System.out.println(mapper.writeValueAsString(new Response(1, "ONE")));
System.out.println(mapper.writeValueAsString(new Response(-1, "MINUS")));
System.out.println(mapper.writeValueAsString(new Response(-1, null)));
}
}
class SilentExceptionBeanPropertyWriter extends BeanPropertyWriter {
public SilentExceptionBeanPropertyWriter(BeanPropertyWriter base) {
super(base);
}
#Override
public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
try {
super.serializeAsField(bean, gen, prov);
} catch (Exception e) {
Throwable cause = e.getCause();
gen.writeFieldName(_name);
gen.writeString(cause.getClass().getName() + ":" + cause.getMessage());
}
}
}
class Response {
private int count;
private String message;
public Response(int count, String message) {
this.count = count;
this.message = message;
}
public int getCount() {
if (count < 0) {
throw new IllegalStateException("Count is less than ZERO!");
}
return count;
}
public void setCount(int count) {
this.count = count;
}
public String getMessage() {
if (message == null) {
throw new NullPointerException("message can not be null!");
}
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Above example prints:
{"count":1,"message":"ONE"}
{"count":"java.lang.IllegalStateException:Count is less than ZERO!","message":"MINUS"}
{"count":"java.lang.IllegalStateException:Count is less than ZERO!","message":"java.lang.NullPointerException:message can not be null!"}

Obtain Spring actuator 'health' metrics

How to obtain Spring actuator /health metrics that already working?
For example, to push them to Grafana. Thus I need them as objects, not text.
You can inject properties which are exposed through actuator endpoints (like /health) by injecting instance of endpoint class using actuator API.
This stackoverflow answer explains about it in detail:
Does Spring Boot Actuator have a Java API?
Take a look on this example
package ru.formatko;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.prometheus.client.Collector;
import io.prometheus.client.exporter.common.TextFormat;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Data;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.Status;
import org.springframework.stereotype.Component;
/**
* Example:
* # HELP health_status HealthCheck result in prometheus's response format
* # TYPE health_status gauge
* health_status{application="java-service",type="main",} 1.0
* health_status{application="java-service",type="db",database="PostgreSQL",validationQuery="isValid()",} 1.0
* health_status{application="java-service",type="diskSpace",total="506332180480",exists="true",threshold="10485760",free="412188921856",} 1.0
* health_status{application="java-service",type="ping",} 1.0
*/
#Component
#Endpoint(id = "health-check")
public class HeathPrometheusEndpoint {
private static final String APPLICATION = "application";
private static final String TYPE = "type";
public static final String SAMPLE_HEALTH_STATUS = "health_status";
private final HealthEndpoint healthEndpoint;
private final String appName;
private final ObjectMapper mapper;
private final HttpCodeStatusMapper httpCodeStatusMapper;
public HeathPrometheusEndpoint(HealthEndpoint healthEndpoint,
ObjectMapper mapper,
#Value("${spring.application.name:}") String appName,
HttpCodeStatusMapper httpCodeStatusMapper) {
this.healthEndpoint = healthEndpoint;
this.mapper = mapper;
this.appName = appName;
this.httpCodeStatusMapper = httpCodeStatusMapper;
}
#ReadOperation(produces = TextFormat.CONTENT_TYPE_004)
public WebEndpointResponse<String> healthPrometheus() {
StatusDto status = createStatusDto();
List<Collector.MetricFamilySamples.Sample> samples = new ArrayList<>();
samples.add(createMainSample(status));
samples.addAll(createComponentSamples(status));
return createStringWebEndpointResponse(status, createMetricFamily(samples));
}
#SneakyThrows
private StatusDto createStatusDto() {
return mapper.readValue(mapper.writeValueAsString(healthEndpoint.health()), StatusDto.class);
}
private Collector.MetricFamilySamples.Sample createMainSample(StatusDto status) {
Labels labels = new Labels();
labels.add(APPLICATION, appName);
labels.add(TYPE, "main");
return createSample(SAMPLE_HEALTH_STATUS, labels, status.getStatus());
}
private List<Collector.MetricFamilySamples.Sample> createComponentSamples(StatusDto status) {
List<Collector.MetricFamilySamples.Sample> list = new ArrayList<>();
for (Map.Entry<String, StatusDto> entry : status.components.entrySet()) {
Labels labels = new Labels();
labels.add(APPLICATION, appName);
labels.add(TYPE, entry.getKey());
StatusDto statusDto = entry.getValue();
Map<String, Object> details = statusDto.getDetails();
if (details != null && !details.isEmpty()) {
details.forEach((k, v) -> labels.add(k, String.valueOf(v)));
}
list.add(createSample(SAMPLE_HEALTH_STATUS, labels, statusDto.getStatus()));
}
return list;
}
private Collector.MetricFamilySamples.Sample createSample(String name, Labels labels, Status status) {
double v = Status.UP.equals(status) ? 1 : 0;
return new Collector.MetricFamilySamples.Sample(name, labels.getLabels(), labels.getValues(), v);
}
private Collector.MetricFamilySamples createMetricFamily(List<Collector.MetricFamilySamples.Sample> s) {
return new Collector.MetricFamilySamples(
"health_status", Collector.Type.GAUGE,
"HealthCheck result in prometheus's response format", s);
}
private WebEndpointResponse<String> createStringWebEndpointResponse(
StatusDto status, Collector.MetricFamilySamples metricFamilySamples
) {
try {
Writer writer = new StringWriter();
TextFormat.write004(writer,
Collections.enumeration(Collections.singletonList(metricFamilySamples)));
return wrapResponse(writer.toString(), status);
} catch (IOException ex) {
// This actually never happens since StringWriter::write() doesn't throw any
// IOException
throw new RuntimeException("Writing metrics failed", ex);
}
}
private WebEndpointResponse<String> wrapResponse(String body, StatusDto status) {
if (body == null || body.isEmpty()) {
return new WebEndpointResponse<>("", 500);
} else {
int statusCode = httpCodeStatusMapper.getStatusCode(status.getStatus());
return new WebEndpointResponse<>(body, statusCode);
}
}
public static class Labels {
private final Map<String, String> map = new HashMap<>();
public void add(String label, String value) {
if (value != null && !value.isEmpty()) {
map.put(label, value);
}
}
public List<String> getLabels() {
return new ArrayList<>(map.keySet());
}
public List<String> getValues() {
return new ArrayList<>(map.values());
}
}
#Data
public static class StatusDto {
private Status status;
private Map<String, StatusDto> components;
private Map<String, Object> details;
}
}

Gson ignores my fields while converting

I created a model:
public class UserRequest extends DefaultRequest {
public String username;
public String password;
public String id;
public UserRequest(String username, String password) {
this.username = username;
this.password = password;
}
}
And I'm calling it like:
//code truncated
UserRequest userRequest = new UserRequest(username,password);
response = getRestClient().sysInitApp(userRequest).execute();
//code truncated
And then I print out request body, instead of:
{
"username":"farid",
"password":"passfarid",
"id":null
}
I get:
{
"username":"farid",
"password":"passfarid"
}
I would appreciate any help with this issue.
from the GsonBuilder javadocs... you can use GsonBuilder to construct your Gson instance, and opt in to have null values serialized as so:
Gson gson = new GsonBuilder()
.serializeNulls()
.create();
Not too familiar with Gson, but I don't think Gson would write null values to an json file. If you initialize the id like:
String id = "";
you may get an empty string in there. But you will not get a null value into a .xml file.
An example of how to enforce outputting values even if null. It will output the empty string (or "{}" if an object) instead of null and ignore transients:
package unitest;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
public class TheResponse<T> {
private String status;
private String message;
private T data;
transient private String resource;
public static void main(String[] args) {
TheResponse<String> foo = new TheResponse<String>();
//TheResponse<Baz> foo = new TheResponse<Baz>();
foo.status = "bar";
foo.data = "baz";
Gson gson = new GsonBuilder().registerTypeAdapter(TheResponse.class, new GenericAdapter()).create();
System.out.println(gson.toJson(foo).toString());
}
public static class GenericAdapter extends TypeAdapter<Object> {
#Override
public void write(JsonWriter jsonWriter, Object o) throws IOException {
recursiveWrite(jsonWriter, o);
}
private void recursiveWrite(JsonWriter jsonWriter, Object o) throws IOException {
jsonWriter.beginObject();
for (Field field : o.getClass().getDeclaredFields()) {
boolean isTransient = Modifier.isTransient(field.getModifiers());
if (isTransient) {
continue;
}
Object fieldValue = null;
try {
field.setAccessible(true);
fieldValue = field.get(o);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
jsonWriter.name(field.getName());
if (fieldValue != null && fieldValue.getClass() != String.class) {
recursiveWrite(jsonWriter, fieldValue);
continue;
}
if (fieldValue == null) {
if (field.getType() == String.class)
jsonWriter.value("");
else {
jsonWriter.jsonValue("{}");
}
} else {
jsonWriter.value(fieldValue.toString());
}
}
jsonWriter.endObject();
}
#Override
public Object read(JsonReader jsonReader) throws IOException {
// todo
return null;
}
}
}

Categories