简体   繁体   中英

How to deserialize dynamic JSON fields with Jackson?

How can I deserialize JSON a object with dynamic fields (like the one called stories in the code below) into an array of objects?

{
   stories: {
      -IhO1742Lki-Pit0snot: {
         sentences: {
            -IhO2fyAEn15XUge6HeY: {
               userName: "Giulia",
               text: "a dude created a new religion called PornDora"
            },
            -IhO2fyAEn15XUge6HeZ: {
               userName: "Will",
               text: "but it was kidnapped by a flying burrito copter"
            },
            -IhO2fyAEn15XUge6HeX: {
               userName: "Jasmine",
               text: "I went this morning at AngelHack hackaton"
            }
         }
      },
      -IhO-gNvUPHpB9fOn-Gm: {
         sentences: {
            -IhO0PBBnJavU2gfMcVO: {
               userName: "Giorgio",
               text: "I woke up alone in a dark alley and a yellow cat was starring at me"
            },
            -IhO11CWL9r6G4Pu8YXx: {
               userName: "Will",
               text: "the cat runned away when a blurred figure approached me and called my name"
            }
         }
      }
   }
}

These below are the Java classes I'm using for deserialization:

public class Story {

    private String id;
    private List sentences;

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public List getSentences() {
        return sentences;
    }
    public void setSentences(List sentences) {
        this.sentences = sentences;
    }

}
public class Sentence {

    private String id;
    private String text;
    private String userName;



    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }

}

Do you control the format of the JSON serialization? For example, could you adapt the following format instead:

{
    "stories": [
        {
            "id": "-IhO1742Lki-Pit0snot", 
            "sentences": [
                {
                    "id": "-IhO2fyAEn15XUge6HeY", 
                    "text": "a dude created a new religion called PornDora", 
                    "userName": "Giulia"
                }
            ]
        )
    ]
}

If you do control the serialization side, you could try using Polymorphic Type Handling (PTH).

This will give Jackson enough type information to deserialize your objects.

Also see Tatu's accompanying blog post for background info.


Here is a code snippet for PTH in Jackson 1.x. If you want to use Jackson 2.x, you'll leverage the com.fasterxml packages and need to change the code a little bit.

// import com.fasterxml.jackson.annotation.JsonTypeInfo;
// import com.fasterxml.jackson.databind.ObjectMapper;
import org.codehaus.jackson.annotate.JsonTypeInfo;
import org.codehaus.jackson.map.ObjectMapper;

public class MyObjectMapperProvider {
    final ObjectMapper defaultObjectMapper;

    public MyObjectMapperProvider() {
        System.out.println("MyObjectMapperProvider()");


        this.defaultObjectMapper = new ObjectMapper();
        this.defaultObjectMapper.enableDefaultTyping(
            ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_OBJECT
        );
        // .enableDefaultTyping();
        // .enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
        // .enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_OBJECT); 
        // this.defaultObjectMapper.addMixInAnnotations(Bindable.class, MyObjectMapperProvider.MixIn.class);
        // this.defaultObjectMapper.addMixInAnnotations(DataModel.WriteOp.class, MyObjectMapperProvider.MixIn.class);
    }
}

By the way, the json you provided is not actually valid JSON. Try using

   {
        "stories": {
            "-IhO-gNvUPHpB9fOn-Gm": {
                "sentences": {
                    "-IhO0PBBnJavU2gfMcVO": {
                        "text": "I woke up alone in a dark alley and a yellow cat was starring at me", 
                        "userName": "Giorgio"
                    }, 
                    "-IhO11CWL9r6G4Pu8YXx": {
                        "text": "the cat runned away when a blurred figure approached me and called my name", 
                        "userName": "Will"
                    }
                }
            }, 
            "-IhO1742Lki-Pit0snot": {
                "sentences": {
                    "-IhO2fyAEn15XUge6HeX": {
                        "text": "I went this morning at AngelHack hackaton", 
                        "userName": "Jasmine"
                    }, 
                    "-IhO2fyAEn15XUge6HeY": {
                        "text": "a dude created a new religion called PornDora", 
                        "userName": "Giulia"
                    }, 
                    "-IhO2fyAEn15XUge6HeZ": {
                        "text": "but it was kidnapped by a flying burrito copter", 
                        "userName": "Will"
                    }
                }
            }
        }
    }

You can verify that your json is correct by passing it into python -m json.tool . On OS X, you can copy the json into your clipboard and pass it to json.tool as follows:

 > pbpaste | python -m json.tool

Here is a small example that leverages Jersey if you want to play around. Use curl http://localhost:7654/sandbox/test to exercise it:

MainClass.java

import java.net.URI;

import org.glassfish.grizzly.http.server.HttpServer;
// import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MainClass {
     private static final Logger LOG = LoggerFactory.getLogger(MainClass.class);
     public static final String PATH = "http://localhost:7654/sandbox/";
     public static void main(String[] args) {
          // create a resource config that scans for JAX-RS resources and providers
          // in com.mlbam.monitoring.lsg.data_aggregator package
          final ResourceConfig rc = new ResourceConfig()
                .packages("com.mlbam.internal")
                // .packages("com.mlbam.monitoring.collector.provis")
                .register(MyObjectMapperProvider.class)
                .register(JacksonFeature.class)
          ;

          // create and start a new instance of grizzly http server
          // exposing the Jersey application at BASE_URI
          final HttpServer grizzlyServerV1 = GrizzlyHttpServerFactory.createHttpServer(URI.create(PATH), rc);
          LOG.info("Jersey app started with WADL available at {}application.wadl", PATH);
          try {
                Thread.sleep(10_000);
          } catch (InterruptedException e) { }
          LOG.info("Stopping");
     }
}

MyObjectMapperProvider.java:

// import com.fasterxml.jackson.annotation.JsonTypeInfo;
// import com.fasterxml.jackson.databind.ObjectMapper;
import org.codehaus.jackson.annotate.JsonTypeInfo;
import org.codehaus.jackson.map.ObjectMapper;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

@Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
    final ObjectMapper defaultObjectMapper;

    public MyObjectMapperProvider() {
         System.out.println("MyObjectMapperProvider()");

         this.defaultObjectMapper = new ObjectMapper();
         this.defaultObjectMapper.enableDefaultTyping(
               ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_OBJECT
         );
         // .enableDefaultTyping();
         // .enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
         // .enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_OBJECT); 
         // this.defaultObjectMapper.addMixInAnnotations(Bindable.class, MyObjectMapperProvider.MixIn.class);
         // this.defaultObjectMapper.addMixInAnnotations(DataModel.WriteOp.class, MyObjectMapperProvider.MixIn.class);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
         System.out.println("MyObjectMapperProvider.getContext(" + type + ")");
         return this.defaultObjectMapper;
    }


    // @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
    // public static class MixIn {
    // }
}

MyResource.java

import javax.inject.Singleton;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Singleton
@Path("test/")
public class MyResource {
    // public Thing[] state = {new Thing("asdf"), new Thing("foo"), new Thing("bar")};
    public String[] state = {"asdf", "foo", "bar"};

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response test() {
         return Response.ok(state).build();
    }
}

Dependencies (if using maven):

<dependencies>
    <!-- Logging -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.5</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.0.13</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.0.13</version>
    </dependency>
    <!-- Jersey Specific Dependencies -->
    <dependency>
        <groupId>org.glassfish.jersey.containers</groupId>
        <artifactId>jersey-container-grizzly2-http</artifactId>
        <version>2.3.1</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>2.3.1</version>
    </dependency>
</dependencies>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM