I am using Jackson to prepare a JSON object to be inserted into ElasticSearch (ES is somewhat unrelated here). The object looks like:
class TimestampedCount {
private Date timestamp;
private Map<Long, Integer> counts;
}
The default behavior is, as expected, to convert the counts variable to an object. However, due to how I am storing the data in ES, I'd like to coerce the map to a byte[] or String field, without having to change the defined type. In other words, I want it stored differently from how it's being used. For example, if I convert it to a String, I'd expect something like the following in the final JSON:
{
"timestamp": 12355812312,
"counts": "{1: 15431, 2: 15423, 3: 1314}"
}
Is there a way to do this without having to write a custom serializer/deserializer?
You can simply add a 'getter' method which would convert the Map into suitable format. Here is an example returning a byte array:
public class JacksonGetter {
static class TimestampedCount {
private final Date timestamp;
private final Map<Long, Integer> counts;
public TimestampedCount(final Date timestamp, final Map<Long, Integer> counts) {
this.timestamp = timestamp;
this.counts = counts;
}
public Date getTimestamp() { return timestamp; }
#JsonProperty("counts")
public byte[] getCountsAsBytes() {
return counts.toString().getBytes();
}
}
public static void main(String[] args) throws JsonProcessingException {
final TimestampedCount timestampedCount = new TimestampedCount(
new Date(),
Collections.singletonMap(1L, 123));
final ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(timestampedCount));
}
}
Output:
{"timestamp":1450555085608,"counts":"ezE9MTIzfQ=="}
Related
I have following json
{"val": 501, "scale": 2}
Field scale represent how much is decimal point shifted in value (filed val). In this case there are to places, therefore result is value 5.01.
I would like to map it to following class
public class ValueClass {
#JsonProperty("val")
#JsonDeserialize(using = ValueDeserializer.class)
private BigDecimal value;
}
I would like to use custom deserializer for this however it is not clear to me how to access the other fields of JSON from within the deserializer then the annotated one.
#SuppressWarnings("serial")
class ValueDeserializer extends StdDeserializer<BigDecimal> {
protected ValueDeserializer() {
super(BigDecimal.class);
}
#Override
public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
var val = p.readValueAs(Integer.class);
int scale = ??; // <-- How to access "scale" field here?
return new BigDecimal(val).scaleByPowerOfTen(-scale);
}
}
P.S. I know that I could you #JsonCreator in this simple case.
public class ValueClass {
private BigDecimal value;
#JsonCreator
public ValueClass(//
#JsonProperty("val") Integer val, //
#JsonProperty("scale") Integer scale //
) {
this.value = new BigDecimal(val).scaleByPowerOfTen(-scale);
}
}
Nevertheless the real use case is much more complex and it would be more beneficial to keep the the logic inside deserializer (if possible) for easier reuse.
Thanks for help.
Edit 1
As a replay to Chaosfire here is a a bit more clarification to my case.
More real JSON which I need to parse looks this
{"val1":501, "scale":2, "val2":407, "val3":86}
Value of scale filed is shared as divider for multiple fields.
The JSON object has about 10 fields like above and 50 other fields which are relatively straightforward. The reason why I would prefer the deserializer is to avoid huge #JsonCreator which would mainly repeat input values.
This is not possible with your current setup, you provide to the deserializer only the val node, but you need the entire object to access scale node.
Since using #JsonCreator is undesirable, you could change the deserializer to handle ValueClass:
public class ValueDeserializer extends StdDeserializer<ValueClass> {
public ValueDeserializer() {
super(ValueClass.class);
}
#Override
public ValueClass deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode node = parser.getCodec().readTree(parser);
int scale = node.get("scale").asInt();
ValueClass valueClass = new ValueClass();
JavaType javaType = context.getTypeFactory().constructType(ValueClass.class);
// Introspect the given type
BeanDescription beanDescription = context.getConfig().introspect(javaType);
// Find properties
List<BeanPropertyDefinition> properties = beanDescription.findProperties();
for (BeanPropertyDefinition property : properties) {
String propertyName = property.getName();//get name as in json
String propertyValue = node.get(propertyName).asText();
BigDecimal decimal = new BigDecimal(propertyValue).scaleByPowerOfTen(-scale);
AnnotatedMember accessor = property.getMutator();
accessor.setValue(valueClass, decimal);
}
return valueClass;
}
}
To avoid manually writing property names and setting their values, properties are introspected from java type. This approach is heavily inspired by this answer, you can check it for additional info and possible pitfalls. I believe setting the rest of the fields should be straightforward, using this as a basis.
And simple test:
#JsonDeserialize(using = ValueDeserializer.class)
public class ValueClass {
#JsonProperty("val1")
private BigDecimal value1;
private BigDecimal val2;
private BigDecimal val3;
//setters and getters
#Override
public String toString() {
return "ValueClass{" +
"value1=" + value1 +
", val2=" + val2 +
", val3=" + val3 +
'}';
}
}
Main:
public class Main {
public static void main(String[] args) throws Exception {
String json = "{\"val1\":501, \"scale\":2, \"val2\":407, \"val3\":86}";
ObjectMapper mapper = new ObjectMapper();
ValueClass value = mapper.readValue(json, ValueClass.class);
System.out.println(value);
}
}
Prints - ValueClass{value1=5.01, val2=4.07, val3=0.86}.
I want to create a json object with values of "3D Tour", "Videos", "Photos Only", etc. You can find the enum class below. How can I implement that?
package com.padgea.listing.application.dto;
public enum General implements Catalogue {
Tour("3D Tour"),
Videos("Videos"),
Photos_Only("Photos Only"),
Price_Reduced("Price Reduced"),
Furnished("Furnished"),
Luxury("Luxury");
private final String value;
General(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
I need a output like this
{General : "3D Tour","Videos","Photos Only",etc}
This will return a list of strings containing all the values.
enum General implements Catalogue {
Tour("3D Tour"),
Videos("Videos"),
Photos_Only("Photos Only"),
Price_Reduced("Price Reduced"),
Furnished("Furnished"),
Luxury("Luxury");
private final String value;
General(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static List<String> valuesList() {
return Arrays.stream(General.values())
.map(General::getValue)
.collect(Collectors.toList());
}
}
And a level up you'll do something like
myJson.put("General", General.valuesList())
An output will be
{
"General": ["3D Tour","Videos","Photos Only"]
}
The valid JSON would look like this
{
"General": ["3D Tour","Videos","Photos Only"]
}
If you would use Jackson library for creating your JSON you would need to create a class like this:
public class GeneralDTO {
#JsonProperty("General")
private String[] general;
...
}
Then you would need to create your GeneralDTO object.
You can get all your enum values in an array like this
String[] generalArray = Arrays.stream(General.values())
.map(st -> st.getValue())
.toArray(String[]::new);
Then using the method writeValueAsString of ObjectMapper class (part of Jackson library) you can get JSON string from your GeneralDTO object.
To simplify you can use Map<String, String[]> instead of GeneralDTO
Map<String, String[]> generalObject = new HashMap<>;
generalObject.put("General", generalArray);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(generalObject);
I'm trying to make an array with 2 values per item, like a value with a custom header, and, coming from Ruby, can't find a correct way to do this in Java.
This is for a REST-assured tests that i need to automate.
This is the method that i need to do, I mix the declaration of obj with some sort of ruby way to do it, so the necessity it's more clear:
private String[] getHeaders() {
String[] obj = [
'Signature' => this.getSignature(),
'Timestamp' => this.getTimestamp(),
];
if(getSessionToken() != null) {
obj.sessionToken = this.getSessionToken();
}
}
}
You can achieve that by creating a model. For example:
public class MyModel {
private String signature;
private String timestamp;
public MyModel() {
// constructor
}
public MyModel(String signature, String timestamp){
this.signature = signature;
this.timestamp = timestamp;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
}
Then create an array of your model. You can use:
private static final int MODEL_SIZE = 5;
private MyModel[] models = new MyModel[MODEL_SIZE];
if you already know the size of your array. Or you can use this approach below if you don't know the size of array yet:
private ArrayList<MyModel> models = new ArrayList<>;
private MyModel model;
// Then fill your models
// by using this way
model = new MyModel("My Signature", "My Timestamp");
models.add(model);
// or this way
model = new MyModel();
model.setSignature("My Signature");
model.setTimestamp("My Timestamp");
models.add(model);
Another way to achieve that without creating a model is by using HashMap. This is the example:
List<HashMap<String, String>> objects = new ArrayList<>();
HashMap<String, String> object = new HashMap<>();
object.put("signature", "My Signature");
object.put("timestamp", "My Timestamp");
objects.add(object);
Something like this I suspect is what you want.
class Headers{
public String signature;
public String timeStamp;
}
Headers[] header = new Headers[10];
You probably don't need getters and setters, but you can throw those in too.
I have a class from an SDK that I don't have access to change, but that I would like to serialize a JSON-valid string into.
However, an external API sometimes puts in the wrong type for a Date field.
Long story short: Can I just ignore errors in GSON, or tell Gson to ignore errors on fields, and just get the partial object?
For example, the field should be a double, but I get a Date(number) instead. But I'm not using it anyway, so I don't care, and I don't need the whole process to fail. I just want the parcable fields out, with the faulty fields left null.
NOTE: Writing a deserializer that creates the object I want to have created by Gson defeats the very purpose I propose.
This i a line of code that fails, because a single field is wrong:
Customer customer = gson.fromJson(settings.getCustomerObjectJSONString(), Customer.class);
I would like for it to just skip the field that it cannot parse, because I don't have access to the Customer class, as it is from a generated SDK/library.
I'm aware of two options.
You can use a JSON deserializer implementation to parse JSON elements on your own. However the following example would affect ALL double and Double fields for whatever DTOs passed to that single gson instance, and such a behavior can be deseriable. Unfortunately, I don't know if it's possible to use JsonDeserializer in a "context" way: e.g. let it work for all double and Double fields if those are fields of a certain parent class.
private static final class Dto {
private double primitive;
private Double nullable;
private String string;
}
private static final class FailSafeDoubleJsonDeserializer
implements JsonDeserializer<Double> {
#Override
public Double deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
if ( !element.isJsonPrimitive() ) {
return null;
}
try {
final JsonPrimitive primitive = (JsonPrimitive) element;
final Number number = primitive.getAsNumber();
return number.doubleValue();
} catch ( final NumberFormatException ignored ) {
return null;
}
}
}
private static final JsonDeserializer<Double> failSafeDoubleJsonDeserializer = new FailSafeDoubleJsonDeserializer();
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(double.class, failSafeDoubleJsonDeserializer)
.registerTypeAdapter(Double.class, failSafeDoubleJsonDeserializer)
.create();
public static void main(final String... args) {
dump(gson.fromJson("{\"primitive\": 23, \"nullable\": 42, \"string\": \"foo bar\"}", Dto.class));
dump(gson.fromJson("{\"primitive\": \"whatever\", \"nullable\": \"whatever\", \"string\": \"foo bar\"}", Dto.class));
}
private static void dump(final Dto dto) {
out.println(dto.primitive + " " + dto.nullable + " " + dto.string);
}
Another more low level option can be a type adapter implementation. One advantage of this one over the previous example is that you can annotate failing fields with the #JsonAdapter annotation in DTO classes that are known to be potentially broken.
private static final class Dto {
#JsonAdapter(FailSafeDoubleTypeAdapter.class)
private double primitive;
#JsonAdapter(FailSafeDoubleTypeAdapter.class)
private Double nullable;
private String string;
}
private static final class FailSafeDoubleTypeAdapter
extends TypeAdapter<Double> {
#Override
public void write(final JsonWriter writer, final Double value) {
throw new UnsupportedOperationException();
}
#Override
public Double read(final JsonReader reader)
throws IOException {
final JsonToken peek = reader.peek();
if ( peek != NUMBER ) {
reader.skipValue();
return null;
}
return reader.nextDouble();
}
}
private static final Gson gson = new Gson();
public static void main(final String... args) {
dump(gson.fromJson("{\"primitive\": 23, \"nullable\": 42, \"string\": \"foo bar\"}", Dto.class));
dump(gson.fromJson("{\"primitive\": \"whatever\", \"nullable\": {\"subValue\": \"whatever\"}, \"string\": \"foo bar\"}", Dto.class));
}
private static void dump(final Dto dto) {
out.println(dto.primitive + " " + dto.nullable + " " + dto.string);
}
Thus, both examples generate the following output:
23.0 42.0 foo bar
0.0 null foo bar
for
{"primitive": 23, "nullable": 42, "string": "foo bar"}
and {"primitive": "whatever", "nullable": {"subValue": "whatever"}, "string": "foo bar"}
payloads respectively.
I looked at the problem in another perspective i.e. the main requirement mentioned in the OP is
1) You don't care what value present in a particular field
2) You are not going to use the particular field value, and don't want the deserializer to fail because of invalid data
In the above case, you can mark the particular field as TRANSIENT or STATIC. By default, Gson will exclude all fields marked transient or static.
Example:-
private transient Date joiningDate;
I want to use Jackson to convert a Java object into JSON format. I have a class which looks pretty much the following structure
public Class Event
{
String type;
String timestamp;
String hostname;
String service;
Payload payload;
}
I have the getters and setters for the above fields and also the getters/setters in Payload class.
Here is the json format, i want
{
"type":"end",
"time":"2016-08-01 11:11:11:111",
"origin":{
"hostname":"<hostname>",
"service":"<service>"
},
"version":"1.0"
"data":{ .... }
}
I can't seem to find a jackson way to get the above format, don't know how to put the whole payload object in "data" node and how to put the hostname, service in the "origin" node.
from your question, this is one approach that should showcase on how to solve it. Since you only posted 1 class, I am changing the payload to be a map. It works the same way with other classes as well.
Consider this example:
public class JacksonTest {
public static void main(String[] args) throws JsonProcessingException {
Event e = new Event();
e.type="end";
e.service="<service>";
e.hostname = "<hostname>";
e.timestamp = LocalDateTime.now().toString();
Map<String,String> payload = new HashMap<>();
payload.put("param1", "xyz");
e.payload = payload;
String writeValueAsString = new ObjectMapper().writeValueAsString(e);
System.out.println(writeValueAsString);
}
public static class Event {
#JsonProperty
String type;
#JsonProperty("time")
String timestamp;
#JsonIgnore
String hostname;
#JsonIgnore
String service;
#JsonProperty("data")
Map<String, String> payload;
#JsonProperty("origin")
Map<String,String> getOrigin() {
Map<String,String> tmp = new HashMap<>();
tmp.put("hostname", hostname);
tmp.put("service", service);
return tmp;
}
#JsonProperty("version")
private String getVersion() {
return "1.0";
}
}
}
I annotate the Event class with the necessary properties I want and the names they should have. Since you want the hostname and service to be in a nested setting and not create a new object for it (a new object would be easier as you could just have that serialised), I ignore those and instead use a getter to create the necessary structure as a map.
The output is:
{
"type":"end",
"time":"2016-08-19T16:45:18.072",
"data":{"param1":"xyz"},
"origin":{
"hostname":"<hostname>",
"service":"<service>"
},
"version":"1.0"
}
Regads,
Artur