4

I am parsing JSON and am having difficulty with one structure that can have one of three forms. In my case it could be zero-dimensional, one-dimensional or two-dimensional. Is there some way I can inspect the JSON on the fly to determine which one it is? Or perhaps consume it anyway and work out what it is afterwards.

The structures look like this and can be embedded in other structures.

"details":{
    "Product":"A zero-dimensional Product"
},

"details":{
    "Product":"A one-dimensional Product",
    "Dimensions": [ "Size" ],
    "Labels": [ "XS", "S", "M", "L" ]
},

"details":{
    "Product":"A two-dimensional Product",
    "Dimensions": [ "Size", "Fit" ],
    "Labels": [[ "XS", "S", "M", "L" ],[ "26", "28", "30", "32" ]]
}

What I may be looking for is a generic class that Jackson will always match with.

Something like translating:

{
"SomeField": "SomeValue",
...
 "details":{
  ...
 }
}

Into:

class MyClass {
  String SomeField;
  ...
  AClass details;
}

Is there a class AClass I can define that could be a universal recipient for any JSON structure or array?

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • But isn't it that dimensions and labels are just optional? Otherwise this is ordinary parsing? – Boris Strandjev Sep 16 '12 at 20:29
  • Try this link: http://stackoverflow.com/questions/2487841/jquery-parse-json-multidimensional-array – David D. Sep 16 '12 at 20:30
  • I may have been misunderstood. I have edited the question to improve (I hope). The point is that the structure can be any of three different flavours. I think I either need to pre-taste the JSON to work out which flavour it is or consume any kind of structure and find out afterwards. – OldCurmudgeon Sep 16 '12 at 20:35
  • @BorisStrandjev - Note that `Labels` can be a one dimensional or two-dimensional array. – OldCurmudgeon Sep 16 '12 at 20:38
  • @OldCurmudgeon I think that this is the default behavior of jackson: single-elemented array is put in as the element, without the enclosing array. – Boris Strandjev Sep 17 '12 at 08:33

2 Answers2

6

Thanks to Eric's comment pointing me to programmerbruce I managed to crack it. Here's the code I used (cut down to simplify).

public static class Info {
  @JsonProperty("Product")
  public String product;
  // Empty in the 0d version, One entry in the 1d version, two entries in the 2d version.
  @JsonProperty("Dimensions")
  public String[] dimensions;

}

public static class Info0d extends Info {
}

public static class Info1d extends Info {
  @JsonProperty("Labels")
  public String[] labels;
}

public static class Info2d extends Info {
  @JsonProperty("Labels")
  public String[][] labels;
}

public static class InfoDeserializer extends StdDeserializer<Info> {
  public InfoDeserializer() {
    super(Info.class);
  }

  @Override
  public Info deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    Class<? extends Info> variantInfoClass = null;
    ObjectMapper mapper = (ObjectMapper) jp.getCodec();
    ObjectNode root = (ObjectNode) mapper.readTree(jp);
    // Inspect the `diemnsions` field to decide what to expect.
    JsonNode dimensions = root.get("Dimensions");
    if ( dimensions == null ) {
      variantInfoClass = Info0d.class;
    } else {
      switch ( dimensions.size() ) {
        case 1:
          variantInfoClass = Info1d.class;
          break;

        case 2:
          variantInfoClass = Info2d.class;
          break;
      }
    }
    if (variantInfoClass == null) {
      return null;
    }
    return mapper.readValue(root, variantInfoClass);
  }
}

And to install this in the ObjectMapper:

// Register the special deserializer.
InfoDeserializer deserializer = new InfoDeserializer();
SimpleModule module = new SimpleModule("PolymorphicInfoDeserializerModule", new Version(1, 0, 0, null));
module.addDeserializer(Info.class, deserializer);
mapper.registerModule(module);
factory = new JsonFactory(mapper);
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
2

Is this structure what you want for AClass?

class Dimension {
    String name;
    List<String> possibleValues;
}

class Product {
    String name;
    List<Dimension> dimensions;
}

All you need to do is change the length of the dimensions list to account for the three types.

Parsing becomes a trivial problem of checking if the Dimensions property is present in the JSON, and if so, iterating over it and appending to the dimensions list.


Another idea would be to restructure the JSON (if you can) such that all cases are of the same form:

"d0":{
    "Product":"A zero-dimensional Product",
    "Dimensions": {}
},

"d1":{
    "Product":"A one-dimensional Product",
    "Dimensions": {
        "Size": [ "XS", "S", "M", "L" ]
    }
},

"d2":{
    "Product":"A two-dimensional Product",
    "Dimensions": {
        "Size": [ "XS", "S", "M", "L" ],
        "Fit": [ "26", "28", "30", "32" ]
    }
}
Eric
  • 95,302
  • 53
  • 242
  • 374
  • Good idea, unfortunately I have found that you cannot name an array in JSON. `"Size": [ "XS", "S", "M", "L" ]` is not valid. I haven't finished studying your first solution. – OldCurmudgeon Sep 16 '12 at 20:41
  • Erm, you can "name" an array? It [validated](http://jsonlint.com/) just fine for me... – Eric Sep 16 '12 at 20:42
  • And no, restructuring the JSON should be a last resort. I would prefer to work out a solution at my end. – OldCurmudgeon Sep 16 '12 at 20:47
  • [Here](http://json.parser.online.fr/) says it fails. Hmmm... OOps!! My mistake!! Copy/Paste error. – OldCurmudgeon Sep 16 '12 at 20:49
  • 1
    @OldCurmudgeon: http://programmerbruce.blogspot.co.uk/2011/05/deserialize-json-with-jackson-into.html might be relevant here, if restructuring is a no-go. – Eric Sep 16 '12 at 20:58
  • Thanks for that. I will study it and get back to you tomorrow. Looks exactly what I am looking for. – OldCurmudgeon Sep 16 '12 at 21:04