I have the following class that uses internally a HashMap:
open class I18n<T> {
var i18n: MutableMap<LanguageEnum, T?> = mutableMapOf()
#JsonAnyGetter get
fun add(lang: LanguageEnum, value: T?) {
i18n[lang] = value
}
// ...
}
Thanks to the #JsonAnyGetter annotation, when I serialize this into Json I have the following format:
{
"pt": "Texto exemplo",
"en": "Example text"
}
instead of
{
i18n: {
"pt": "Texto exemplo",
"en": "Example text"
}
}
Now I need to make the way back. I have a HashMap containing the languages key and I need to desserialize it into my I18n object.
The caveat here is that I'm doing this among a lot of reflections and abstractions and it would be really nice if it could work like this:
// Here I am going through the fields of a given POJO.
// One of those fields is a I18n type.
// My model variable is a Map containing the same keys as my POJO field's name, so I'm basically trying to map them all
for (f in fields) {
if (model.containsKey(f.name)) {
// when f.type is I18n, value is a HashMap<string, string>
val value = model[f.name]
f.isAccessible = true
// when f.type is I18n.class, the field is set to an empty instance of I18n because it could not desserialize
f.set(dto, mapper.convertValue(value, f.type))
f.isAccessible = false
}
}
I would not like to do things like:
if (f.type === I18n.class) {
// Special treatment
}
Any ideas? Thanks in advance.
Related
I have simple REST Controller (in Kotlin) method with #RequestBody object.
#PostMappging(...)
fun postTestFunction(#RequestBody data: ExampleObject) {
...
}
Where ExampleObject is:
class ExampleObject(
#JsonProperty("paramOne")
val paramOne: Map<String, Int>,
#JsonProperty("paramTwo")
val paramTwo: String
)
That request can be send with next body:
{
"paramOne": {
"test": 1 // Here I can write any number of Map<String, Int>
},
"paramTwo": "test"
}
But I need another request body something like this:
{
"test": 1, // I need any number of Map<String, Int> without field name "paramOne"
"paramTwo": "test"
}
What should I do to send request body with Map field (String, Int) but without field name?
I found solution. It's need to use #JsonAnySetter annotation.
I changed ExampleObject to:
class ExampleObject(
#JsonProperty("paramTwo")
val paramTwo: String
) {
#JsonProperty("paramOne")
val paramOne = MutableMap<String, Int> = mutableMapOf()
#JsonAnySetter
fun setParamOne(key: String, value: Int) {
paramOne[key] = value
}
}
And now I can send request body the way I wanted. All fields that don't match with declared #JsonProperty annotation will be assigned to property with #JsonAnySetter method.
{
"paramTwo": "data",
"testStringOne": 1,
"testStringTwo": 2,
...
}
I'm working on a Spring-boot project where I receive different format of Json String. My goal is to convert these Json string into an Unified Java class.
I can receive many variations of this Json:
{ "id" : "someId", "type" : "temperature", "value" : 21.0 }
For example, one variation might look like :
{ "id" : "someId", "data" : { "type": "temp", "val" : 21.0 }, "location": "here" }
So these 2 Json must be mapped into the same Java class.
I already have 2 solutions in mind :
First solution
1) Create a Specific Java Class for each Json that I may receive
2) Create a function that takes this specific object and return the Unified Java Class
Second solution
1) Create a JsonNode with the Json String
2) For each key try to match it with a field of the Unified Java Class.
But we have to take into consideration every key possible of a node like "value" or "val".
What is the best approach to solve this problem ?
I'm looking for a solution that could be easy to maintain.
Edit : I'm already using Jackson, but my problem is to map this Json object into an universal Java Class independently of the Json
Edit 2 : The Unified Java Class is a class model that already exist and it's used to store information in our database. So to push information inside our database, I have to convert each json I receive into this unified format
I can see following solutions. E.g. you use Jackson for parse JSON you could declare you custom ObjectMapper:
ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
This mapper contains additional options to ignore unknow properties.
Do you Map<String, Object> as destination class. This is magic key and it works always. Contra: you do not have json validation and have to add many constant keys to read this.
Example:
public static <T> Map<String, T> readMap(String json) throws NGPException {
if (json == null) {
return null;
}
ObjectReader reader = JSON_MAPPER.readerFor(Map.class);
MappingIterator<Map<String, T>> it = reader.readValues(json);
if (it.hasNextValue()) {
Map<String, T> res = it.next();
return res.isEmpty() ? Collections.emptyMap() : res;
}
return Collections.emptyMap();
}
Client:
Map<String, Object> map = readMap("json string");
String id = (String)map.getOrDefault("id", null);
Second way is to build one general class that contain all posiible variables. Additionnaly you have to set option to Jackson ignore unknown fields. In this case, existed fields will be used by Jackson.
Example:
public static <T> T read(String json, Class<T> clazz) throws NGPException {
return mapper.readerFor(clazz).readValue(json);
}
class Response {
private String id;
private String type;
private Double value;
private String location;
private Data data;
public class Data {
private String type;
private String temp;
private Double value;
}
}
Client:
Response response = read("json string", Response.class);
I usually use GSon from Google. It is really usefull. Check gson.fromJson(yourJsonString) in your case.
You can easy use
Gson gson = new Gson();
Data data = gson.fromJson(jsonString, Data.class);
Does exist an Java query string parser that support nested object ?
For example, I have the following query string :
foo=bar&nested[attr]=found&nested[bar]=false
I would to a java map (Map<String, Object>) like this :
list:
foo => bar
nested => list:
attr => found
bar => false
It will be useful to generate json like this :
{"foo": "bar", "nested": {"attr": "found", "bar": false}}
Yes, there are tons of JSON parsers, the easiest one is JSONSimple, check here:
https://www.mkyong.com/java/json-simple-example-read-and-write-json/
It can handle nested objects (arrays of objects) and a lot of other things. Like you can find on the link, if you need to convert objects to/from JSON consider using something more advanced, like Jackson.
Let's write some code to encode
{
filter: {
make: "honda";
model: "civic";
}
}
into the query string filter.make=honda&filter.model=civic
const { escape } = require("querystring");
function encode(queryObj, nesting = "") {
let queryString = "";
const pairs = Object.entries(queryObj).map(([key, val]) => {
// Handle the nested, recursive case, where the value to encode is an object
itself
if (typeof val === "object") {
return encode(val, nesting + `${key}.`);
} else {
// Handle base case, where the value to encode is simply a string.
return [nesting + key, val].map(escape).join("=");
}
});
return pairs.join("&");
}
This is what my class looks like -
public class A {
private Map<String, Object> objects = null;
....
}
My json would be like -
{
"f1" : {
"name" : "some name",
"val" : 3
},
"f2" : {
"arg": {
some field/value pairs
}
}
}
What I want is to specify in the JSON itself the type to which it can be deserialized to. So the value for f1 would be converted to an object of class B and f2 would get converted to object of C.
My code will look like this -
Object o = objects.get("f1");
if (o instanceof B) {
...
} else if (o instanceof C) {
...
}
Is there a way to do this? I want the json to control the deserialization.
Yes, Jackson can use a type identifier if JSON document has it. This is usually done by using annotation #JsonTypeInfo.
There are multiple ways to add/use type identifier, both regarding how it is included in JSON document, and as to what kind of id is being used (type name or Java class name?).
The easiest way to see how things match is to actually start with a POJO, add #JsonTypeInfo annotation, and serialize it to see kind of JSON produced. And once you understood how inclusion works you can modify, if necessary, structure of JSON and/or Java class definition.
I have this object structure which I'm trying to annotate with Jackson to marshal/unmarshal to a JSON file.
public class A {
List<B> bList;
}
public class B {
String attr;
Map<String, C> map;
}
public class C {
#JsonIgnore
String name;
String value;
}
A has a list of B's and B has a map of C's where the key of the map is the name attribute of C. I want the JSON to look like this if possible:
{
"bList" : [
{
"attr":"itsValue"
"KEY_IN_MAP":"VALUE_IN_C",
"KEY_2_IN_MAP":"VALUE_2_IN_C"
}
]
}
Where KEY_IN_MAP is the name of C as the key in B's map and VALUE_IN_C is the value of the value object in the map. I've tried annotating a put method for the map:
#JsonAnySetter
private void put(String name, C value) {
map.put(name, c);
}
But marshaling this gives me:
{
"bList" : [
{
"attr":"itsValue"
"KEY_IN_MAP": {
"value":"VALUE_IN_C",
},
"KEY_2_IN_MAP": {
"value":"VALUE_2_IN_C"
}
}
]
}
Is there any way to get the above mapping with Jackson or any other JSON serializing library? My goal is to get rid of the redundancy of writing "value" every time and compress the file as much as possible.
The map in B could be turned into a list of C but I still need the mapping to be
{"name" : "value"} for each object C
Here are few suggestions that I have:
Make your Map<String,C> to be a Map<String,String>.
You can also make your Map<String,C> to be List<C>.
Please have a look at the other thread which discusses the same.