Skip to content

Commit

Permalink
Support multi-level JSON unwrapped (#532)
Browse files Browse the repository at this point in the history
Adds support for nesting `@JsonUnwrapped` declarations.

---------

Co-authored-by: yawkat <jonas.konrad@oracle.com>

---------

Co-authored-by: Radovan Radic <radovan.radic@oracle.com>
Co-authored-by: yawkat <jonas.konrad@oracle.com>
Co-authored-by: radovanradic <radicr@gmail.com>
  • Loading branch information
4 people authored Jul 28, 2023
1 parent 59512d7 commit 9cc2998
Show file tree
Hide file tree
Showing 9 changed files with 479 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package io.micronaut.serde.jackson.annotation

import io.micronaut.core.type.Argument
import io.micronaut.serde.jackson.JsonCompileSpec
import io.micronaut.serde.jackson.nested.Address
import io.micronaut.serde.jackson.nested.NestedEntity
import io.micronaut.serde.jackson.nested.NestedEntityId
import spock.lang.Requires

class JsonUnwrappedSpec extends JsonCompileSpec {
Expand Down Expand Up @@ -596,4 +599,145 @@ class Name {
cleanup:
context.close()
}

void "test @JsonUnwrapped - levels"() {
given:
def context = buildContext("""
package unwrapped;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import io.micronaut.serde.annotation.Serdeable;
@Serdeable
class Foo {
@JsonUnwrapped(prefix = "hk_", suffix = "_out")
private ComplexFooId hashKey;
private String value;
public ComplexFooId getHashKey() {
return hashKey;
}
public void setHashKey(ComplexFooId hashKey) {
this.hashKey = hashKey;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
@Serdeable
class ComplexFooId {
private Integer theInt;
@JsonUnwrapped(prefix = "foo_", suffix = "_in")
private InnerFooId nested;
public Integer getTheInt() {
return theInt;
}
public void setTheInt(Integer theInt) {
this.theInt = theInt;
}
public InnerFooId getNested() {
return nested;
}
public void setNested(InnerFooId nested) {
this.nested = nested;
}
}
@Serdeable
class InnerFooId {
private Long theLong;
private String theString;
public Long getTheLong() {
return theLong;
}
public void setTheLong(Long theLong) {
this.theLong = theLong;
}
public String getTheString() {
return theString;
}
public void setTheString(String theString) {
this.theString = theString;
}
}
""")

when:
def foo = newInstance(context, 'unwrapped.Foo', [value: "TheValue", hashKey: newInstance(context, 'unwrapped.ComplexFooId', [theInt: 10,
nested: newInstance(context, 'unwrapped.InnerFooId', [theLong: 200L, theString: 'MyString'])])])

def result = writeJson(jsonMapper, foo)

then:
result == '{"hk_theInt_out":10,"hk_foo_theLong_in_out":200,"hk_foo_theString_in_out":"MyString","value":"TheValue"}'

when:
def read = jsonMapper.readValue(result, Argument.of(context.classLoader.loadClass('unwrapped.Foo')))

then:
read
read.value == 'TheValue'
read.hashKey.theInt == 10
read.hashKey.nested.theLong == 200
read.hashKey.nested.theString == 'MyString'

cleanup:
context.close()
}


void "test @JsonUnwrapped - levels 2"() {
given:
def ctx = buildContext("")

when:
def nestedEntity = new NestedEntity();
nestedEntity.setValue("test1");
NestedEntityId hashKey = new NestedEntityId();
hashKey.setTheInt(100);
hashKey.setTheString("MyString");
nestedEntity.setHashKey(hashKey);
Address address = new Address();
address.getCityData().setCity("NY");
address.getCityData().setZipCode("22000");
address.setStreet("Blvd 11");
nestedEntity.setAddress(address);
def nestedJsonStr = writeJson(jsonMapper, nestedEntity)

then:
nestedJsonStr == '{"hk_theInt":100,"hk_theString":"MyString","value":"test1","addr_street":"Blvd 11","addr_cd_zipCode":"22000","addr_cd_city":"NY","version":1,"dateCreated":"1970-01-01T00:00:00Z","dateUpdated":"1970-01-01T00:00:00Z"}'

when:
def deserNestedEntity = jsonMapper.readValue(nestedJsonStr, NestedEntity.class)

then:
deserNestedEntity
deserNestedEntity.hashKey.theInt == nestedEntity.hashKey.theInt
deserNestedEntity.value == nestedEntity.value
deserNestedEntity.audit.dateCreated == nestedEntity.audit.dateCreated
deserNestedEntity.address.cityData.zipCode == nestedEntity.address.cityData.zipCode
deserNestedEntity.address.street == nestedEntity.address.street

cleanup:
ctx.close()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.micronaut.serde.jackson.nested;

import com.fasterxml.jackson.annotation.JsonUnwrapped;
import io.micronaut.serde.annotation.Serdeable;

@Serdeable
public class Address {

@JsonUnwrapped(prefix = "cd_")
private CityData cityData = new CityData();

private String street;

public String getStreet() {
return street;
}

public void setStreet(String street) {
this.street = street;
}

public CityData getCityData() {
return cityData;
}

public void setCityData(CityData cityData) {
this.cityData = cityData;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.micronaut.serde.jackson.nested;

import io.micronaut.serde.annotation.Serdeable;

import java.sql.Timestamp;
import java.util.Date;

@Serdeable
public class Audit {

static final Timestamp MIN_TIMESTAMP = new Timestamp(new Date(0).getTime());

private Long version = 1L;

// Init manually because cannot be nullable and not getting populated by the event
private Timestamp dateCreated = MIN_TIMESTAMP;

private Timestamp dateUpdated = MIN_TIMESTAMP;

public Long getVersion() {
return version;
}

public void setVersion(Long version) {
this.version = version;
}

public Timestamp getDateCreated() {
return dateCreated;
}

public void setDateCreated(Timestamp dateCreated) {
this.dateCreated = dateCreated;
}

public Timestamp getDateUpdated() {
return dateUpdated;
}

public void setDateUpdated(Timestamp dateUpdated) {
this.dateUpdated = dateUpdated;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.micronaut.serde.jackson.nested;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.serde.annotation.Serdeable;

@Serdeable
public class CityData {

@NonNull
private String zipCode;

private String city;

public String getZipCode() {
return zipCode;
}

public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.micronaut.serde.jackson.nested;

import com.fasterxml.jackson.annotation.JsonUnwrapped;
import io.micronaut.serde.annotation.Serdeable;

@Serdeable
public class NestedEntity {

@JsonUnwrapped(prefix = "hk_")
private NestedEntityId hashKey;

private String value;

@JsonUnwrapped(prefix = "addr_")
private Address address;

@JsonUnwrapped
private Audit audit = new Audit();

public NestedEntityId getHashKey() {
return hashKey;
}

public void setHashKey(NestedEntityId hashKey) {
this.hashKey = hashKey;
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

public Address getAddress() {
return address;
}

public void setAddress(Address address) {
this.address = address;
}

public Audit getAudit() {
return audit;
}

public void setAudit(Audit audit) {
this.audit = audit;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.micronaut.serde.jackson.nested;

import io.micronaut.serde.annotation.Serdeable;

@Serdeable
public class NestedEntityId {

private Integer theInt;

private String theString;

public Integer getTheInt() {
return theInt;
}

public void setTheInt(Integer theInt) {
this.theInt = theInt;
}

public String getTheString() {
return theString;
}

public void setTheString(String theString) {
this.theString = theString;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -101,6 +102,11 @@ private int probe(String key) {
}
}

/**
* Get the properties in this bag with their property names.
*
* @return All properties in this bag
*/
public List<Map.Entry<String, DeserBean.DerProperty<T, Object>>> getProperties() {
Stream<AbstractMap.SimpleEntry<String, DeserBean.DerProperty<T, Object>>> originalProperties = Arrays.stream(originalNameToPropertiesMapping)
.filter(index -> index != -1)
Expand All @@ -118,6 +124,15 @@ public List<Map.Entry<String, DeserBean.DerProperty<T, Object>>> getProperties()
.collect(Collectors.toList());
}

/**
* Get the properties in this bag.
*
* @return All properties in this bag
*/
public List<DeserBean.DerProperty<T, Object>> getDerProperties() {
return Collections.unmodifiableList(Arrays.asList(properties));
}

public int propertyIndexOf(@NonNull String name) {
int i = probe(name);
return i < 0 ? -1 : indexTable[i];
Expand Down
Loading

0 comments on commit 9cc2998

Please sign in to comment.