Jackson: Split a json and populate known and unknown properties - java

I'm coding an Spring-boot service and I'm using jackson ObjectMapper in order to handle with my jsons.
I need to split a json like this:
{
"copy": {
"mode": "mode",
"version": "version"
},
"known": "string value",
"unknown": {
"field1": "sdf",
"field2": "sdfdf"
},
"unknown2": "sdfdf"
}
I mean, my bean is like this:
public class MyBean {
private CopyMetadata copy;
private String known;
private Object others;
}
I'd like to populate known fields to MyBean properties, and move the other unknown properties inside MyBean.others property.
Known properties are which are placed as a field inside MyBean.
Any ideas?

A possible solution to this problem is to use the jackson annotations #JsonAnyGetter and #JsonAnySetter
Your Model Mybean.class should look something like this and it should work
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
public class MyBean {
private CopyMetadata copy;
private String known;
private Map<String, Object> others = new HashMap<>();
public CopyMetadata getCopy() {
return copy;
}
public void setCopy(CopyMetadata copy) {
this.copy = copy;
}
public String getKnown() {
return known;
}
public void setKnown(String known) {
this.known = known;
}
public Map<String, Object> getOthers() {
return others;
}
public void setOthers(Map<String, Object> others) {
this.others = others;
}
#JsonAnyGetter
public Map<String, Object> getUnknownFields() {
return others;
}
#JsonAnySetter
public void setUnknownFields(String name, Object value) {
others.put(name, value);
}
}

Related

How to use dynamic property names for a Json object

How can we make the JSON property name dynamic. For example
public class Value {
#JsonProperty(value = "value")
private String val;
public void setVal(String val) {
this.val = val;
}
public String getVal() {
return val;
}
}
when serializing this object it's saved as {"value": "actual_value_saved"} but I want to make the key also dynamic like {"new_key": "actual_value_saved"}. Any help is much appreciated.
You can use JsonAnySetter JsonAnyGetter annotations. Behind you can use Map instance. In case you have always one-key-object you can use Collections.singletonMap in other case use HashMap or other implementation. Below example shows how easy you can use this approach and create as many random key-s as you want:
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
public class JsonApp {
public static void main(String[] args) throws Exception {
DynamicJsonsFactory factory = new DynamicJsonsFactory();
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(factory.createUser("Vika")));
System.out.println(mapper.writeValueAsString(factory.createPhone("123-456-78-9")));
System.out.println(mapper.writeValueAsString(factory.any("val", "VAL!")));
}
}
class Value {
private Map<String, String> values;
#JsonAnySetter
public void put(String key, String value) {
values = Collections.singletonMap(key, value);
}
#JsonAnyGetter
public Map<String, String> getValues() {
return values;
}
#Override
public String toString() {
return values.toString();
}
}
class DynamicJsonsFactory {
public Value createUser(String name) {
return any("name", name);
}
public Value createPhone(String number) {
return any("phone", number);
}
public Value any(String key, String value) {
Value v = new Value();
v.put(Objects.requireNonNull(key), Objects.requireNonNull(value));
return v;
}
}
Above code prints:
{"name":"Vika"}
{"phone":"123-456-78-9"}
{"val":"VAL!"}
You could have all the possible names as variables, and annotate them so they are ignored if null. This way you only get in your JSON the one that has a value
Then change your setter to feed into the variable mapped to the key you want.
class Value {
#JsonProperty("val")
#JsonInclude(JsonInclude.Include.NON_NULL)
private String val;
#JsonProperty("new_key")
#JsonInclude(JsonInclude.Include.NON_NULL)
private String newKey;
#JsonProperty("any_random_string")
#JsonInclude(JsonInclude.Include.NON_NULL)
private String anyRandomString;
public void setVal(String s) {
if(/* condition1 */)
this.val = s;
else if (/* condition2 */) {
this.newKey = s;
} else if (/* condition3 */) {
this.anyRandomString = s;
}
}
}
Good question #Prasad, This answer is not about JAVA or SPRING BOOT, I'm just putting this answer because I searched to do this with node and hope this helps somebody somehow. In JAVASCRIPT we can add dynamic property names for JSON objects as below
var dogs = {};
var dogName = 'rocky';
dogs[dogName] = {
age: 2,
otherSomething: 'something'
};
dogName = 'lexy';
dogs[dogName] = {
age: 3,
otherSomething: 'something'
};
console.log(dogs);
But when we need to dynamically change the name we have to
get that property
and create another property with the same content and new name
and delete the old property from JSON
assign the new property to the JSON
is there a another way to dynamically change JSON name except for this method, thanks in advance

Jackson and deserialisation when you don't know the JSON tag name ahead of time?

I want to use Jackson to deserialise my JSON, from Jira, into a set of POJOs. I have most of what I want working beautifully, now I just have to decode the custom field values.
My input JSON looks like:
{
"expand": "renderedFields,names,schema,operations,editmeta,changelog,versionedRepresentations",
"id": "104144",
"self": "https://jira.internal.net/rest/api/2/issue/104144",
"key": "PRJ-524",
"fields": {
"summary": "Redo unit tests to load from existing project",
"components": [],
"customfield_10240": {
"self": "https://jira.internal.net/rest/api/2/customFieldOption/10158",
"value": "Normal",
"id": "10158"
}
}
I can trivially load the summary and components, since I know ahead of time what the name of those JSON elements are, and can define them in my POJO:
#JsonIgnoreProperties({ "expand", "self", "id", })
public class JiraJson
{
private JiraFields fields;
private String key;
public JiraFields getFields()
{
return fields;
}
public String getKey()
{
return key;
}
public void setFields(JiraFields newFields)
{
fields = newFields;
}
public void setKey(String newKey)
{
key = newKey;
}
}
And similarly for JiraFields:
#JsonIgnoreProperties({ "issuetype", "priority", "status" })
public class JiraFields
{
private List<JiraComponent> components;
private String summary;
public List<JiraComponent> getComponents()
{
return components;
}
public String getSummary()
{
return summary;
}
public void setComponents(List<JiraComponent> newComponents)
{
components = newComponents;
}
public void setSummary(String newSummary)
{
summary = newSummary;
}
}
However, the field custom_10240 actually differs depending on which Jira system this is run against, on one it is custom_10240, on another it is custom_10345, so I cannot hard-code this into the POJO. Using another call, it is possible to know at runtime, before the deserialisation starts, what the name of the field is, but this is not possible at compile time.
Assuming that I want to map the value field into a String on JiraFields called Importance, how do I go about doing that? Or perhaps simpler, how to map this Importance onto a JiraCustomField class?
You can use a method annotated with #JsonAnySetter that accepts all properties that are undefined (and not ignored). in case of a Json Object (like the custom field in the question) Jackson passes a Map that contains all the Object properties (it may even contain Map values in case of nested objects). You can now at run time extract whatever properties you want:
#JsonIgnoreProperties({ "issuetype", "priority", "status" })
public class JiraFields
{
private List<JiraComponent> components;
private String summary;
private String importance;
// getter/setter omitted for brevity
#JsonAnySetter
public void setCustomField(String name, Object value) {
System.out.println(name); // will print "customfield_10240"
if (value instanceof Map) { // just to make sure we got a Json Object
Map<String, Object> customfieldMap = (Map<String, Object>)value;
if (customfieldMap.containsKey("value")) { // check if object contains "value" property
setImportance(customfieldMap.get("value").toString());
}
}
}
}
After searching further, I finally found the JsonAlias annotation. This is still defined at compile time, but I had something that I could search further on!
Further searching, and I found PropertyNamingStrategy, which allows you to rename what JSON field name is expected for a setter/field. This has the advantage in that this is done via a method, and the class can be constructed at runtime.
Here is the class that I used to perform this mapping:
import java.util.Map;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
public final class CustomFieldNamingStrategy
extends PropertyNamingStrategy
{
private static final long serialVersionUID = 8263960285216239177L;
private final Map<String, String> fieldRemapping;
private final Map<String, String> reverseRemapping;
public CustomFieldNamingStrategy(Map<String, String> newFieldRemappings)
{
fieldRemapping = newFieldRemappings;
reverseRemapping = fieldRemapping.entrySet()//
.stream()//
.collect(Collectors.toMap(Map.Entry::getValue,
Map.Entry::getKey));
}
#Override
public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName)
{
if (field.getDeclaringClass().getName().equals(JiraFields.class.getName()))
{
return reverseRemapping.getOrDefault(defaultName, defaultName);
}
return defaultName;
}
#Override
public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method,
String defaultName)
{
if (method.getDeclaringClass().getName().equals(JiraFields.class.getName()))
{
return reverseRemapping.getOrDefault(defaultName, defaultName);
}
return defaultName;
}
#Override
public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method,
String defaultName)
{
if (method.getDeclaringClass().getName().equals(JiraFields.class.getName()))
{
return reverseRemapping.getOrDefault(defaultName, defaultName);
}
return defaultName;
}
}

Deserialize values in a tree structured JSON using Jackson

Need to deserialize a JSON structure like below using Jackson, this is a response from a REST API and I am using Spring RestTemplate with MappingJackson2HttpMessageConverter to parse it.
Problems:
1. All elements can have one or more child elements of its own type (e.g. root has children : childABC & childAnyRandomName)
2. Child nodes do not have a standard naming convention, names can be any random string.
{
"FooBar": {
"root": {
"name": "test1",
"value": "900",
"childABC": {
"name": "test2",
"value": "700",
"childXYZ": {
"name": "test3",
"value": "600"
}
},
"childAnyRandomName": {
"name": "test4",
"value": "300"
}
}
}
}
As per my understanding, a POJO to store this could be something like this:
import java.util.Map;
public class TestObject {
private String name;
private String value;
private Map<String, TestObject> children;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Map<String, TestObject> getChildren() {
return children;
}
public void setChildren(Map<String, TestObject> children) {
this.children = children;
}
}
I have no control over the actual JSON that I am parsing so If Jackson does not work, I will have to push all of it in JsonNode and build this object structure with custom logic.
Disclaimer: I am relatively new to Jackson, so please don't mind if this was a classic text book solution that I didn't read, just point me to the documentation location.
In your bean, have the attributes you know are there:
private String blah;
#JsonProperty("blah")
#JsonSerialize(include = Inclusion.NON_NULL)
public String getBlah() {
return blah;
}
#JsonProperty("blah")
public void setBlah(String blah) {
this.blah = blah;
}
Then, add the following, which is kinda like a catch-all for any properties which you haven't explicitly mapped that appear on the JSON:
private final Map<String, JsonNode> properties = new HashMap<String, JsonNode>();
#JsonAnyGetter
public Map<String, JsonNode> getProperties() {
return properties;
}
#JsonAnySetter
public void setProperty(String key, JsonNode value) {
properties.put(key, value);
}
Alternatively, just map known complex objects to Map fields (IIRC, complex objects under that will also end up in maps).

Java structure for Datatables editor json

I need a Java data structure for some JSON data passed to me by the Datatables Editor. The format of the data received is this:
{
"action":"edit",
"data": {
"1009558":{
"weekNumber":"2"
... (more properties)
}
}
}
Here's the full documentation: https://editor.datatables.net/manual/server
Edit: The documentation shows the data sent as form params. I am stringifying the data and sending it as JSON. An example is above.
"1009558" is the row ID. If there are multiple rows sent by the editor, there would be multiple array entries (each with an ID).
Can anyone offer some advice on how to make a Java data structure for deserialization (by Spring MVC)? I can map "action" easy enough, but I'm getting stuck on the "data" element.
I'd rather suggest you to use jackson.
Here's an example, that you're asking for:
package com.github.xsavikx.jackson;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
public class JacksonTest {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, DatabaseRow> data = new HashMap<>();
DatabaseRow row = new DatabaseRow(2, "someData");
data.put("1009558", row);
String action = "action";
DatabaseEntry dbEntry = new DatabaseEntry();
dbEntry.setAction(action);
dbEntry.setData(data);
System.out.println(objectMapper.writeValueAsString(dbEntry));
}
}
And the result:
{"action":"action","data":{"1009558":{"weekNumber":2,"someData":"someData"}}}
Models:
package com.github.xsavikx.jackson;
import java.util.Map;
public class DatabaseEntry {
private String action;
private Map<String, DatabaseRow> data;
public DatabaseEntry() {
}
public DatabaseEntry(String action, Map<String, DatabaseRow> data) {
this.action = action;
this.data = data;
}
public Map<String, DatabaseRow> getData() {
return data;
}
public void setData(Map<String, DatabaseRow> data) {
this.data = data;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
}
package com.github.xsavikx.jackson;
public class DatabaseRow {
private int weekNumber;
private String someData;
public DatabaseRow(){
}
public DatabaseRow(int weekNumber, String someData) {
this.weekNumber = weekNumber;
this.someData = someData;
}
public int getWeekNumber() {
return weekNumber;
}
public void setWeekNumber(int weekNumber) {
this.weekNumber = weekNumber;
}
public String getSomeData() {
return someData;
}
public void setSomeData(String someData) {
this.someData = someData;
}
}
Update:
more generic solution with Map of maps:
package com.github.xsavikx.jackson;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class JacksonTest {
public static void main(String[] args) throws IOException {
serializeTest();
deserializeTest();
}
private static void deserializeTest() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
DatabaseEntry databaseEntry = objectMapper.readValue("{\"action\":\"action\",\"data\":{\"1009558\":{\"weekNumber\":2,\"someData\":\"someData\"}}}", DatabaseEntry.class);
System.out.println(databaseEntry);
}
private static void serializeTest() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Map<String,Map<String,String>> data = new HashMap<>();
Map<String,String> values = new HashMap<>();
values.put("weekDay","2");
values.put("unpredictableValue","value");
data.put("1009558", values);
String action = "action";
DatabaseEntry dbEntry = new DatabaseEntry();
dbEntry.setAction(action);
dbEntry.setData(data);
System.out.println(objectMapper.writeValueAsString(dbEntry));
}
}
Model:
package com.github.xsavikx.jackson;
import java.util.Map;
public class DatabaseEntry {
private String action;
private Map<String, Map<String,String>> data;
public DatabaseEntry() {
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public Map<String, Map<String, String>> getData() {
return data;
}
public void setData(Map<String, Map<String, String>> data) {
this.data = data;
}
}
I'm a huge fan of Joe Littlejohn's JSON tool. Provide it with a sample JSON file and it can generate POJOs for you.
Here's a sample of what it generated, based on a snipped of JSON from the site you posted.
JSON:
{
"data": [
{
"DT_RowId": "row_29",
"first_name": "Fiona",
"last_name": "Green",
"position": "Chief Operating Officer (COO)",
"office": "San Francisco",
"extn": "2947",
"salary": "850000",
"start_date": "2010-03-11"
}
]
}
JAVA:
#Generated("org.jsonschema2pojo")
public class Datum {
public String dTRowId;
public String firstName;
public String lastName;
public String position;
public String office;
public String extn;
public String salary;
public String startDate;
}
#Generated("org.jsonschema2pojo")
public class Example {
public List<Datum> data = new ArrayList<Datum>();
}
Update:
It looks like this is what the form submit actually sends:
action:edit
data[row_1][first_name]:Tiger23
data[row_1][last_name]:Nixon
data[row_1][position]:System Architect
data[row_1][office]:Edinburgh
data[row_1][extn]:5421
data[row_1][start_date]:2011-04-25
data[row_1][salary]:320800
I don't think this is Json, and I dunno if I would try to treat it as such. If you need to submit form data with Java, you might be better of using the Apache HttpComponents. You can reuse the Java "data" object above as a domain object, and then populate the POST content with Strings of the format:
data[ \DT_RowId\ ][\PropertyName\]: \PropertyValue\
With Spring Boot the json conversion between server and client is automatic (https://stackoverflow.com/a/44842806/3793165).
This way is working for me:
Controller
#PostMapping(value="/nuevoVideo")
#ResponseBody
public RespuestaCreateVideo crearNuevoVideo(#RequestBody PeticionVideos datos) {
RespuestaCreateVideo respuesta = new RespuestaCreateVideo();
respuesta.setData("datos");
//respuesta.setError("error"); // implement the logic for error and return the message to show to the user.
return respuesta;
}
where PeticionVideos (RequestVideos) is the create structure Datatables editor sends (with setters, getters...):
public class PeticionVideos {
private Map<String, Video> data;
private String action;
}
The response from the server to the client datatable is waiting for has a particular format (check at https://editor.datatables.net/manual/server).
I often use this:
public class RespuestaCreateVideo { //ResponseCreateVideo
private String data;
private String error;
}
After two days trying this is working perfect now!

Deserializing Map from Json, using Gson

Lets say your Json consist of a bunch of freeform pairs
"config": {
"k1": "abc",
"k2": "xyz"
},
Rules
I don't know how many keys i'll have
All values will be Strings
I'd like for map to be addressable by a key
Where some of the values are Strings and others are Numbers. I was thinking that HashMap
public class Outer {
private Config config = new Config();
public Config getConfig() {
return config;
}
public void setConfig(Config config) {
this.config = config;
}
public class Config {
private Map<String, String> map = new HashMap<>();
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
}
When using testing this, i see that getConfig() returns a non-null value. But when i get to getMap() i get null
Please help me understand what am i missing here.
You should use just:
public class Outer {
private Map<String, String> config = new HashMap<>();
public Map<String, String> getConfig() {
return config;
}
public void setConfig(Map<String, String> config) {
this.config = config;
}
}
In the class model you wrote, you are describing a JSON like this:
"config": {
"map": {
"k1": "abc",
"k2": "xyz"
}
},
but as you can see, that's not what you want...
You have to realize that the field config in your JSON is not an object that contains a field called map that represents a Map... but the field config represents itself a Map!

Categories

Resources