How can I find the JSON key of a field (jackson)? - java

I have the following problem: I export my user object as usual with the jackson.databind.ObjectMapper and that works fine: ({"address":{"village":"NY"},"prename":"Joe"}).
Now I have to get the key (for address and prename) with Java reflection.
If the field has the annotation #JsonProperty, there is no problem to get this key. But this annotation isn't pressent on all fields (for example the m_address field).
At How does the Jackson mapper know what field in each Json object to assign to a class object? I read that the ObjectMapper tries to call the getter or so.
But I have no clue how I can find the right getter to my field.
I know that this isn't probably the most beautiful way to solve my problem, but I haven't found any method on the ObjectMapper like: mapper.getJSONKeyByName(field).
If something like that exist even better. :)
Is there a way to find the right getter to a field and does something like mapper.getJSONKeyByName(field) exist on the ObjectMapper?
Main.java
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
// Object to JSON as usual
mapper.writeValue(System.out, new User("Joe", new Address("NY")));
// {"address":{"village":"NY"},"prename":"Joe"}
// Lookup with reflection
for (Field field : User.class.getDeclaredFields()) {
field.setAccessible(true);
try {
if (field.isAnnotationPresent(JsonProperty.class)) {
System.out.println("JSON-Key with annotation: " +
field.getAnnotation(JsonProperty.class).value());
// JSON-Key with annotation: prename
} else {
//TODO do something to get "JSON-Key without annotation: address
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
User.java
public class User implements Serializable {
#JsonProperty(value="prename")
#Validationinfo(name="prename", required=true, maxlenght=20)
private String m_name;
private Address m_address;
public User(String name, Address a) {
m_name = name;
m_address = a;
}
#JsonIgnore
public String getName() {
return m_name;
}
public void setName(String name) {
m_name = name;
}
public Address getAddress() {
return m_address;
}
public void setAddress(Address address) {
m_address = address;
}
}
Address.java
public class Address implements Serializable {
#JsonProperty(value="village")
#Validationinfo(name="village", required=false, maxlenght=10)
private String m_village;
public Address(String village) {
m_village = village;
}
public String getVillage() {
return m_village;
}
public void setVillage(String village) {
m_village = village;
}
}
EDIT:
The code is simplified. I have a REST service which does the writeValue part. The reflection part is done in a static recursive method on the User.
The thing is I have a custom Validationinfo annotation (with things like required, maxlength and so on) on my fields and also a name parameter. This name is the same as it is on the #JsonProperty annotation.
On an JavaScript application I want merge the value of the user fields with the ValidationInfos. For that I have to ensure that every validationInfos->name is unique. So I have to prefix the annotated Vaditioninfo->name whith the JSON serialized name/key of its parent (see in the REST respons "address.village").
The rest response I am locking for:
{
"user": {
"prename": "Joe",
"address" : {
"village": "NY"
}
}, "validationInfos": [{
"name": "prename",
"required": true,
"maxlenght": 10
}, {
"name": "address.village",
"required": false,
"maxlenght": 20
}]
}
In JavaScript I planning to do something like:
for (var i = 0; i < data.validationInfos.length;; i++) {
var element = data.validationInfos;
element.value = eval ("data.user." + element.name);
}

You should use jackson introspection instead of pure java reflection. It will allow you to discover json properties mapped to java fields/methods according to your serialization config.
JavaType userType = mapper.getTypeFactory().constructType(User.class);
BeanDescription introspection =
mapper.getSerializationConfig().introspect(userType);
List<BeanPropertyDefinition> properties = introspection.findProperties();
// do some processing over properties...

Related

JSON to Java object - Unrecognized field, not marked as ignorable

I'm trying to transform the following JSON into a java Object.
{
"Data":[
{
"AccountId":"2009852923",
"Currency":"EUR",
"Nickname":"SA 01",
"Account":{
"SchemeName":"BBAN",
"Name":"SA 01",
"Identification":"2009852923"
},
"Servicer":{
"SchemeName":"BICFI",
"Identification":"FNBSZAJJ"
}
},
{
"AccountId":"1028232942",
"Currency":"EUR",
"Nickname":"FNBCREDIT",
"Account":{
"SchemeName":"BBAN",
"Name":"FNBCREDIT",
"Identification":"1028232942"
},
"Servicer":{
"SchemeName":"BICFI",
"Identification":"FNBSZAJJ"
}
}
],
"Links":{
"self":"http://localhost:3000/api/open-banking/accounts/1009427721/transactions"
},
"Meta":{
"total-pages":1
}
}
Using the following DTO (for brevity, the referenced classes haven't been posted).
public class TransactionDTO {
private Data[] data;
private Links links;
private Meta meta;
public Data[] getData () { return data; }
public void setData (Data[] data) { this.data = data; }
public Links getLinks () { return links; }
public void setLinks (Links links) { this.links = links; }
public Meta getMeta () { return meta; }
public void setMeta (Meta meta) { this.meta = meta; }
}
The code to transform the DTO to a Java object being:
private TransactionDTO marshall(String accountTransactionsJSON) {
ObjectMapper objectMapper = new ObjectMapper();
TransactionDTO transactionDTO = null;
try {
transactionDTO = objectMapper.readValue(accountTransactionsJSON, TransactionDTO.class);
} catch (IOException e) {
e.printStackTrace();
}
return transactionDTO;
}
I'm getting this error:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "Data" (class xxx.dto.TransactionDTO), not marked as ignorable (3 known properties: "links", "data", "meta"])
at [Source: java.io.StringReader#48f43b70; line: 2, column: 11] (through reference chain: xxx.dto.TransactionDTO["Data"])
I tried different approach to solve this issue such as:
objectMapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
As well as:
#JsonRootName(value = "data")
But I either get the same problem, or no problems, but the TransactionDTO containing null values only.
I guess the problem is the Data field, but I don't know how to fix this problem (the solutions here don't work for me neither).
Questions
Any idea how to fix this problem ?
Should the accessors case reflect the case in the JSON ?
I solved a similar problem using this aproach
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
Jackson is case sensitive by default. Try this:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
The problem is that your JSON property names (e.g. "Data")
don't match your Java property names (e.g. data).
Besides #psmagin's answer there are two alternative options to fix it:
Keep your Java code unchanged.
And in the JSON contents change all keys (the strings left from the :)
from first-uppercase to first-lowercase:
{
"data":[
{
"accountId":"2009852923",
"currency":"EUR",
"nickname":"SA 01",
"account":{
"schemeName":"BBAN",
"name":"SA 01",
"identification":"2009852923"
},
....
}
Keep the JSON contents unchanged.
And in your Java-code use #JsonProperty annotations
to tell Jackson the corresponding JSON property-names of your Java properties:
public class TransactionDTO {
private #JsonProperty("Data") Data[] data;
private #JsonProperty("Links") Links links;
private #JsonProperty("Meta") Meta meta;
public Data[] getData () { return data; }
public void setData (Data[] data) { this.data = data; }
public Links getLinks () { return links; }
public void setLinks (Links links) { this.links = links; }
public Meta getMeta () { return meta; }
public void setMeta (Meta meta) { this.meta = meta; }
}
and in the same manner in your other Java classes
(Links, Meta, Data, ...)
I would prefer the first option, because property names with first-lowercase
are the established best practice in JSON and Java.
I got this error as I did not intend to map all the JSON fields to my POJO, but only a few. Consequently, it was asking me to mark them ignore. Following sample presents the idea:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Book {
#JsonProperty("kind")
private String kind;
#JsonProperty("id")
private String id;
#JsonProperty("volumeInfo")
private BookInfo bookInfo;
#Override
public String toString() {
return "ClassPojo [kind = " + kind + ", id = " + id + ", bookInfo = " + bookInfo +"]";
}
On the other hand, my Json response carried out 10+ fields.

Jackson JSON key as value in Java

I'm using Jackson in Spring MVC application. I want to use a String value as key name for Java POJO --> JSON
"record": {
"<Dynamic record name String>": {
"value": {
....
}
}
}
So the dynamic record name String could be "abcd","xyz" or any other string value. How can I define my "record" POJO to have a key like that ?
Unfortunately, you cannot have dynamic fields in Java classes (unlike some other languages), so you have two choices:
Using Maps
Using JSON objects (i.e. JsonNode in case of Jackson)
Suppose, you have a data like this:
{
"record": {
"jon-skeet": {
"name": "Jon Skeet",
"rep": 982706
},
"darin-dimitrov": {
"name": "Darin Dimitrov",
"rep": 762173
},
"novice-user": {
"name": "Novice User",
"rep": 766
}
}
}
Create two classes to capture it, one for user and another for the object itself:
User.java:
public class User {
private String name;
private Long rep;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Long getRep() { return rep; }
public void setRep(Long rep) { this.rep = rep; }
#Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", rep=" + rep +
'}';
}
}
Data.java:
public class Data {
private Map<String, User> record;
public Map<String, User> getRecord() { return record; }
public void setRecord(Map<String, User> record) { this.record = record; }
#Override
public String toString() {
return "Data{" +
"record=" + record +
'}';
}
}
Now, parse the JSON (I assume there is a data.json file in the root of your classpath):
public class App {
public static void main(String[] args) throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();
System.out.println(objectMapper.readValue(App.class.getResourceAsStream("/data.json"), Data.class));
System.out.println(objectMapper.readTree(App.class.getResourceAsStream("/data.json")));
}
}
This will output:
Data{record={jon-skeet=User{name='Jon Skeet', rep=982706}, darin-dimitrov=User{name='Darin Dimitrov', rep=762173}, novice-user=User{name='Novice User', rep=766}}}
{"record":{"jon-skeet":{"name":"Jon Skeet","rep":982706},"darin-dimitrov":{"name":"Darin Dimitrov","rep":762173},"novice-user":{"name":"Novice User","rep":766}}}
In case of a Map you can use some static classes, like User in this case, or go completely dynamic by using Maps of Maps (Map<String, Map<String, ...>>. However, if you find yourself using too much maps, consider switching to JsonNodes. Basically, they are the same as Map and "invented" specifically for highly dynamic data. Though, you'll have some hard time working with them later...
Take a look at a complete example, I've prepared for you here.
This is in Kotlin but I have found a solution to the same problem using Jackson.
You don't need the root node "record", so you will need to get rid of it or start one node deeper(you're on your own there) but to turn the list of records that are children of their id into a list of records with id in the object follows:
val node = ObjectMapper().reader().readTree(json)
val recordList = mutableListOf<Record>()
node.fields().iterator().forEach {
val record = record(
it.key,
it.value.get("name").asText(),
it.value.get("rep").asText()
)
recordList.add(event)
}
node.fields() returns a map of children(also maps)
iterating through the parent map you will get the id from the key and then the nested data is in the value (which is another map)
each child of fields is key : value where
key = record id
value = nested data (map)
This solution, you don't need multiple classes to deserialize a list of classes.
I have my data in this format:
{
"0" : {"a": {}}, {"b": {}}, ...
"1" : {"c": {}}, {"d": {}}, ...
.
.
.
}
I am able to capture it into a map using the dynamic capture feature of jackson by using #JsonAnySetter annotation.
public class Destination{
Map<String, Object> destination = new LinkedHashMap<>();
#JsonAnySetter
void setDestination(String key, Object value) {
destination.put(key, value);
}
}

Jackson how to convert array of objects to flat properties in class depends on value

I have the following json object came from remote service:
{
"accumulators": [
{
"balance": "100",
"name": "SMS",
"units": "International SMS"
},
{
"balance": "100",
"name": "VOICE",
"units": "minutes"
},
{
"balance": "50",
"name": "MMS",
"units": "MMS"
}
]
}
I wand to convert map it to the following class depends on the value of object inside that array, so if it is "name": "MMS" then the value must be set to the value of AccumulatorDTO MMS;:
public class BaseDTO {
private AccumulatorDTO messages;
private AccumulatorDTO minutes;
private AccumulatorDTO MMS;
// setters and geters
}
public class AccumulatorDTO {
private int balance;
private String name;
private String units;
// setters and geters
}
Any Idea how to do that using Jackson annotation or custom deserializer?
I can do something like:
AccumulatorDTO[] accumulators = (new ObjectMapper()).convertValue(response.asJson().get("accumulators"), AccumulatorDTO[].class);
Then make iteration over the array and set each property, but really hard to my project structure, I'm looking fo really better solution for a generic purpose (using 1 method for all remote servers, and the deserialization better to be inside DTO somehow, I'm doing some wrapping layer between frontend and backend).
Consider using reflection and name the BaseDTO fields according to the name field in the JSON. Using a #JsonCreator annotated constructor fulfils the requirement of "deserialization better to be inside DTO somehow". E.g.
class BaseDTO {
private AccumulatorDTO sms;
private AccumulatorDTO voice;
private AccumulatorDTO mms;
#JsonCreator
public BaseDTO(#JsonProperty("accumulators") final AccumulatorDTO[] accumulators) {
for (AccumulatorDTO accumulator : accumulators) {
String fieldName = accumulator.getName().toLowerCase();
try {
Field field = getClass().getDeclaredField(fieldName);
field.set(this, accumulator);
} catch (NoSuchFieldException | IllegalAccessException ignored) {
}
}
}
}
And deserialise like this:
BaseDTO accumulators = new ObjectMapper().readValue(response.asJson(), BaseDTO.class);
This will initialise the BaseDTO fields according to the array elements and their name. It will let a field be null if it can't match it to an array element and exceptions are thrown.
Jackson doesn't have an annotation to do what you want AFAIK.
The answer provided from #Manos Nikolaidis helped me so much to code my real answer, his answer is good to start, in my cause some of the values contains a spaces or just a non-standard, so I do create a map to map between fields on JSON and the class:
#JsonCreator
public AccountDTO(#JsonProperty("accumulators") final AccumulatorDTO[] accumulators) {
HashMap<String, String> accumulatorsMap = new HashMap<>();
// key is value from JSON, value is field name of class
accumulatorsMap.put("intl sms", "internationalSMS");
accumulatorsMap.put("voice", "minutes");
accumulatorsMap.put("mms", "MMS");
accumulatorsMap.put("voicemessage", "voiceMessages");
accumulatorsMap.put("message", "messages");
for (AccumulatorDTO accumulator : accumulators) {
String fieldName = accumulator.getName().toLowerCase();
try {
Field field = getClass().getDeclaredField(accumulatorsMap.get(fieldName));
field.set(this, accumulator);
} catch (NoSuchFieldException | IllegalAccessException ignored) {
}
}
}
I created this Jackson custom deserializer for your BaseDTO class that satisfies your requirement. It looks for the "balance" property and when it found it knows that the following are "name" and "units" so it takes them. Then it switchs on the "name" property and it sets the current AccumulatorDTO to the right field of the BaseDTO class.
public class CustomDeserializer extends StdDeserializer<BaseDTO>{
/**
*
*/
private static final long serialVersionUID = 1L;
public CustomDeserializer(Class<BaseDTO> t) {
super(t);
}
#Override
public BaseDTO deserialize(JsonParser jp, DeserializationContext dc)
throws IOException, JsonProcessingException {
BaseDTO bd = new BaseDTO();
JsonToken currentToken = null;
while ((currentToken = jp.nextValue()) != null) {
if (jp.getCurrentName() != null && jp.getCurrentName().equals("balance"))
{
System.out.println(jp.getCurrentName());
AccumulatorDTO adto = new AccumulatorDTO();
adto.setBalance(Integer.parseInt(jp.getValueAsString()));
currentToken = jp.nextValue();
adto.setName(jp.getValueAsString());
currentToken = jp.nextValue();
adto.setUnits(jp.getValueAsString());
switch (adto.getName().toLowerCase())
{
case "sms":
bd.setMessages(adto);
break;
case "voice":
bd.setMinutes(adto);
break;
case "mms":
bd.setMMS(adto);
break;
}
}
}
return bd;
}
}
I tested it with this simple program:
public class JsonArrayExampleApplication {
public static void main(String[] args) {
//
String json = "{\"accumulators\": [{\"balance\": \"100\",\"name\": \"SMS\",\"units\": \"International SMS\"" +
"},{\"balance\": \"100\",\"name\": \"VOICE\",\"units\": \"minutes\"},{\"balance\": \"50\",\"name\": \"MMS\"," +
"\"units\": \"MMS\"}]}";
ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("MyModule");
mod.addDeserializer(BaseDTO.class, new CustomDeserializer(BaseDTO.class));
mapper.registerModule(mod);
BaseDTO bdto = null;
try {
bdto = mapper.readValue(json, BaseDTO.class);
}
catch (IOException e) {
e.printStackTrace();
}
System.out.println("\n--- JSON to JAVA ---\n" + bdto);
}
}
and I got the following output that seems to be ok, because every AccumulatorDTO has been associated to the right property.
--- JSON to JAVA ---
BaseDTO [messages=AccumulatorDTO [balance=100, name=SMS, units=International SMS], minutes=AccumulatorDTO [balance=100, name=VOICE, units=minutes], MMS=AccumulatorDTO [balance=50, name=MMS, units=MMS]]

Renaming JSON fields after the JSON is parsed, but before it's returned as a response [duplicate]

Is it possible: to have one field in class, but different names for it during serialization/deserialization in Jackson library?
For example, I have class "Coordiantes".
class Coordinates{
int red;
}
For deserialization from JSON want to have format like this:
{
"red":12
}
But when I will serialize object, result should be like this one:
{
"r":12
}
I tried to implement this by applying #JsonProperty annotation both on getter and setter (with different values):
class Coordiantes{
int red;
#JsonProperty("r")
public byte getRed() {
return red;
}
#JsonProperty("red")
public void setRed(byte red) {
this.red = red;
}
}
but I got an exception:
org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "red"
Just tested and this works:
public class Coordinates {
byte red;
#JsonProperty("r")
public byte getR() {
return red;
}
#JsonProperty("red")
public void setRed(byte red) {
this.red = red;
}
}
The idea is that method names should be different, so jackson parses it as different fields, not as one field.
Here is test code:
Coordinates c = new Coordinates();
c.setRed((byte) 5);
ObjectMapper mapper = new ObjectMapper();
System.out.println("Serialization: " + mapper.writeValueAsString(c));
Coordinates r = mapper.readValue("{\"red\":25}",Coordinates.class);
System.out.println("Deserialization: " + r.getR());
Result:
Serialization: {"r":5}
Deserialization: 25
You can use #jsonAlias which got introduced in jackson 2.9.0
Example:
public class Info {
#JsonAlias({ "red" })
public String r;
}
This uses r during serialization, but allows red as an alias during deserialization. This still allows r to be deserialized as well, though.
You can use a combination of #JsonSetter, and #JsonGetter to control the deserialization, and serialization of your property, respectively. This will also allow you to keep standardized getter and setter method names that correspond to your actual field name.
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.JsonGetter;
class Coordinates {
private int red;
//# Used during serialization
#JsonGetter("r")
public int getRed() {
return red;
}
//# Used during deserialization
#JsonSetter("red")
public void setRed(int red) {
this.red = red;
}
}
Edit: Updated the documentation links, as the fasterxml GitHub pages are now returning 404.
I would bind two different getters/setters pair to one variable:
class Coordinates{
int red;
#JsonProperty("red")
public byte getRed() {
return red;
}
public void setRed(byte red) {
this.red = red;
}
#JsonProperty("r")
public byte getR() {
return red;
}
public void setR(byte red) {
this.red = red;
}
}
It's possible to have normal getter/setter pair. You just need to specify access mode in #JsonProperty
Here is unit test for that:
public class JsonPropertyTest {
private static class TestJackson {
private String color;
#JsonProperty(value = "device_color", access = JsonProperty.Access.READ_ONLY)
public String getColor() {
return color;
};
#JsonProperty(value = "color", access = JsonProperty.Access.WRITE_ONLY)
public void setColor(String color) {
this.color = color;
}
}
#Test
public void shouldParseWithAccessModeSpecified() throws Exception {
String colorJson = "{\"color\":\"red\"}";
ObjectMapper mapper = new ObjectMapper();
TestJackson colotObject = mapper.readValue(colorJson, TestJackson.class);
String ser = mapper.writeValueAsString(colotObject);
System.out.println("Serialized colotObject: " + ser);
}
}
I got the output as follows:
Serialized colotObject: {"device_color":"red"}
You can use this variant:
import lombok.Getter;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
//...
#JsonProperty(value = "rr") // for deserialization
#Getter(onMethod_ = {#JsonGetter(value = "r")}) // for serialization
private String rrrr;
with Lombok getter
This was not what I was expecting as a solution (though it is a legitimate use case). My requirement was to allow an existing buggy client (a mobile app which already released) to use alternate names.
The solution lies in providing a separate setter method like this:
#JsonSetter( "r" )
public void alternateSetRed( byte red ) {
this.red = red;
}
Annotating with #JsonAlias which got introduced with Jackson 2.9+, without mentioning #JsonProperty on the item to be deserialized with more than one alias(different names for a json property) works fine.
I used com.fasterxml.jackson.annotation.JsonAlias for package consistency with com.fasterxml.jackson.databind.ObjectMapper for my use-case.
For e.g.:
#Data
#Builder
public class Chair {
#JsonAlias({"woodenChair", "steelChair"})
private String entityType;
}
#Test
public void test1() {
String str1 = "{\"woodenChair\":\"chair made of wood\"}";
System.out.println( mapper.readValue(str1, Chair.class));
String str2 = "{\"steelChair\":\"chair made of steel\"}";
System.out.println( mapper.readValue(str2, Chair.class));
}
just works fine.
I know its an old question but for me I got it working when I figured out that its conflicting with Gson library so if you are using Gson then use #SerializedName("name") instead of #JsonProperty("name") hope this helps
They must have included this as a feature, because now setting a different #JsonProperty for a getter and setter results in exactly what you would expect (different property name during serialization and deserialization for the same field). Jackson version 2.6.7
In my case, I had to read inputs in Brazilian portuguese and generate outputs in english.
So, a workaround which worked for me was using #JsonAlias instead of #JsonProperty:
// pseudo-java
#Value
public class User {
String username;
public User(
#JsonAlias("nome_usuario") String username) {
// ...
}
}
You can write a serialize class to do that:
public class Symbol
{
private String symbol;
private String name;
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class SymbolJsonSerializer extends JsonSerializer<Symbol> {
#Override
public void serialize(Symbol symbol, JsonGenerator jgen, SerializerProvider serializers) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("symbol", symbol.getSymbol());
//Changed name to full_name as the field name of Json string
jgen.writeStringField("full_name", symbol.getName());
jgen.writeEndObject();
}
}
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Symbol.class, new SymbolJsonSerializer());
mapper.registerModule(module);
//only convert non-null field, option...
mapper.setSerializationInclusion(Include.NON_NULL);
String jsonString = mapper.writeValueAsString(symbolList);
For Kotlin guys:
data class TestClassDTO(
#JsonProperty("user_name")
val username: String
)
You will successfull handle {"user_name": "John"} from POST payload in RestControllers
But when you need to serialize back with same name of #JsonProperty you can use this reflexe-approach
fun Any.forceSerialize(separator: String, sorted: Boolean = false): String {
var fieldNameToAnnotatedNameMap = this.javaClass.declaredFields.map { it.name }.associateWith { fieldName ->
val jsonFieldName =
this::class.primaryConstructor?.parameters?.first { it.name == fieldName }?.annotations?.firstOrNull { it is JsonProperty }
val serializedName = if (jsonFieldName != null) (jsonFieldName as JsonProperty).value else fieldName
serializedName
}
if (sorted)
fieldNameToAnnotatedNameMap = fieldNameToAnnotatedNameMap.toList().sortedBy { (_, value) -> value}.toMap()
return fieldNameToAnnotatedNameMap.entries.joinToString(separator) { e ->
val field = this::class.memberProperties.first { it.name == e.key }
"${e.value}=${field.javaGetter?.invoke(this)}"
}
}
Use both JsonAlias and JsonProperty on the attribute.
data class PayoutMethodCard(
#JsonProperty("payment_account_id")
#JsonAlias("payout_account_id")
val payoutAccountId: Long
)
In this case paymentAccountId can serialized from JSON either by payment_account_id or by payout_account_id, but when deserialized back to JSON JSONProperty will be used, and payment_account_id will be used.

Why do I get "Failed to bounce to type" when I turn JSON from Firebase into Java objects?

[Disclosure: I am an engineer at Firebase. This question is meant to be a reference question to answer many questions in one go.]
I have the following JSON structure in my Firebase database:
{
"users": {
"-Jx5vuRqItEF-7kAgVWy": {
"handle": "puf",
"name": "Frank van Puffelen",
"soId": 209103
},
"-Jx5w3IOHD2kRFFgkMbh": {
"handle": "kato",
"name": "Kato Wulf",
"soId": 394010
},
"-Jx5x1VWs08Zc5S-0U4p": {
"handle": "mimming",
"name": "Jenny Tong",
"soId": 839465
}
}
}
I am reading this with the following code:
private static class User {
String handle;
String name;
public String getHandle() { return handle; }
public String getName() { return name; }
}
Firebase ref = new Firebase("https://stackoverflow.firebaseio.com/32108969/users");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot usersSnapshot) {
for (DataSnapshot userSnapshot : usersSnapshot.getChildren()) {
User user = userSnapshot.getValue(User.class);
System.out.println(user.toString());
}
}
#Override
public void onCancelled(FirebaseError firebaseError) { }
});
But I get this error:
Exception in thread "FirebaseEventTarget" com.firebase.client.FirebaseException: Failed to bounce to type
How can I read my users into Java objects?
Firebase uses Jackson to allow serialization of Java objects to JSON and deserialization of JSON back into Java objects. You can find more about Jackson on the Jackson website and this page about Jackson annotations.
In the rest of this answer, we’ll show a few common ways of using Jackson with Firebase.
Loading complete users
The simplest way of loading the users from Firebase into Android is if we create a Java class that completely mimics the properties in the JSON:
private static class User {
String handle;
String name;
long stackId;
public String getHandle() { return handle; }
public String getName() { return name; }
public long getStackId() { return stackId; }
#Override
public String toString() { return "User{handle='"+handle+“', name='"+name+"', stackId="+stackId+"\’}”; }
}
We can use this class in a listener:
Firebase ref = new Firebase("https://stackoverflow.firebaseio.com/32108969/users");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot usersSnapshot) {
for (DataSnapshot userSnapshot : usersSnapshot.getChildren()) {
User user = userSnapshot.getValue(User.class);
System.out.println(user.toString());
}
}
#Override
public void onCancelled(FirebaseError firebaseError) { }
});
You may note that the User class follow the JavaBean property pattern. Every JSON property maps by a field in the User class and we have a public getter method for each field. By ensuring that all properties are mapped with the exact same name, we ensure that Jackson can automatically map them.
You can also manually control the mapping by putting Jackson annotations on your Java class, and its fields and methods. We’ll cover the two most common annotations (#JsonIgnore and #JsonIgnoreProperties) below.
Partially loading users
Say that you only care about the user’s name and handle in your Java code. Let’s remove the stackId and see what happens:
private static class User {
String handle;
String name;
public String getHandle() { return handle; }
public String getName() { return name; }
#Override
public String toString() {
return "User{handle='" + handle + “\', name='" + name + "\’}”;
}
}
If we now attach the same listener as before and run the program, it will throw an exception:
Exception in thread "FirebaseEventTarget" com.firebase.client.FirebaseException: Failed to bounce to type
at com.firebase.client.DataSnapshot.getValue(DataSnapshot.java:187)
at com.firebase.LoadPartialUsers$1.onDataChange(LoadPartialUsers.java:16)
The “failed to debounce type” indicates that Jackson was unable to deserialize the JSON into a User object. In the nested exception it tells us why:
Caused by: com.shaded.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "stackId" (class com.firebase.LoadPartialUsers$User), not marked as ignorable (2 known properties: , "handle", "name"])
at [Source: java.io.StringReader#43079089; line: 1, column: 15] (through reference chain: com.firebase.User["stackId"])
at com.shaded.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:79)
Jackson found a property stackId in the JSON and doesn’t know what to do with it, so it throws an exception. Luckily there is an annotation that we can use to tell it to ignore specific properties from the JSON when mapping it to our User class:
#JsonIgnoreProperties({ "stackId" })
private static class User {
...
}
If we not run the code with our listener again, Jackson will know that it can ignore stackId in the JSON and it will be able to deserialize the JSON into a User object again.
Since adding properties to the JSON is such a common practice in Firebase applications, you may find it more convenient to simply tell Jackson to ignore all properties that don’t have a mapping in the Java class:
#JsonIgnoreProperties(ignoreUnknown=true)
private static class User {
...
}
Now if we add properties to the JSON later, the Java code will still be able to load the Users. Just keep in mind that the User objects won’t contain all information that was present in the JSON, so be careful when writing them back to Firebase again.
Partially saving users
One reason why it is nice to have a custom Java class, is that we can add convenience methods to it. Say that we add a convenience method that gets the name to display for a user:
private static class User {
String handle;
String name;
public String getHandle() { return handle; }
public String getName() { return name; }
#JsonIgnore
public String getDisplayName() {
return getName() + " (" + getHandle() + ")";
}
#Override
public String toString() {
return "User{handle='" + handle + "\', name='" + name + "\', displayName='" + getDisplayName() + "'}";
}
}
Now let's read the users from Firebase and write them back into a new location:
Firebase srcRef = new Firebase("https://stackoverflow.firebaseio.com/32108969/users");
final Firebase copyRef = new Firebase("https://stackoverflow.firebaseio.com/32108969/copiedusers");
srcRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot usersSnapshot) {
for (DataSnapshot userSnapshot : usersSnapshot.getChildren()) {
User user = userSnapshot.getValue(User.class);
copyRef.child(userSnapshot.getKey()).setValue(user);
}
}
#Override
public void onCancelled(FirebaseError firebaseError) { }
});
The JSON in the copiedusers node looks like this:
"copiedusers": {
"-Jx5vuRqItEF-7kAgVWy": {
"displayName": "Frank van Puffelen (puf)",
"handle": "puf",
"name": "Frank van Puffelen"
},
"-Jx5w3IOHD2kRFFgkMbh": {
"displayName": "Kato Wulf (kato)",
"handle": "kato",
"name": "Kato Wulf"
},
"-Jx5x1VWs08Zc5S-0U4p": {
"displayName": "Jenny Tong (mimming)",
"handle": "mimming",
"name": "Jenny Tong"
}
}
That’s not the same as the source JSON, because Jackson recognizes the new getDisplayName() method as a JavaBean getter and thus added a displayName property to the JSON it outputs. We solve this problem by adding a JsonIgnore annotation to getDisplayName().
#JsonIgnore
public String getDisplayName() {
return getName() + "(" + getHandle() + ")";
}
When serializing a User object, Jackson will now ignore the getDisplayName() method and the JSON we write out will be the same as what we got it.
The 9.x (and higher) versions of the Firebase SDK for Android/Java stopped including Jackson for serializing/deserializing Java<->JSON. The newer SDK instead provides a minimal set of custom annotations to allow control over the most common customization needs, while having a minimal impact on the resulting JAR/APK size.
My original answer is still valid if you're:
Using the Firebase 2.x SDKs
Using the Firebase 9.0 or higher SDKs, but use Jackson for serializing/deserializing Java<->JSON.
The rest of this answer covers how to handle serialization/deserialization scenarios in Firebase SDK 9.0 or higher.
Data structure
We'll start with this JSON structure in our Firebase Database:
{
"-Jx86I5e8JBMZ9tH6W3Q" : {
"handle" : "puf",
"name" : "Frank van Puffelen",
"stackId" : 209103,
"stackOverflowId" : 209103
},
"-Jx86Ke_fk44EMl8hRnP" : {
"handle" : "mimming",
"name" : "Jenny Tong",
"stackId" : 839465
},
"-Jx86N4qeUNzThqlSMer" : {
"handle" : "kato",
"name" : "Kato Wulf",
"stackId" : 394010
}
}
Loading complete users
At its most basic, we can load each user from this JSON into the following Java class:
private static class CompleteUser {
String handle;
String name;
long stackId;
public String getHandle() { return handle; }
public String getName() { return name; }
public long getStackId() { return stackId; }
#Override
public String toString() { return "User{handle='"+handle+"', name='"+name+"', stackId="+stackId+ "'}"; }
}
If we declare the fields to be public, we don't even need the getters:
private static class CompleteUser {
public String handle;
public String name;
public long stackId;
}
Partially loading users
We can also partially load a user, for example with:
private static class PartialUser {
String handle;
String name;
public String getHandle() {
return handle;
}
public String getName() { return name; }
#Override
public String toString() {
return "User{handle='" + handle + "', NAME='" + name + "''}";
}
}
When we use this class to load the users from the same JSON, the code runs (unlike the Jackson variant mentioned in my other answer). But you'll see a warning in your logging output:
WARNING: No setter/field for stackId found on class Annotations$PartialUser
So get rid of that, we can annotate the class with #IgnoreExtraProperties:
#IgnoreExtraProperties
private static class PartialUser {
String handle;
String name;
public String getHandle() {
return handle;
}
public String getName() { return name; }
#Override
public String toString() {
return "User{handle='" + handle + "', NAME='" + name + "''}";
}
}
Partially saving users
As before, you might want to add a calculated property to the user. You'd want to ignore such a property when saving the data back to the database. To do this, you can annotate the property/getter/setter/field with #Exclude:
private static class OvercompleteUser {
String handle;
String name;
long stackId;
public String getHandle() { return handle; }
public String getName() { return name; }
public long getStackId() { return stackId; }
#Exclude
public String getTag() { return getName() + " ("+getHandle()+")"; }
#Override
public String toString() { return "User{handle='"+handle+"', name='"+name+"', stackId="+stackId+ "'}"; }
}
Now when writing a user to the database, the value of getTag() will be ignored.
Using a different property name in the JSON than in the Java code
You can also specify what name a field/getter/setter from the Java code should get in the JSON in the database. To do this: annotate the field/getter/setter with #PropertyName().
private static class UserWithRenamedProperty {
String handle;
String name;
#PropertyName("stackId")
long stackOverflowId;
public String getHandle() { return handle; }
public String getName() { return name; }
#PropertyName("stackId")
public long getStackOverflowId() { return stackOverflowId; }
#Override
public String toString() { return "User{handle='"+handle+"', name='"+name+"', stackId="+stackOverflowId+ "'}"; }
}
In general it's best to use the default mapping between Java<->JSON that the Firebase SDK uses. But #PropertyName may be needed when you have a pre-existing JSON structure that you can't otherwise map to Java classes.
Because your wrong query path folder in root.this is example
My code:
private void GetUpdates(DataSnapshot snapshot){
romchat.clear();
for (DataSnapshot ds: snapshot.getChildren()){
Rowitemroom row = new Rowitemroom();
row.setCaption(ds.getValue(Rowitemroom.class).getCaption());
row.setFileUrl(ds.getValue(Rowitemroom.class).getFileUrl());
romchat.add(row);
/* Rowitemroom row = snapshot.getValue(Rowitemroom.class);
String caption = row.getCaption();
String url = row.getFileUrl();*/
}
if (romchat.size()>0){
adapter = new CustomRoom(context,romchat);
recyclerView.setAdapter(adapter);
}else {
Toast.makeText(context, "No data", Toast.LENGTH_SHORT).show();
}
}
db_url ="your apps`enter code here`.appspot.com/admins"

Categories