4

Working on building the model for an application dealing with physical buildings.

Ideally, we'd want something like this:

City has multiple Offices, which have multiple Rooms, which have properties.

We're using jackson to parse the JSON payload received from the API datasource, and it ends up looking a bit differently than the examples I've seen.

The format we're getting is:

{
"CityName1": 
    { "OfficeName1": 
        [   
            {"name": RoomName1, "RoomProperty2": RoomValue1},
            {"name": RoomName2, "RoomProperty2": RoomValue2}
        ]
    }, 
    { "OfficeName2": [{...}]},
    { "OfficeNameX" : [{...}] },
"CityName2": {...},
"CityNameN": {...}}

Java classes:

public class City {
  private Map<String, Object> additionalProperties = new HashMap<String, Object();

  private List<Office> _offices = new ArrayList<Office>();

  @JsonAnyGetter
  public Map<String, Object> getAdditionalProperties() {
    return this.additionalProperties;
  }

  @JsonAnySetter
  public void setAdditionalProperty(String name, Object value)
      throws IOException {
    _cityName = name;
    String officeJson = _mapper.writeValueAsString(value);
    StringBuilder sb = new StringBuilder(officeJson);
    _offices.add(_mapper.readValue(officeJson, Office.class));
    this.additionalProperties.put(name, value);
  }
}

public class Office {

  private String _officeName;

  private static final ObjectMapper _mapper = new ObjectMapper();

  private Map<String, Object> additionalProperties = new HashMap<String, Object>();

  private List<Room> _rooms = new ArrayList<Room>();

  @JsonAnyGetter
  public Map<String, Object> getAdditionalProperties() {
    return this.additionalProperties;
  }

  @JsonAnySetter
  public void setAdditionalProperty(String name, Object value)
      throws IOException {
    _officeName = name;
    String roomJson = _mapper.writeValueAsString(value);
    Room[] rooms  = _mapper.readValue(roomJson, Room[].class);
    _rooms.addAll(Arrays.asList(rooms));
    this.additionalProperties.put(name, value);
  }

  public List<Room> getRooms() {
    return _rooms;
  }

  public void setRooms(List<Room> rooms) {
    _rooms = rooms;
  }  
}

public class Room {

  private static final String NAME = "name";
  private static final String PROP_2 = "RoomProperty2";

  @JsonProperty(PROP_2)
  private String _propertyTwo;

  @JsonProperty(NAME)
  private String name;

  @JsonProperty(PROP_2)
  public String getPropertyTwo() {
    return _propertyTwo;
  }

  @JsonProperty(PROP_2)
  public void setPropertyTwo(String propTwo) {
    _propertyTwo = propTwo;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

So how would I go about parsing this with jackson ? Currently, I am using an @JsonAnySetter to grab the name, and saving that as the city or office name and then sending the value sent to JsonAnySetter to the appropriate nested class. The real issue comes with getting a list of Offices in the City. When using a mapper.readvalues(String, Office.class), I get returned an iterator of only the last office for each city. Any ideas guys?

Sorry if that seemed confusing! Would love to answer any questions I've created.

Thanks for the help!

4
  • 2
    As usual, showing Java classes involved is necessary here. Commented May 7, 2015 at 23:48
  • Added classes. The issue is still that _offices only contains 1 Office, when it should contain N. Commented May 8, 2015 at 0:44
  • One quick suggestion: instead of writing json, re-reading, try ObjectMapper.convertValue(value, Office.class. Same thing, simpler, more efficient. Commented May 8, 2015 at 22:24
  • Actually, come to think of this, why not declare method as "public void setAdditionalProperty(String name, Office value) { }"? Jackson should be able to directly bind the value, if (but only if) values are guaranteed to be proper instances. Similarly for the second "any setter". Commented May 11, 2015 at 18:32

1 Answer 1

3

I think the best solution is to write your own deserialiser here since your JSON document doesn't really map well to the class structure you want.

The solution below reads each city as a Map<String, List<Room>> and the collection of cities as a Map<String, City> and then create City and Office objects from these inside the deserialisers.

Room.java is the same as yours, here are the rest:

Cities.java:

@JsonDeserialize(using=CitiesDeserializer.class)
public class Cities implements Iterable<City> {

    private final List<City> cities;

    public Cities(final List<City> cities) {
        this.cities = cities;
    }

    public Cities() {
        this.cities = new ArrayList<>();
    }

    public List<City> getCities() {
        return cities;
    }

    @Override
    public Iterator<City> iterator() {
        return cities.iterator();
    }
}

CitiesDeserialiser.java:

public class CitiesDeserializer extends JsonDeserializer<Cities> {
    private static final TypeReference<Map<String, City>> TYPE_REFERENCE = new TypeReference<Map<String, City>>() {};

    @Override
    public Cities deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
        final Map<String, City> map = jp.readValueAs(TYPE_REFERENCE);
        List<City> cities = new ArrayList<>();
        for(Map.Entry<String, City> entry : map.entrySet()) {
            City city = entry.getValue();
            city.setName(entry.getKey());
            cities.add(city);
        }
        return new Cities(cities);
    }
}

City.java:

@JsonDeserialize(using=CityDeserialzer.class)
public class City {

    private String name;

    private List<Office> offices;

    // Setters and getters
}

CityDeserializer.java:

public class CityDeserialzer extends JsonDeserializer<City> {
    private static final TypeReference<Map<String, List<Room>>> TYPE_REFERENCE = new TypeReference<Map<String, List<Room>>>() {};

    @Override
    public City deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
        final Map<String, List<Room>> map = jp.readValueAs(TYPE_REFERENCE);
        List<Office> offices = new ArrayList<>();
        for(Map.Entry<String, List<Room>> entry : map.entrySet()) {
            Office office = new Office();
            office.setName(entry.getKey());
            office.setRooms(entry.getValue());
            offices.add(office);
        }
        City city = new City();
        city.setOffices(offices);
        return city;
    }
}

Office.java:

public class Office {

    private String name;

    private List<Room> rooms;

    // Setters and getters
}

And here's a test to show that it works:

JSON:

{
    "CityName1": {
        "OfficeName1": [ {
                "name": "RoomName1",
                "RoomProperty2": "RoomValue1"
            }, {
                "name": "RoomName2",
                "RoomProperty2": "RoomValue2"
            } ],
        "OfficeName2": [ {
                "name": "RoomName3",
                "RoomProperty2": "RoomValue3"
            }, {
                "name": "RoomName4",
                "RoomProperty2": "RoomValue4"
            } ]
    },
    "CityName2": {
        "OfficeName3": [ {
                "name": "RoomName5",
                "RoomProperty2": "RoomValue5"
            }, {
                "name": "RoomName6",
                "RoomProperty2": "RoomValue6"
            } ],
        "OfficeName4": [ {
                "name": "RoomName7",
                "RoomProperty2": "RoomValue7"
            }, {
                "name": "RoomName8",
                "RoomProperty2": "RoomValue8"
            } ]
    }
}

Test.java:

public class Test {

    public static void main(String[] args) {
        String json = ...
        ObjectMapper mapper = new ObjectMapper();
        Cities cities = mapper.readValue(json, Cities.class);
        for(City city : cities) {
            System.out.println(city.getName());
            for(Office office : city.getOffices()) {
                System.out.println("\t" + office.getName());
                for(Room room : office.getRooms()) {
                    System.out.println("\t\t" + room.getName());
                    System.out.println("\t\t\t" + room.getPropertyTwo());
                }
            }
        }
    }
}

Output:

CityName1
    OfficeName1
        RoomName1
            RoomValue1
        RoomName2
            RoomValue2
    OfficeName2
        RoomName3
            RoomValue3
        RoomName4
            RoomValue4
CityName2
    OfficeName3
        RoomName5
            RoomValue5
        RoomName6
            RoomValue6
    OfficeName4
        RoomName7
            RoomValue7
        RoomName8
            RoomValue8
Sign up to request clarification or add additional context in comments.

1 Comment

That could work. Another approach that might be slightly less work: use Converter, via @JsonDeserializer(converter=MyConverter.class) (and/or same for @JsonSerialize) -- that way, you can ask Jackson to do half of the job, bind to JsonNode, Map, List<AnyType>, and you then convert from that Java type into actual type, and do any structural conversions needed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.