Writing Java object instance to YAML using Jackson - java

I have a 'Example' Pojo class as mentioned below.
Can any one tel to save instance of Example class to YAML file using Jackson.
public class Example {
String name;
int value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}

Jackson has a module that supports YAML. Ensure that you add the required dependency to your project, then you can use it as following:
// Create an ObjectMapper mapper for YAML
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
// Write object as YAML file
mapper.writeValue(new File("/path/to/yaml/file"), example);
Alternatively you can write your object as a string:
// Write object as YAML string
String yaml = mapper.writeValueAsString(example);

Related

How to write custom serializer for proxy classes

I need to serialize/deserialize a POJO with enum. I have the following DTO:
public enum MyEnum {
VAL1("val1"),
VAL2("val2") {
#Override
public String getValue() {
return "test2";
}
};
private final String name;
MyEnum(String name) {
this.name = name;
}
public String getValue() {
return name;
}
}
public class MyPojo {
public MyEnum prop;
}
public static void main(String... args) {
Gson gson = new GsonBuilder().registerTypeAdapter(MyEnum.class, new MyEnumSeserializer());
MyPojo p = new MyPojo();
p.prop = MyEnum.VAL2; // and I get MyEnum$1.class and My serializer doesn't work
String json = gson.toJson(p);
MyPojo p1 = gson.fromJson(json, MyPojo.class);
}
How can I write a custom serializer/deserializer for proxy classes using Gson library? I can't use another library.
I've been found the solution. Need to change
new GsonBuilder().registerTypeAdapter(MyEnum.class, new MyEnumSeserializer());
to
new GsonBuilder(). registerTypeHierarchyAdapter(MyEnum.class, new MyEnumSeserializer());
and all work fine.

Jackson mixin is ignored on serialization and deserialization

I need to be able to create a Java POJO from a JSON object when I only have an interface that can't be changed. I'm hoping that Mixins can help make this possible. I created a Mixin that hopefully will work but can't get Jackson to use it.
It appears that Jackson is ignoring the Mixin I am defining for both an Interface and an Implementation. The test failures are what I would expect without the Mixin added to the ObjectMapper.
Below is the simplest example that shows the problem. The classes are each in their own package. The real uses case is much more complex, including Lists of interfaces. I am using Jackson 2.10.3.
Any suggestions on what I'm doing wrong?
Timothy
What doesn't work
The interface reader test fails with InvalidDefinitionException: Cannot construct instance of model.Level4 (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
Of secondary importance, the Mixin defines a new label (nameTest) for the name field which should be reflected in the output from writeValueAsString. It outputs the field with the original value for the label (name).
Interface
public interface Level4 {
public Long getId();
public void setId(Long id);
public String getName();
public void setName(String name);
}
Implementation
public class Level4Impl implements Level4 {
private Long id;
private String name;
#Override
public Long getId() {
return id;
}
#Override
public void setId(Long id) {
this.id = id;
}
#Override
public String getName() {
return name;
}
#Override
public void setName(String name) {
this.name = name;
}
}
Mixin
public abstract class Level4Mixin {
public Level4Mixin(
#JsonProperty("id") Long id,
#JsonProperty("nameTest") String name) { }
}
Unit Test
class Level4MixinTest {
private ObjectMapper mapper;
#BeforeEach
void setUp() throws Exception {
mapper = new ObjectMapper();
mapper.addMixIn(Level4.class, Level4Mixin.class);
mapper.addMixIn(Level4Impl.class, Level4Mixin.class);
}
#Test
void test_InterfaceWrite() throws JsonProcessingException {
Level4 lvl4 = new Level4Impl();
lvl4.setId(1L);
lvl4.setName("test");
String json = mapper.writeValueAsString(lvl4);
assertNotNull(json);
assertTrue(json.contains("nameTest"));
}
#Test
void test_InterfaceRead() throws JsonProcessingException {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4 parsed = mapper.readValue(json, Level4.class);
assertNotNull(parsed);
});
}
#Test
void test_ImplWrite() throws JsonProcessingException {
Level4Impl lvl4 = new Level4Impl();
lvl4.setId(1L);
lvl4.setName("test");
String json = mapper.writeValueAsString(lvl4);
assertNotNull(json);
assertTrue(json.contains("nameTest"));
}
#Test
void test_ImplRead() {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4Impl parsed = mapper.readValue(json, Level4Impl.class);
assertNotNull(parsed);
});
}
}
First of all you have to let Jackson know which subclass of your interface it should instantiate. You do it by adding #JsonTypeInfo and/or #JsonSubTypes annotations to your mix-in class. For single subclass the following would suffice:
#JsonTypeInfo(use = Id.NAME, defaultImpl = Level4Impl.class)
public abstract class Level4Mixin {
}
For multiple sub-classes it will a bit more complex and will require additional field in JSON payload that will identify concrete type. See Jackson Polymorphic Deserialization for details. Also worth mentioning that adding type info will cause type ID field to be written to JSON. JFYI.
Adding new label would be as trivial as adding a pair of getter and setter for desired property. Obviously original name field will be written to JSON too in this case. To change that you may want to place #JsonIgnore on getter in subclass or in mix-in. In latter case name will be ignored for all sub-classes.
Last note: in this case you should register your mix-in with super-type only.
Here are the changes to your classes that satisfy your tests:
Level4Impl
public class Level4Impl implements Level4 {
private Long id;
private String name;
#Override
public Long getId() {
return id;
}
#Override
public void setId(Long id) {
this.id = id;
}
#Override
public String getName() {
return name;
}
#Override
public void setName(String name) {
this.name = name;
}
public String getNameTest() {
return name;
}
public void setNameTest(String name) {
this.name = name;
}
}
Mixin
#JsonTypeInfo(use = Id.NAME, defaultImpl = Level4Impl.class)
public interface Level4Mixin {
#JsonIgnore
String getName();
}
Level4MixinTest change
#BeforeEach
void setUp() throws Exception {
mapper = new ObjectMapper();
mapper.addMixIn(Level4.class, Level4Mixin.class);
// remove
//mapper.addMixIn(Level4Impl.class, Level4Mixin.class);
}
For adding properties to an object when that object is serialized you can use #JsonAppend. For example:
#JsonAppend(attrs = {#JsonAppend.Attr(value = "nameTest")})
public class Level4Mixin {}
And the test:
#BeforeEach
void setUp() throws Exception {
mapper = new ObjectMapper()
.addMixIn(Level4Impl.class, Level4Mixin.class);
}
#Test
void test_ImplWrite() throws JsonProcessingException {
Level4Impl lvl4 = new Level4Impl();
lvl4.setId(1L);
lvl4.setName("test");
String json = mapper.writerFor(Level4Impl.class)
.withAttribute("nameTest", "myValue")
.writeValueAsString(lvl4);
assertNotNull(json);
assertTrue(json.contains("nameTest"));
assertTrue(json.contains("myValue"));
}
The same works for test_InterfaceWrite.
The tests to deserialize a json into an object are not clear:
#Test
void test_ImplRead() {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4Impl parsed = mapper.readValue(json, Level4Impl.class);
assertNotNull(parsed);
});
}
The class Level4Impl does not have the property nameTest so the deserialization fails. If you don't want to throw the exception you can configure the ObjectMapper to don't fail on unknown properties. For example:
#Test
void test_ImplRead() {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4Impl parsed = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(json, Level4Impl.class);
assertNotNull(parsed);
});
}
In case you can't make it work by default (which was my case), try to modify existing MappingJackson2HttpMessageConverter converter, e.g. do it this way:
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.stream()
.filter(c -> c instanceof MappingJackson2HttpMessageConverter)
.forEach(c -> {
MappingJackson2HttpMessageConverter converter = (MappingJackson2HttpMessageConverter) c;
converter.getObjectMapper().addMixIn(APIResponse.class, MixInAPIResponse.class);
});
}
Where MixInAPIResponse is your configured MixIn class for target class ApiResponse.

Json Deserialization in Java /w Jackson of mixed types, contained in one array

Consider the following json, getting from an public API:
anyObject : {
attributes: [
{
"name":"anyName",
"value":"anyValue"
},
{
"name":"anyName",
"value":
{
"key":"anyKey",
"label":"anyLabel"
}
}
]
}
As you can see, sometimes the value is a simple string and sometimes its an object. Is it somehow possible to deserialize those kind of json-results, to something like:
class AnyObject {
List<Attribute> attributes;
}
class Attribute {
private String key;
private String label;
}
How would I design my model to cover both cases. Is that possible ?
Despite being hard to manage as others have pointed out, you can do what you want. Add a custom deserializer to handle this situation. I rewrote your beans because I felt your Attribute class was a bit misleading. The AttributeEntry class in the object that is an entry in that "attributes" list. The ValueObject is the class that represents that "key"/"label" object. Those beans are below, but here's the custom deserializer. The idea is to check the type in the JSON, and instantiate the appropriate AttributeEntry based on its "value" type.
public class AttributeDeserializer extends JsonDeserializer<AttributeEntry> {
#Override
public AttributeEntry deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode root = p.readValueAsTree();
String name = root.get("name").asText();
if (root.get("value").isObject()) {
// use your object mapper here, this is just an example
ValueObject attribute = new ObjectMapper().readValue(root.get("value").asText(), ValueObject.class);
return new AttributeEntry(name, attribute);
} else if (root.get("value").isTextual()) {
String stringValue = root.get("value").asText();
return new AttributeEntry(name, stringValue);
} else {
return null; // or whatever
}
}
}
Because of this ambiguous type inconvenience, you will have to do some type checking throughout your code base.
You can then add this custom deserializer to your object mapper like so:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(AttributeEntry.class, new AttributeDeserializer());
objectMapper.registerModule(simpleModule);
Here's the AttributeEntry:
public class AttributeEntry {
private String name;
private Object value;
public AttributeEntry(String name, String value) {
this.name = name;
this.value = value;
}
public AttributeEntry(String name, ValueObject attributes) {
this.name = name;
this.value = attributes;
}
/* getters/setters */
}
Here's the ValueObject:
public class ValueObject {
private String key;
private String label;
/* getters/setters */
}

Convert Jackson object into JSONObject java

I'm trying to figure out how to convert a Jackson object into a JSONObject?
What I've tried, however I don't believe this is the correct approach.
public JSONObject toJSON() throws IOException {
ObjectMapper mapper = new ObjectMapper();
return new JSONObject(mapper.writeValueAsString(new Warnings(warnings)));
}
The way you are doing is work fine, because i also use that way to make a JSONobject.
here is my code
public JSONObject getRequestJson(AccountInquiryRequestVO accountInquiryRequestVO) throws JsonGenerationException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
JSONObject jsonAccountInquiry;
jsonAccountInquiry=new JSONObject(mapper.writeValueAsString(accountInquiryRequestVO));
return jsonAccountInquiry;
}
its working fine for me. but you can always use JsonNode also
here is the sample code for that
JsonNode jsonNode=mapper.valueToTree(accountInquiryRequestVO);
its very easy to use.
Right now, you are serializing your Pojo to a String, then parsing that String and converting it into a HashMap style object in the form of JSONObject.
This is very inefficient and doesn't accomplish anything of benefit.
Jackson already provides an ObjectNode class for interacting with your Pojo as a JSON object. So just convert your object to an ObjectNode. Here's a working example
public class Example {
public static void main(String[] args) throws Exception {
Pojo pojo = new Pojo();
pojo.setAge(42);
pojo.setName("Sotirios");
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = mapper.valueToTree(pojo);
System.out.println(node);
}
}
class Pojo {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Otherwise, the way you are doing it is fine.

Exclude classes and namespaces from serialization?

I'm trying to serialize a rather large structure with Jackson.
However, it's also trying to export a lot of substructures I will never need (causing a JsonMappingException: No serializer found for class)
So how can I exclude classes and namespaces from serialization?
Alternatively, how can I flag properties of my classes as excluded/ignored?
Use the transient keyword if you have actually access to the substructure you want to exclude.
transient is a Java keyword which marks a member variable not to be
serialized when it is persisted to streams of bytes. When an object is
transferred through the network, the object needs to be 'serialized'.
Serialization converts the object state to serial bytes. Those bytes
are sent over the network and the object is recreated from those
bytes. Member variables marked by the java transient keyword are not
transferred, they are lost intentionally.
http://en.wikibooks.org/wiki/Java_Programming/Keywords/transient
Please give an example for exclude classes and namespace but for properties for which you might not control the source code you can use the following on types and fields
#JsonIgnoreProperties(value = {"propertyName", "otherProperty"})
Here's the javadoc.
Here's an example
#JsonIgnoreProperties(value = { "name" })
public class Examples {
public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException {
Examples examples = new Examples();
examples.setName("sotirios");
Custom custom = new Custom();
custom.setValue("random");
custom.setNumber(42);
examples.setCustom(custom);
ObjectMapper mapper = new ObjectMapper();
StringWriter writer = new StringWriter();
mapper.writeValue(writer, examples);
System.out.println(writer.toString());
}
private String name;
#JsonIgnoreProperties(value = { "value" })
private Custom custom;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Custom getCustom() {
return custom;
}
public void setCustom(Custom custom) {
this.custom = custom;
}
static class Custom {
private String value;
private int number;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
}
prints
{"custom":{"number":42}}
In other words, it ignored Examples#name and Custom#value.

Categories

Resources