JSON parse to change String attribute to an object atribute in Java - java

I have a JSON object that one of the a attributes is a JSON string.
a = {
"dt" : "2022-01-02 00:00:00"
"fx": "{\"id\":1,\"faixaId\":1,\"speedAtivo\":true}",
"hash": "8c91a61a0a49b73de2fc13caed00e6a93dbe435b354216802da0dbe8bfda3300",
}
In JavaScript, I can convert the "fx" attribute to an object using:
a.fx = JSON.parse(a.fx)
And the new JSON:
a = {
"dt" : "2022-01-02 00:00:00"
"fx": {"id":1,
"faixaId":1,
"speedAtivo":true
},
"hash": "8c91a61a0a49b73de2fc13caed00e6a93dbe435b354216802da0dbe8bfda3300",
}
There is a way to do this with Java?

Yes, you can parse JSON string using one of these libraries
to use these library check the docs and
maven dependency
But make sure that your JSON is in correct format as the above JSON is missing comma after the first line ending.
Below is the simple example to parse a JSON String using the above library.
String jsonStr = "{\"dt\":\"2022-01-02 00:00:00\",
\"fx\":\"id\":1,\"faixaId\":1,\"speedAtivo\":true},
\"hash\":\"8c91accsiamkFVXtw6N7DnE3QtredADYBYU35b354216802da0dbe8bfda3300\",
}";
JSONObject strObj = JSONObject.fromObject(jsonStr);
Output:
{
"dt": "2022-01-02 00:00:00",
"fx": {
"id": 1,
"faixaId": 1,
"speedAtivo": true
},
"hash":
"8c91accsiamkFVXtw6N7DnE3QtredADYBYU35b354216802da0dbe8bfda3300"
}

If you use Jackson lirary to convert objects from JSON String to objects via custom deserializer.
First you create Classes that will represent the main object and fx object
public class AClass {
public String dt;
#JsonDeserialize(using = Deserializer.class)
public FxClass fx;
public String hash;
}
public class FxClass {
public int id;
public int faixaId;
public boolean speedAtivo;
}
Then you create deserizalizer
public class Deserializer extends StdDeserializer<FxClass> {
protected Deserializer(Class<?> vc) {
super(vc);
}
protected Deserializer() {
this(null);
}
#Override
public FxClass deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
TextNode treeNode = jsonParser.getCodec().readTree(jsonParser);
return new ObjectMapper().readValue(treeNode.asText(), FxClass.class);
}
}
And add deserializer to ObjectMapperConfiguration
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(FxClass.class, new Deserializer());
mapper.registerModule(module);
And thats it. To convert JsonString to object
AClass readValue = new ObjectMapper.readValue(json, AClass.class);
For additional info try reading https://www.baeldung.com/jackson-deserialization

Related

Using jackson deserialising a property which can be List of object or the object

My lib is calling an API which can return either of the following JSON structure -
{
"key_is_same" : {
"inner" : "val"
}
}
-or-
{
"key_is_same" : [{
"inner" : "val1"
},
{
"inner" : "val2"
}
]
}
Is there any annotation in jakson which can handle this and deserializ it into respective type
Looks like you are looking for the ACCEPT_SINGLE_VALUE_AS_ARRAY deserialization feature.
Feature that determines whether it is acceptable to coerce non-array (in JSON) values to work with Java collection (arrays, java.util.Collection) types. If enabled, collection deserializers will try to handle non-array values as if they had "implicit" surrounding JSON array. This feature is meant to be used for compatibility/interoperability reasons, to work with packages (such as XML-to-JSON converters) that leave out JSON array in cases where there is just a single element in array.
Feature is disabled by default.
It could be enabled either in ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
Or via the #JsonFormat annotation:
#JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<Foo> oneOrMany;
For illustration purposes, consider the following JSON documents:
{
"oneOrMany": [
{
"value": "one"
},
{
"value": "two"
}
]
}
{
"oneOrMany": {
"value": "one"
}
}
It could be the deserialized to the following classes:
#Data
public class Foo {
private List<Bar> oneOrMany;
}
#Data
public class Bar {
private String value;
}
Just ensure the feature is enabled in your ObjectMapper or your field is annotated with #JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY).
And in case you are looking for the equivalent feature for serialization, refer to WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED.
I would recommend using Object as your data type for the property which is dynamic. So Here is my sample.
import java.util.Arrays;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class MainObject {
private Object key_is_same;
public Object getKey_is_same() {
return key_is_same;
}
public void setKey_is_same(Object key) {
this.key_is_same = key;
}
public static class KeyObject {
private String inner;
public String getInner() {
return inner;
}
public void setInner(String inner) {
this.inner = inner;
}
}
public static void main(String...s) throws JsonProcessingException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
public static void main(String...s) throws IOException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
// Deserialize
MainObject mainWithObject = om.readValue("{\"key_is_same\":{\"inner\":\"val1\"}}", MainObject.class);
MainObject mainWithList = om.readValue("{\"key_is_same\":[{\"inner\":\"val1\"},{\"inner\":\"val1\"}]}", MainObject.class);
if(mainWithList.getKey_is_same() instanceof java.util.List) {
((java.util.List) mainWithList.getKey_is_same()).forEach(System.out::println);
}
}
}
}
Output
{"key_is_same":{"inner":"val1"}}
{"key_is_same":[{"inner":"val1"},{"inner":"val1"}]}

Custom Deserializer in Json

I want to implement a custom Json mapping to the POJO below at class level.
#JsonDeserialize(using = DeviceDeserializer.class)
public class Pojo {
private String device;
private String port;
private String reservbleBw;
*default constructor, getter and setters*
}
Below is my Json File
[
{
"Device":"ABCD",
"ifces":[
{
"port":"ABCD:1/2/0",
"reservableBW":"1000",
"capabilites":[ "MPLS" ]
},
{
"port":"ABCD:1/2/1",
"reservableBW":"100",
"capabilites":[ "ETHERNET" ]
}
]
}
]
Now i only need to map the ports and reservableBw when 'capabilities' is 'ETHERNET'. I looked at few examples of custom deserializer but i do not know how to pass the value for JsonParser and DeserializationContext. I have problem understanding the below line.
public Pojo deserialize(JsonParser jParser, DeserializationContext ctxt) throws IOException, JsonProcessingException
{
// custom deserializer implementation
}
There are two methods to solve your issue:
Method 1: use a middle object to mapper: I have created a demo for your issue see this.
Create a middle object to mapper the original JSON
public class SfPojoDto {
private String device;
private List<Ifce> ifces;
}
public class Ifce {
private String port;
private String reservableBW;
private String[] capabilites;
}
Then use custom deserializer to mapper it firstly and convert to your target object.
public class DeviceDeserializer extends JsonDeserializer<SfPojo> {
#Override
public SfPojo deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// while (p.nextToken()!=null) {
// if (p.getCurrentToken()==)
// }
String temp = p.readValueAsTree().toString();
ObjectMapper mapper = new ObjectMapper();
SfPojoDto sfPojoDto = mapper.readValue(temp, SfPojoDto.class);
SfPojo sfPojo = new SfPojo();
sfPojo.setDevice(sfPojoDto.getDevice());
List<Ifce> ifceList = sfPojoDto.getIfces();
for (Ifce ifce : ifceList) {
List<String> capabilities = Arrays.asList(ifce.getCapabilites());
if (capabilities.contains("ETHERNET")) {
sfPojo.setPort(ifce.getPort());
sfPojo.setReservbleBw(ifce.getReservableBW());
return sfPojo;
}
}
return sfPojo;
}
}
Method 2: you can use the JsonParser to operate the JSON string, but this method is more complicated, you can ref this article:
Your model:
public class Ifce {
public String port;
public String reservableBW;
public List<String> capabilites = null;
}
public class Device {
public String device;
public List<Ifce> ifces = null;
}
Then use the objectMapper like this:
objectMapper = new ObjectMapper()
Device device = objectMapper.readValue(yourJsonData, Device.class)
List<Ifce> deviceList = device.ifces.find { it.capabilites.contains("ETHERNET")}

How to use java.util.Optional with REST API?

I have a class that looks like
public class ActiveDirectorySetup implements Serializable {
private ActiveDirectoryDataSource activeDirectoryDataSource;
private Optional<ShActiveDirectorySettings> shActiveDirectorySettings;
private Optional<SaActiveDirectorySettings> saActiveDirectorySettings;
// ...
}
I send this over the API as
Optional<ActiveDirectoryConfiguration> configuration = store.getConfiguration();
if (configuration.isPresent()) {
return configuration.get();
}
What I see on the browser is
[
{
"activeDirectoryDataSource":{
"host":"localhost",
"port":0,
"userName":"user",
"password":"password",
"activeDirectoryQueryConfig":{
"base":{
"present":false
},
"filter":"filter",
"attributes":[
]
},
"activeDirectorySslSettings":{
"present":false
}
},
"shActiveDirectorySettings":{
"present":true
},
"saActiveDirectorySettings":{
"present":true
}
}
]
for a payload that looks like
{
"activeDirectorySetups": [
{
"activeDirectoryDataSource": {
"host": "localhost",
"port": 0,
"userName": "user",
"password": "password",
"activeDirectoryQueryConfig": {
"base": null,
"filter": "filter",
"attributes": []
},
"activeDirectorySslSettings": null
},
"shActiveDirectorySettings": {
"enableUserMapping": true,
"attributes": null
},
"saActiveDirectorySettings": null
}
]
}
As you could see, I get {"present":true} instead of the actual value.
I am using jackson-datatype-jdk8 for this work. How can I force it to replace {"present":true} with actual values - either null or
{"enableUserMapping": true, "attributes": null}
I'm pretty sure you'd need to write custom serialization / deserialization functionality for this.
Deserializer
public class OptionalDeserializer<T> extends StdDeserializer<Optional<T>> {
private ObjectMapper customObjectMapper;
private Class<T> type;
/**
* #param customObjectMapper any ObjectMapper, possibly with deserialization logic for the wrapped type
* #param type the wrapped type
*/
public OptionalDeserializer(ObjectMapper customObjectMapper, Class<T> type) {
this(Optional.class);
this.customObjectMapper = customObjectMapper;
this.type = type;
}
// At least one type-based constructor is required by Jackson
private OptionalDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Optional<T> deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
// Read entire tree
JsonNode treeNode = jsonParser.getCodec().readTree(jsonParser);
// Check if "present" is true
if (treeNode.has("present") && treeNode.get("present").asBoolean()) {
// Read your wrapped value
return Optional.of(customObjectMapper.treeToValue(treeNode.get("data"), type));
}
// Return empty() by default
return Optional.empty();
}
}
Serializer
Note you could include a custom ObjectMapper for the Box type anywhere in the pipeline. It's omitted in the serializer for simplicity.
public class OptionalSerializer<T> extends StdSerializer<Optional<T>> {
public OptionalSerializer(Class<T> type) {
this(TypeFactory.defaultInstance().constructParametricType(Optional.class, type));
}
protected OptionalSerializer(JavaType javaType) {
super(javaType);
}
#Override
public void serialize(Optional<T> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
if (value.isPresent()) {
gen.writeBooleanField("present", true);
gen.writeObjectField("data", value.get());
} else {
gen.writeBooleanField("present", false);
}
gen.writeEndObject();
}
}
Example usage:
public static void main(String[] args) throws IOException {
ObjectMapper optionalMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
// Add any custom deserialization logic for Box objects to this mapper
ObjectMapper boxMapper = new ObjectMapper();
OptionalDeserializer<Box> boxOptionalDeserializer = new OptionalDeserializer<>(boxMapper, Box.class);
OptionalSerializer<Box> boxOptionalSerializer = new OptionalSerializer<>(Box.class);
module.addDeserializer(Optional.class, boxOptionalDeserializer);
// use addSerializer(JsonSerializer<?> ser), not addSerializer(Class<? extends T> type, JsonSerializer<T> ser)
// The generic types involved here will otherwise not let the program compile
module.addSerializer(boxOptionalSerializer);
optionalMapper.registerModule(module);
String json = "{\"present\": true, \"data\": {\"myValue\": 123}}";
Optional optional = optionalMapper.readValue(json, Optional.class);
#SuppressWarnings("unchecked") // Guaranteed safe cast
Optional<Box> boxOptional = (Optional<Box>) optional;
// Prints "123"
boxOptional.ifPresent(box -> System.out.println(box.getMyValue()));
// Prints the contents of "json" (variable defined above)
System.out.println(optionalMapper.writeValueAsString(boxOptional));
}
Where Box is just a simple example class:
private static class Box {
private int myValue;
public int getMyValue() {
return myValue;
}
public void setMyValue(int myValue) {
this.myValue = myValue;
}
}
I think you are relying on default java serialization while using Optional in Java 8.
Kindly note that Optional is not serializable and hence, you will have to write your own JSON serializer/deserializer.

Jackson custom deserializer mapping

I need to deserialize some json which can contain either an array of objects [{},{}] or a single object {}. See my question. Here is what I'm trying to do :
public class LocationDeserializer extends JsonDeserializer<List<Location>>{
#Override
public List<Location> deserialize(JsonParser jp,
DeserializationContext ctxt) throws IOException
{
List<Location> list = new ArrayList<Location>();
if(!jp.isExpectedStartArrayToken()){
list.add(...);
}else{
//Populate the list
}
return list;
}
But I'm getting stuck here. How can I remap the object? And how to tell Jackson to use this deserializer for the attribute "location"?
Here is how the Json can look :
{
"location":
[
{
"code":"75",
"type":"1"
},
{
"code":"77",
"type":"1"
}
]
}
or
{
"location":
{
"code":"75",
"type":"1"
}
}
You can tell Jackson to use this deserializer with the Annotation JsonDeserialize.
And inside your deserialize method, you could use the following:
#Override
public List<Location> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
List<Location> list = new ArrayList<Location>();
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(jp);
if(root.get("location").isArray()){
// handle the array
}else{
// handle the single object
}
return list;
}
I don't know what your JSON looks like, but I think using ObjectNode is a lot easier for this case than using JsonDeserializer. Something like this:
ObjectNode root = mapper.readTree("location.json");
if (root.getNodeType() == JsonNodeType.ARRAY) {
//Use a get and the JsonNode API to traverse the tree to generate List<Location>
}
else {
//Use a get and the JsonNode API to traverse the tree to generate single Location or a one-element List<Location>
}

Jackson - How to process (deserialize) nested JSON?

{
vendors: [
{
vendor: {
id: 367,
name: "Kuhn-Pollich",
company_id: 1,
}
},
{
vendor: {
id: 374,
name: "Sawayn-Hermann",
company_id: 1,
}
}]
}
I have a Vendor object that can properly be deserialized from a single "vendor" json, but I want to deserialize this into a Vendor[], I just can't figure out how to make Jackson cooperate. Any tips?
Here is a rough but more declarative solution. I haven't been able to get it down to a single annotation, but this seems to work well. Also not sure about performance on large data sets.
Given this JSON:
{
"list": [
{
"wrapper": {
"name": "Jack"
}
},
{
"wrapper": {
"name": "Jane"
}
}
]
}
And these model objects:
public class RootObject {
#JsonProperty("list")
#JsonDeserialize(contentUsing = SkipWrapperObjectDeserializer.class)
#SkipWrapperObject("wrapper")
public InnerObject[] innerObjects;
}
and
public class InnerObject {
#JsonProperty("name")
public String name;
}
Where the Jackson voodoo is implemented like:
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotation
public #interface SkipWrapperObject {
String value();
}
and
public class SkipWrapperObjectDeserializer extends JsonDeserializer<Object> implements
ContextualDeserializer {
private Class<?> wrappedType;
private String wrapperKey;
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException {
SkipWrapperObject skipWrapperObject = property
.getAnnotation(SkipWrapperObject.class);
wrapperKey = skipWrapperObject.value();
JavaType collectionType = property.getType();
JavaType collectedType = collectionType.containedType(0);
wrappedType = collectedType.getRawClass();
return this;
}
#Override
public Object deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
ObjectNode objectNode = mapper.readTree(parser);
JsonNode wrapped = objectNode.get(wrapperKey);
Object mapped = mapIntoObject(wrapped);
return mapped;
}
private Object mapIntoObject(JsonNode node) throws IOException,
JsonProcessingException {
JsonParser parser = node.traverse();
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(parser, wrappedType);
}
}
Hope this is useful to someone!
Your data is problematic in that you have inner wrapper objects in your array. Presumably your Vendor object is designed to handle id, name, company_id, but each of those multiple objects are also wrapped in an object with a single property vendor.
I'm assuming that you're using the Jackson Data Binding model.
If so then there are two things to consider:
The first is using a special Jackson config property. Jackson - since 1.9 I believe, this may not be available if you're using an old version of Jackson - provides UNWRAP_ROOT_VALUE. It's designed for cases where your results are wrapped in a top-level single-property object that you want to discard.
So, play around with:
objectMapper.configure(SerializationConfig.Feature.UNWRAP_ROOT_VALUE, true);
The second is using wrapper objects. Even after discarding the outer wrapper object you still have the problem of your Vendor objects being wrapped in a single-property object. Use a wrapper to get around this:
class VendorWrapper
{
Vendor vendor;
// gettors, settors for vendor if you need them
}
Similarly, instead of using UNWRAP_ROOT_VALUES, you could also define a wrapper class to handle the outer object. Assuming that you have correct Vendor, VendorWrapper object, you can define:
class VendorsWrapper
{
List<VendorWrapper> vendors = new ArrayList<VendorWrapper>();
// gettors, settors for vendors if you need them
}
// in your deserialization code:
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readValue(jsonInput, VendorsWrapper.class);
The object tree for VendorsWrapper is analogous to your JSON:
VendorsWrapper:
vendors:
[
VendorWrapper
vendor: Vendor,
VendorWrapper:
vendor: Vendor,
...
]
Finally, you might use the Jackson Tree Model to parse this into JsonNodes, discarding the outer node, and for each JsonNode in the ArrayNode, calling:
mapper.readValue(node.get("vendor").getTextValue(), Vendor.class);
That might result in less code, but it seems no less clumsy than using two wrappers.
#Patrick
I would improve your solution a bit
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectNode objectNode = jp.readValueAsTree();
JsonNode wrapped = objectNode.get(wrapperKey);
JsonParser parser = node.traverse();
parser.setCodec(jp.getCodec());
Vendor mapped = parser.readValueAs(Vendor.class);
return mapped;
}
It works faster :)
I'm quite late to the party, but one approach is to use a static inner class to unwrap values:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
class Scratch {
private final String aString;
private final String bString;
private final String cString;
private final static String jsonString;
static {
jsonString = "{\n" +
" \"wrap\" : {\n" +
" \"A\": \"foo\",\n" +
" \"B\": \"bar\",\n" +
" \"C\": \"baz\"\n" +
" }\n" +
"}";
}
#JsonCreator
Scratch(#JsonProperty("A") String aString,
#JsonProperty("B") String bString,
#JsonProperty("C") String cString) {
this.aString = aString;
this.bString = bString;
this.cString = cString;
}
#Override
public String toString() {
return "Scratch{" +
"aString='" + aString + '\'' +
", bString='" + bString + '\'' +
", cString='" + cString + '\'' +
'}';
}
public static class JsonDeserializer {
private final Scratch scratch;
#JsonCreator
public JsonDeserializer(#JsonProperty("wrap") Scratch scratch) {
this.scratch = scratch;
}
public Scratch getScratch() {
return scratch;
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Scratch scratch = objectMapper.readValue(jsonString, Scratch.JsonDeserializer.class).getScratch();
System.out.println(scratch.toString());
}
}
However, it's probably easier to use objectMapper.configure(SerializationConfig.Feature.UNWRAP_ROOT_VALUE, true); in conjunction with #JsonRootName("aName"), as pointed out by pb2q

Categories