Jackson API:部分更新字符串

时间:2021-05-02 18:02:47

I have a very complex Json object that I get as a String:

我有一个非常复杂的Json对象,我得到一个字符串:

{ "a": ..., "b":..., /* lots of other properties */ "z":... }

that I read partially with Jackson and map into a Java class:

我用杰克逊部分阅读并映射到Java类:

class PartialObjectForB { @JsonProperty("b") private ObjectB b; }

I use the readValue() method from the ObjectMapper class and get what I want... So far, so good.

我使用ObjectMapper类中的readValue()方法得到我想要的东西......到目前为止,非常好。

Now, I want to update some values in PartialObjectForB and update the initial string I had. I figured how to update a Java object with jackson (by using readerForUpdating) but can't find how to do the opposite: update a Json object/string with a Java object.

现在,我想更新PartialObjectForB中的一些值并更新我的初始字符串。我想到了如何使用jackson更新Java对象(通过使用readerForUpdating)但无法找到如何执行相反的操作:使用Java对象更新Json对象/字符串。

I know how to solve quickly that problem by using JSONObject. For example, if I just want to update 1 value:

我知道如何使用JSONObject快速解决这个问题。例如,如果我只想更新1个值:

JSONObject j = new JSONObject(/* the full json string */);
j.getJSONObject("b").getJSONObject("bb")/* etc. */.put("bbbb", 4);
j.toString(); // will give me the full original text with only "b" updated.

But can't find how to do it with jackson.

但是杰克逊找不到怎么做。

Any idea?

任何想法?

Notes:

笔记:

  • My input/output are strings, can't change that.
  • 我的输入/输出是字符串,不能改变它。
  • I don't know what data is in the json object. I just know that I may have the property "b" and that if I don't I can create it.
  • 我不知道json对象中有什么数据。我只知道我可能拥有属性“b”,如果我不知道我可以创建它。
  • I may want to deserialize and update more than 1 property at the root level (e.g: "b", "h" and "w").
  • 我可能想要在根级别反序列化和更新多于1个属性(例如:“b”,“h”和“w”)。
  • This problem is not recursive. Meaning: I have a full representation of the values I unserialize (no unknown properties).
  • 这个问题不是递归的。含义:我完全表示了我反序列化的值(没有未知属性)。
  • The json object, as a string, is made of a few thousand bytes, but the piece(s) I want to update is usually a lot smaller (e.g: around 100 bytes).
  • json对象,作为一个字符串,由几千字节组成,但我想要更新的部分通常要小得多(例如:大约100字节)。

3 个解决方案

#1


2  

Full executable source with benchmark included is:

包含基准的完整可执行文件源是:

import java.io.IOException;
import java.io.StringWriter;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Random;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.Module;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.introspect.BasicBeanDescription;
import org.codehaus.jackson.map.ser.BeanPropertyWriter;
import org.codehaus.jackson.map.ser.std.BeanSerializerBase;
import org.codehaus.jackson.node.ObjectNode;
import org.json.JSONException;
import org.json.JSONObject;

public class JacksonModule {

    private static final ObjectMapper MAPPER = new ObjectMapper();
    private static final int COUNT = 0;
    private static final int REPEAT_HEADER = 40;

    static {
        MAPPER.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        MAPPER.configure(SerializationConfig.Feature.WRITE_NULL_PROPERTIES, false);
        MAPPER.registerModule(new MyModule());
    }
    private DataProcessor sdp;
    private long[] sum = new long[5];

    public static void main(String[] args) throws IOException, JSONException {
        new JacksonModule().start();
    }

    public JacksonModule() throws IOException, JSONException {
        this.sdp = new DataProcessor();
    }

    public void start() throws IOException, JSONException {
        run(-1, false); // load classes: slow
        if (COUNT > 0) {
            for (int i = 0; i < COUNT; ++i) {
                if (i % REPEAT_HEADER == 0) {
                    System.out.println("---------------------------------------------------------------------------------------");
                    print("", "RO JSONObject", "RO Jackson", "R/- Jackson", "R/W JSONObject", "R/W Jackson");
                    System.out.println("---------------------------------------------------------------------------------------");
                }
                run(i, true);
            }
            System.out.println("-- AVERAGE ----------------------------------------------------------------------------");
            print(1, sum[0] / COUNT, sum[1] / COUNT, sum[2] / COUNT, sum[3] / COUNT, sum[4] / COUNT);
            System.out.println("---------------------------------------------------------------------------------------");
            print("", "RO JSONObject", "RO Jackson", "R/- Jackson", "R/W JSONObject", "R/W Jackson");
            System.out.println("---------------------------------------------------------------------------------------");
        }
    }

    public void run(int i, boolean print) throws JSONException, IOException {
        long t1 = sdp.doReadWithJSONObject();
        long t2 = sdp.doReadWithJackson();
        long t3 = sdp.doReadForUpdatingWithJacksonButDontWrite();
        long t4 = sdp.doSomeWritingWithJSONObject();
        long t5 = sdp.doSomeWritingWithJackson();
        if (print) {
            print(i, t1, t2, t3, t4, t5);
            sum[0] += t1;
            sum[1] += t2;
            sum[2] += t3;
            sum[3] += t4;
            sum[4] += t5;
        }
    }

    private void print(int index, long t1, long t2, long t3, long t4, long t5) {
        print(Integer.toString(index), String.format("%,d", t1), String.format("%,d", t2), String.format("%,d", t3), String.format("%,d", t4), String.format("%,d", t5));
    }

    private void print(String i0, String a, String b, String c, String d, String e) {
        System.out.println("|"
                + StringUtils.leftPad(i0, 5) + "|"
                + StringUtils.leftPad(a, 15) + "|"
                + StringUtils.leftPad(b, 15) + "|"
                + StringUtils.leftPad(c, 15) + "|"
                + StringUtils.leftPad(d, 15) + "|"
                + StringUtils.leftPad(e, 15) + "|");
    }

    private static class DataProcessor {

        private DataStore store;
        private long t0, t1;

        private DataProcessor() throws IOException, JSONException {
            this.store = new DataStore(customer, browser);
        }

        public long doReadWithJSONObject() throws JSONException {
            t0 = System.nanoTime();
            JSONObject json = new JSONObject(store.readData(null)); // can throw JSONException
            JSONObject customer = json.getJSONObject("customer");  // can throw JSONException
            JSONObject browserInfo = json.getJSONObject("browser");  // can throw JSONException
            // need to do manually the mapping and figure out what is exactly in this object. Hell no!
            t1 = System.nanoTime();
            return t1 - t0;
        }

        public long doReadWithJackson() throws IOException {
            t0 = System.nanoTime();
            KnownPart obj = store.readData(null, KnownPart.class);
            t1 = System.nanoTime();
            return t1 - t0;
        }

        public long doReadForUpdatingWithJacksonButDontWrite() throws IOException {
            t0 = System.nanoTime();
            KnownPart obj = store.readDataForUpdating(null, KnownPart.class);
            t1 = System.nanoTime();
            return t1 - t0;
        }

        public long doSomeWritingWithJSONObject() throws JSONException {
            t0 = System.nanoTime();
            JSONObject json = new JSONObject(store.readData(null)); // can throw JSONException
            JSONObject customer = json.getJSONObject("customer");  // can throw JSONException
            JSONObject browserInfo = json.getJSONObject("browser");  // can throw JSONException
            customer.put("name", "Jackson Doe");
            browserInfo.put("version", "10");
            store.saveData(json);
            t1 = System.nanoTime();
            return t1 - t0;
        }

        public long doSomeWritingWithJackson() throws IOException {
            t0 = System.nanoTime();
            KnownPart obj = store.readDataForUpdating(null, KnownPart.class);
            obj.customer.name = "Jackson Doe";
            obj.browser.version = "10";
            store.saveData(obj);
            t1 = System.nanoTime();
            return t1 - t0;
        }
    }

    private static class DataStore {

        private final String data;

        private DataStore(Customer customer, BrowserInfo browser) throws IOException, JSONException {
            StringWriter sw = new StringWriter(1000);
            try (JsonGenerator jgen = MAPPER.getJsonFactory().createJsonGenerator(sw)) {
                jgen.writeStartObject();
                writeBunchOfProperties(jgen);
                jgen.writeFieldName("customer");
                jgen.writeRawValue(MAPPER.writeValueAsString(customer));
                writeBunchOfProperties(jgen);
                jgen.writeFieldName("browser");
                jgen.writeRawValue(MAPPER.writeValueAsString(browser));
                writeBunchOfProperties(jgen);
                jgen.writeEndObject();
            }
            this.data = sw.toString();
        }

        private void writeBunchOfProperties(JsonGenerator jgen) throws IOException {
            int c = new Random().nextInt(3) + 1;
            for (int i = 0; i < c; ++i) {
                jgen.writeFieldName(RandomStringUtils.random(10));
                jgen.writeRawValue(JSON_LONG);
            }
        }

        public String readData(String query) {
            return data;
        }

        private void saveData(String json) {
            // TODO
        }

        public void saveData(JSONObject json) {
            saveData(json.toString());
        }

        public void saveData(Object obj) throws IOException {
            saveData(MAPPER.writeValueAsString(obj));
        }

        public <T> T readData(String query, Class<T> clazz) throws IOException {
            return MAPPER.readValue(readData(query), clazz);
        }

        public <T extends UnknownPart> T readDataForUpdating(String query, Class<T> clazz) throws IOException {
            ObjectNode root = (ObjectNode) MAPPER.readTree(readData(query));
            T obj = MAPPER.readValue(root, clazz);
            obj.tree = root;
            return obj;
        }
    }

    private static abstract class UnknownPart {

         ObjectNode tree;
    }

    private static class KnownPart extends UnknownPart {

        @JsonProperty
        private Customer customer;
        @JsonProperty
        private BrowserInfo browser;
    }

    private static class Customer {

        @JsonProperty
        private int id;
        @JsonProperty
        private String name;
        @JsonProperty
        private Address[] addresses; // just to make it more complex for this example

        public Customer(int id, String name, Address[] addresses) {
            this.id = id;
            this.name = name;
            this.addresses = addresses;
        }

        public Customer() {
        }
    }

    private static class Address {

        @JsonProperty
        private String street;
        @JsonProperty
        private String city;

        public Address(String street, String city) {
            this.street = street;
            this.city = city;
        }

        public Address() {
        }
    }

    private static class BrowserInfo {

        @JsonProperty
        private String agent;
        @JsonProperty
        private String version;

        public BrowserInfo(String agent, String version) {
            this.agent = agent;
            this.version = version;
        }

        public BrowserInfo() {
        }
    }

    private static class MyModule extends Module {

        @Override
        public String getModuleName() {
            return "MyModule";
        }

        @Override
        public Version version() {
            return new Version(0, 0, 1, "SNAPSHOT");
        }

        @Override
        public void setupModule(Module.SetupContext context) {
            context.addBeanSerializerModifier(new org.codehaus.jackson.map.ser.BeanSerializerModifier() {
                private UnknownPartSerializer cs;

                @Override
                public JsonSerializer modifySerializer(SerializationConfig config, BasicBeanDescription beanDesc, JsonSerializer<?> serializer) {
                    return UnknownPart.class.isAssignableFrom(beanDesc.getBeanClass())
                       ? new UnknownPartSerializer((BeanSerializerBase) serializer)
                       : serializer;
                }
            });
        }
    }

    private static class UnknownPartSerializer extends BeanSerializerBase {

        public UnknownPartSerializer(BeanSerializerBase src) {
            super(src);
        }

        @Override
        public void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
            UnknownPart up = (UnknownPart) bean;
            jgen.writeStartObject();
            serializeFields(up, jgen, provider);
            jgen.writeEndObject();
        }

        protected void serializeFields(UnknownPart bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
            final BeanPropertyWriter[] props;
            if (_filteredProps != null && provider.getSerializationView() != null) {
                props = _filteredProps;
            } else {
                props = _props;
            }
            int i = 0;
            try {
                for (final int len = props.length; i < len; ++i) {
                    BeanPropertyWriter prop = props[i];
                    if (prop != null) { // can have nulls in filtered list
                        prop.serializeAsField(bean, jgen, provider);
                        bean.tree.remove(prop.getName());
                    }
                }
                if (_anyGetterWriter != null) {
                    _anyGetterWriter.getAndSerialize(bean, jgen, provider);
                }
                Iterator<Entry<String, JsonNode>> it = bean.tree.getFields();
                while (it.hasNext()) {
                    Entry<String, JsonNode> e = it.next();
                    jgen.writeFieldName(e.getKey());
                    jgen.writeObject(e.getValue());
                }
            } catch (Exception e) {
                String name = (i == props.length) ? "[anySetter]"
                        : props[i].getName();
                wrapAndThrow(provider, e, bean, name);
            } catch (*Error e) {
                /* 04-Sep-2009, tatu: Dealing with this is tricky, since we do not
                 *   have many stack frames to spare... just one or two; can't
                 *   make many calls.
                 */
                JsonMappingException mapE = new JsonMappingException("Infinite recursion (*Error)", e);
                String name = (i == props.length) ? "[anySetter]"
                        : props[i].getName();
                mapE.prependPath(new JsonMappingException.Reference(bean, name));
                throw mapE;
            }
        }
    }
    private static Customer customer = new Customer(1, "John Doe", new Address[]{
        new Address("broadway av", "new york"),
        new Address("peachtree st", "atlanta")
    });
    private static BrowserInfo browser = new BrowserInfo("IE", "6.0");
    // some json found on the internet
    private static final String JSON_LONG = "{\"web-app\": {"
            + "\"servlet\": ["
            + "{"
            + "\"servlet-name\": \"cofaxCDS\","
            + "\"servlet-class\": \"org.cofax.cds.CDSServlet\","
            + "\"init-param\": {"
            + "\"configGlossary:installationAt\": \"Philadelphia, PA\","
            + "\"configGlossary:adminEmail\": \"ksm@pobox.com\","
            + "\"configGlossary:poweredBy\": \"Cofax\","
            + "\"configGlossary:poweredByIcon\": \"/images/cofax.gif\","
            + "\"configGlossary:staticPath\": \"/content/static\","
            + "\"templateProcessorClass\": \"org.cofax.WysiwygTemplate\","
            + "\"templateLoaderClass\": \"org.cofax.FilesTemplateLoader\","
            + "\"templatePath\": \"templates\","
            + "\"templateOverridePath\": \"\","
            + "\"defaultListTemplate\": \"listTemplate.htm\","
            + "\"defaultFileTemplate\": \"articleTemplate.htm\","
            + "\"useJSP\": false,"
            + "\"jspListTemplate\": \"listTemplate.jsp\","
            + "\"jspFileTemplate\": \"articleTemplate.jsp\","
            + "\"cachePackageTagsTrack\": 200,"
            + "\"cachePackageTagsStore\": 200,"
            + "\"cachePackageTagsRefresh\": 60,"
            + "\"cacheTemplatesTrack\": 100,"
            + "\"cacheTemplatesStore\": 50,"
            + "\"cacheTemplatesRefresh\": 15,"
            + "\"cachePagesTrack\": 200,"
            + "\"cachePagesStore\": 100,"
            + "\"cachePagesRefresh\": 10,"
            + "\"cachePagesDirtyRead\": 10,"
            + "\"searchEngineListTemplate\": \"forSearchEnginesList.htm\","
            + "\"searchEngineFileTemplate\": \"forSearchEngines.htm\","
            + "\"searchEngineRobotsDb\": \"WEB-INF/robots.db\","
            + "\"useDataStore\": true,"
            + "\"dataStoreClass\": \"org.cofax.SqlDataStore\","
            + "\"redirectionClass\": \"org.cofax.SqlRedirection\","
            + "\"dataStoreName\": \"cofax\","
            + "\"dataStoreDriver\": \"com.microsoft.jdbc.sqlserver.SQLServerDriver\","
            + "\"dataStoreUrl\": \"jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon\","
            + "\"dataStoreUser\": \"sa\","
            + "\"dataStorePassword\": \"dataStoreTestQuery\","
            + "\"dataStoreTestQuery\": \"SET NOCOUNT ON;select test='test';\","
            + "\"dataStoreLogFile\": \"/usr/local/tomcat/logs/datastore.log\","
            + "\"dataStoreInitConns\": 10,"
            + "\"dataStoreMaxConns\": 100,"
            + "\"dataStoreConnUsageLimit\": 100,"
            + "\"dataStoreLogLevel\": \"debug\","
            + "\"maxUrlLength\": 500}},"
            + "{"
            + "\"servlet-name\": \"cofaxEmail\","
            + "\"servlet-class\": \"org.cofax.cds.EmailServlet\","
            + "\"init-param\": {"
            + "\"mailHost\": \"mail1\","
            + "\"mailHostOverride\": \"mail2\"}},"
            + "{"
            + "\"servlet-name\": \"cofaxAdmin\","
            + "\"servlet-class\": \"org.cofax.cds.AdminServlet\"},"
            + ""
            + "{"
            + "\"servlet-name\": \"fileServlet\","
            + "\"servlet-class\": \"org.cofax.cds.FileServlet\"},"
            + "{"
            + "\"servlet-name\": \"cofaxTools\","
            + "\"servlet-class\": \"org.cofax.cms.CofaxToolsServlet\","
            + "\"init-param\": {"
            + "\"templatePath\": \"toolstemplates/\","
            + "\"log\": 1,"
            + "\"logLocation\": \"/usr/local/tomcat/logs/CofaxTools.log\","
            + "\"logMaxSize\": \"\","
            + "\"dataLog\": 1,"
            + "\"dataLogLocation\": \"/usr/local/tomcat/logs/dataLog.log\","
            + "\"dataLogMaxSize\": \"\","
            + "\"removePageCache\": \"/content/admin/remove?cache=pages&id=\","
            + "\"removeTemplateCache\": \"/content/admin/remove?cache=templates&id=\","
            + "\"fileTransferFolder\": \"/usr/local/tomcat/webapps/content/fileTransferFolder\","
            + "\"lookInContext\": 1,"
            + "\"adminGroupID\": 4,"
            + "\"betaServer\": true}}],"
            + "\"servlet-mapping\": {"
            + "\"cofaxCDS\": \"/\","
            + "\"cofaxEmail\": \"/cofaxutil/aemail/*\","
            + "\"cofaxAdmin\": \"/admin/*\","
            + "\"fileServlet\": \"/static/*\","
            + "\"cofaxTools\": \"/tools/*\"},"
            + ""
            + "\"taglib\": {"
            + "\"taglib-uri\": \"cofax.tld\","
            + "\"taglib-location\": \"/WEB-INF/tlds/cofax.tld\"}}}";
}

#2


2  

Victory! :) I have now an implementation for it and it is not quite simple. Benchmark Jackson vs JSONObject included.

胜利! :)我现在有一个实现它并不是很简单。基准Jackson与JSONObject包括在内。

Problem and solution described piece by piece:

问题和解决方案逐件描述:

First, I have a big json string in a data store that I want to partially deserialize and update. The deserialization has to be partial, but serialization full, so I don't lose the data I didn't deserialize. Here are the object I am using for the example:

首先,我在数据存储中有一个大的json字符串,我想部分反序列化和更新。反序列化必须是部分的,但序列化完整,所以我不会丢失我没有反序列化的数据。以下是我用于示例的对象:

   private static class KnownPart {

        @JsonProperty
        private Customer customer;
        @JsonProperty
        private BrowserInfo browser;
    }

    private static class Customer {

        @JsonProperty
        private int id;
        @JsonProperty
        private String name;
        @JsonProperty
        private Address[] addresses; // just to make it more complex for this example

        public Customer(int id, String name, Address[] addresses) {
            this.id = id;
            this.name = name;
            this.addresses = addresses;
        }

        public Customer() {
        }
    }

    private static class Address {

        @JsonProperty
        private String street;
        @JsonProperty
        private String city;

        public Address(String street, String city) {
            this.street = street;
            this.city = city;
        }

        public Address() {
        }
    }

    private static class BrowserInfo {

        @JsonProperty
        private String agent;
        @JsonProperty
        private String version;

        public BrowserInfo(String agent, String version) {
            this.agent = agent;
            this.version = version;
        }

        public BrowserInfo() {
        }
    }

KnownPart object contains a lot more than 2 properties, but I have no idea which ones exactly and I can not guarantee to keep track of which properties are added to the json string...

KnownPart对象包含超过2个属性,但我不知道究竟是哪些属性,我不能保证跟踪哪些属性被添加到json字符串...

I have a data store which has its data encoded in Json. The current implementation uses JSONObject to read/write and supports jackson to read json and map it to "real" Java object (POJOs and more complex objects). While writing full Json strings with jackson is not a problem, updating an existing string with a Java object that only represents a small part of it is much harder.

我有一个数据存储,其数据在Json中编码。当前实现使用JSONObject进行读/写,并支持jackson读取json并将其映射到“真实”Java对象(PO​​JO和更复杂的对象)。虽然使用jackson编写完整的Json字符串不是问题,但使用仅代表其中一小部分的Java对象更新现有字符串要困难得多。

Data store problem:

数据存储问题:

    private static class DataStore {

        private final String data;

        private DataStore(Customer customer, BrowserInfo browser) throws IOException, JSONException {
            StringWriter sw = new StringWriter(1000);
            try (JsonGenerator jgen = MAPPER.getJsonFactory().createJsonGenerator(sw)) {
                jgen.writeStartObject();
                writeBunchOfProperties(jgen);
                jgen.writeFieldName("customer");
                jgen.writeRawValue(MAPPER.writeValueAsString(customer));
                writeBunchOfProperties(jgen);
                jgen.writeFieldName("browser");
                jgen.writeRawValue(MAPPER.writeValueAsString(browser));
                writeBunchOfProperties(jgen);
                jgen.writeEndObject();
            }
            this.data = sw.toString();
        }

        private void writeBunchOfProperties(JsonGenerator jgen) throws IOException {
            int c = new Random().nextInt(3) + 1;
            for (int i = 0; i < c; ++i) {
                jgen.writeFieldName(RandomStringUtils.random(10));
                jgen.writeRawValue(JSON_LONG);
            }
        }

        public String readData(String query) {
            return data;
        }

        private void saveData(String json) {
            // Not implemented
        }

        public void saveData(JSONObject json) {
            saveData(json.toString());
        }

        public void saveData(Object obj) throws IOException {
            // problem: ?
        }

        public <T> T readData(String query, Class<T> clazz) throws IOException {
            return MAPPER.readValue(readData(query), clazz);
        }

        public <T> T readDataForUpdating(String query, Class<T> clazz) throws IOException {
            // problem: ?
        }

At the higher level, I want to be able to do something like:

在更高层次上,我希望能够做到这样的事情:

        public long doSomeWritingWithJackson() throws IOException {
            t0 = System.nanoTime();
            KnownPart obj = store.readDataForUpdating(null, KnownPart.class);
            obj.customer.name = "Jackson Doe";
            obj.browser.version = "10";
            store.saveData(obj);
            t1 = System.nanoTime();
            return t1 - t0;
        }

without losing the data I didn't read when I save. Obviously, I don't want to read a 2nd time the data because I'm reading this from a remote host and I don't want to cache it in some static/instance map because I need this to be still very effective in a highly concurrent environment.

我没有丢失我保存时没有读到的数据。显然,我不想第二次读取数据,因为我是从远程主机读取它而我不想将它缓存在某个静态/实例映射中,因为我需要它仍然非常有效高度并发的环境。

So the solution, in a few words is: - to read the tree of the json string first and use it to deserialize the Json object into the Java object. - store the deserialize object (in KnowPart class) and store the tree in some parent abstract class - write a Jackson module to customize how bean are serialized. The code is pretty much the same than the original one with the difference that when an attribute from the KnownPart is written, it's key is removed the tree which is in the UnknownPart object, then it is easy to write the unknown part...

所以解决方案,简而言之: - 首先读取json字符串的树,然后使用它将Json对象反序列化为Java对象。 - 存储deserialize对象(在KnowPart类中)并将树存储在某个父抽象类中 - 编写一个Jackson模块来自定义bean的序列化方式。代码与原始代码几乎相同,区别在于当写入KnownPart的属性时,它的键被删除了UnknownPart对象中的树,然后很容易编写未知部分...

The main object becomes:

主要对象变为:

    private static abstract class UnknownPart {

         ObjectNode tree;
    }

    private static class KnownPart extends UnknownPart {

        @JsonProperty
        private Customer customer;
        @JsonProperty
        private BrowserInfo browser;
    }

The module only deals with UnknownPart objects: private static class MyModule extends Module {

该模块仅处理UnknownPart对象:私有静态类MyModule扩展Module {

        @Override
        public String getModuleName() {
            return "MyModule";
        }

        @Override
        public Version version() {
            return new Version(0, 0, 1, "SNAPSHOT");
        }

        @Override
        public void setupModule(Module.SetupContext context) {
            context.addBeanSerializerModifier(new org.codehaus.jackson.map.ser.BeanSerializerModifier() {
                private UnknownPartSerializer cs;

                @Override
                public JsonSerializer modifySerializer(SerializationConfig config, BasicBeanDescription beanDesc, JsonSerializer<?> serializer) {
                   return UnknownPart.class.isAssignableFrom(beanDesc.getBeanClass())
                       ? new UnknownPartSerializer((BeanSerializerBase) serializer)
                       : serializer;
                }
            });
        }
    }

And the serializer is:

序列化器是:

    private static class UnknownPartSerializer extends BeanSerializerBase {

        public UnknownPartSerializer(BeanSerializerBase src) {
            super(src);
        }

        @Override
        public void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
            UnknownPart up = (UnknownPart) bean;
            jgen.writeStartObject();
            serializeFields(up, jgen, provider);
            jgen.writeEndObject();
        }

        protected void serializeFields(UnknownPart bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
            final BeanPropertyWriter[] props;
            if (_filteredProps != null && provider.getSerializationView() != null) {
                props = _filteredProps;
            } else {
                props = _props;
            }
            int i = 0;
            try {
                for (final int len = props.length; i < len; ++i) {
                    BeanPropertyWriter prop = props[i];
                    if (prop != null) { // can have nulls in filtered list
                        prop.serializeAsField(bean, jgen, provider);
                        bean.tree.remove(prop.getName()); // new
                    }
                }
                if (_anyGetterWriter != null) {
                    _anyGetterWriter.getAndSerialize(bean, jgen, provider);
                }
                // new:
                Iterator<Entry<String, JsonNode>> it = bean.tree.getFields();
                while (it.hasNext()) {
                    Entry<String, JsonNode> e = it.next();
                    jgen.writeFieldName(e.getKey());
                    jgen.writeObject(e.getValue());
                }
            } catch (Exception e) {
                String name = (i == props.length) ? "[anySetter]"
                        : props[i].getName();
                wrapAndThrow(provider, e, bean, name);
            } catch (*Error e) {
                /* 04-Sep-2009, tatu: Dealing with this is tricky, since we do not
                 *   have many stack frames to spare... just one or two; can't
                 *   make many calls.
                 */
                JsonMappingException mapE = new JsonMappingException("Infinite recursion (*Error)", e);
                String name = (i == props.length) ? "[anySetter]"
                        : props[i].getName();
                mapE.prependPath(new JsonMappingException.Reference(bean, name));
                throw mapE;
            }
        }
    }

In the same time, I wrote a benchmark to confirm (or not) that this solution is faster than JSONObject for big Json strings... The test compares: - read with JSONObject (without mapping) - read with Jackson - read with tree with Jackson - read/write with JSONObject - read/write with Jackson And it is indeed faster :)

同时,我写了一个基准来确认(或没有)这个解决方案比大Json字符串的JSONObject更快...测试比较: - 用JSONObject读取(没有映射) - 用杰克逊读 - 用树读取杰克逊 - 用JSONObject读/写 - 用杰克逊读/写它确实更快:)

After 1000 iterations, excluding potential class load or some initialization the JVM does, i get, in nano seconds:

经过1000次迭代,排除了潜在的类加载或JVM所做的一些初始化,我得到了,在纳秒内:

-- AVERAGE ----------------------------------------------------------------------------
|    1|        860,560|        157,772|        234,654|      1,595,018|        488,427|
---------------------------------------------------------------------------------------
|     |  RO JSONObject|     RO Jackson|    R/- Jackson| R/W JSONObject|    R/W Jackson|
---------------------------------------------------------------------------------------

#3


1  

The simplest solution which I can imagine - is deserializing your JSON into Map class (for example LinkedHashMap). Please, see my below example:

我能想象的最简单的解决方案是将JSON反序列化为Map类(例如LinkedHashMap)。请看下面的例子:

import java.util.LinkedHashMap;

import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonProgram {

    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();

        String json = "{\"a\":\"java.lang.Integer\",\"b\":\"time json\",\"c\":\"action json\",\"d\":[1,2,3]}";
        System.out.println(json);

        LinkedHashMap<String, Object> map = mapper.readValue(json, LinkedHashMap.class);
        map.put("b", "Override property or create new");

        System.out.println(mapper.writeValueAsString(map));
    }
}

Above program prints:

以上程序打印:

{"a":"java.lang.Integer","b":"time json","c":"action json","d":[1,2,3]}
{"a":"java.lang.Integer","b":"Override property or create new","c":"action json","d":[1,2,3]}

If you want to change inner property on the path you can implement it in this way:

如果要更改路径上的内部属性,可以通过以下方式实现它:

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;

import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonProgram {

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();

        String json = "{\"b\":{\"bb\":{\"bbb\":20}}}";
        System.out.println(json);

        LinkedHashMap<String, Object> map = mapper.readValue(json, LinkedHashMap.class);
        JsonUpdater updater = new JsonUpdater(map);
        updater.update(Arrays.asList("b", "bb", "bbb"), 4);

        System.out.println(mapper.writeValueAsString(map));
    }
}

class JsonUpdater {

    private LinkedHashMap<String, Object> jsonMap;

    public JsonUpdater(LinkedHashMap<String, Object> jsonMap) {
        this.jsonMap = jsonMap;
    }

    public boolean update(Collection<String> propertiesOnThePath, Object newValue) {
        LinkedList<String> path = new LinkedList<String>(propertiesOnThePath);
        String lastProperty = path.removeLast();

        LinkedHashMap<String, Object> objectMap = jsonMap;
        while (!path.isEmpty()) {
            String property = path.poll();
            if (!objectMap.containsKey(property)) {
                return false;
            }

            objectMap = (LinkedHashMap<String, Object>) objectMap.get(property);
        }

        if (!objectMap.containsKey(lastProperty)) {
            return false;
        }

        objectMap.put(lastProperty, newValue);

        return false;
    }
}

Above program prints:

以上程序打印:

{"b":{"bb":{"bbb":20}}}
{"b":{"bb":{"bbb":4}}}

As we can see: value was changed. But this solution has huge disadvantage - we have to deserialize all JSON. Few thousands bytes String is not a problem for Java, but if you really want to optimaze your program you can play with ObjectNode class and ObjectMapper#readTree method. Please see below source code:

我们可以看到:价值已经改变。但是这个解决方案有很大的缺点 - 我们必须反序列化所有JSON。几千字节字符串不是Java的问题,但如果你真的想要优化你的程序,你可以使用ObjectNode类和ObjectMapper#readTree方法。请看下面的源代码:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class JacksonProgram {

    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();

        String json = "{\"a\":\"java.lang.Integer\",\"b\":\"time json\",\"c\":\"action json\",\"d\":[1,2,3]}";
        System.out.println(json);

        ObjectNode jsonTree = (ObjectNode) mapper.readTree(json);
        jsonTree.put("b", "Override property or create new");
        System.out.println(jsonTree.toString());
    }
}

Above program prints:

以上程序打印:

{"a":"java.lang.Integer","b":"time json","c":"action json","d":[1,2,3]}
{"a":"java.lang.Integer","b":"Override property or create new","c":"action json","d":[1,2,3]}

I didn't do any comparison tests, but you can test which solution works faster for you.

我没有做任何比较测试,但你可以测试哪种解​​决方案更快。

#1


2  

Full executable source with benchmark included is:

包含基准的完整可执行文件源是:

import java.io.IOException;
import java.io.StringWriter;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Random;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.Module;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.introspect.BasicBeanDescription;
import org.codehaus.jackson.map.ser.BeanPropertyWriter;
import org.codehaus.jackson.map.ser.std.BeanSerializerBase;
import org.codehaus.jackson.node.ObjectNode;
import org.json.JSONException;
import org.json.JSONObject;

public class JacksonModule {

    private static final ObjectMapper MAPPER = new ObjectMapper();
    private static final int COUNT = 0;
    private static final int REPEAT_HEADER = 40;

    static {
        MAPPER.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        MAPPER.configure(SerializationConfig.Feature.WRITE_NULL_PROPERTIES, false);
        MAPPER.registerModule(new MyModule());
    }
    private DataProcessor sdp;
    private long[] sum = new long[5];

    public static void main(String[] args) throws IOException, JSONException {
        new JacksonModule().start();
    }

    public JacksonModule() throws IOException, JSONException {
        this.sdp = new DataProcessor();
    }

    public void start() throws IOException, JSONException {
        run(-1, false); // load classes: slow
        if (COUNT > 0) {
            for (int i = 0; i < COUNT; ++i) {
                if (i % REPEAT_HEADER == 0) {
                    System.out.println("---------------------------------------------------------------------------------------");
                    print("", "RO JSONObject", "RO Jackson", "R/- Jackson", "R/W JSONObject", "R/W Jackson");
                    System.out.println("---------------------------------------------------------------------------------------");
                }
                run(i, true);
            }
            System.out.println("-- AVERAGE ----------------------------------------------------------------------------");
            print(1, sum[0] / COUNT, sum[1] / COUNT, sum[2] / COUNT, sum[3] / COUNT, sum[4] / COUNT);
            System.out.println("---------------------------------------------------------------------------------------");
            print("", "RO JSONObject", "RO Jackson", "R/- Jackson", "R/W JSONObject", "R/W Jackson");
            System.out.println("---------------------------------------------------------------------------------------");
        }
    }

    public void run(int i, boolean print) throws JSONException, IOException {
        long t1 = sdp.doReadWithJSONObject();
        long t2 = sdp.doReadWithJackson();
        long t3 = sdp.doReadForUpdatingWithJacksonButDontWrite();
        long t4 = sdp.doSomeWritingWithJSONObject();
        long t5 = sdp.doSomeWritingWithJackson();
        if (print) {
            print(i, t1, t2, t3, t4, t5);
            sum[0] += t1;
            sum[1] += t2;
            sum[2] += t3;
            sum[3] += t4;
            sum[4] += t5;
        }
    }

    private void print(int index, long t1, long t2, long t3, long t4, long t5) {
        print(Integer.toString(index), String.format("%,d", t1), String.format("%,d", t2), String.format("%,d", t3), String.format("%,d", t4), String.format("%,d", t5));
    }

    private void print(String i0, String a, String b, String c, String d, String e) {
        System.out.println("|"
                + StringUtils.leftPad(i0, 5) + "|"
                + StringUtils.leftPad(a, 15) + "|"
                + StringUtils.leftPad(b, 15) + "|"
                + StringUtils.leftPad(c, 15) + "|"
                + StringUtils.leftPad(d, 15) + "|"
                + StringUtils.leftPad(e, 15) + "|");
    }

    private static class DataProcessor {

        private DataStore store;
        private long t0, t1;

        private DataProcessor() throws IOException, JSONException {
            this.store = new DataStore(customer, browser);
        }

        public long doReadWithJSONObject() throws JSONException {
            t0 = System.nanoTime();
            JSONObject json = new JSONObject(store.readData(null)); // can throw JSONException
            JSONObject customer = json.getJSONObject("customer");  // can throw JSONException
            JSONObject browserInfo = json.getJSONObject("browser");  // can throw JSONException
            // need to do manually the mapping and figure out what is exactly in this object. Hell no!
            t1 = System.nanoTime();
            return t1 - t0;
        }

        public long doReadWithJackson() throws IOException {
            t0 = System.nanoTime();
            KnownPart obj = store.readData(null, KnownPart.class);
            t1 = System.nanoTime();
            return t1 - t0;
        }

        public long doReadForUpdatingWithJacksonButDontWrite() throws IOException {
            t0 = System.nanoTime();
            KnownPart obj = store.readDataForUpdating(null, KnownPart.class);
            t1 = System.nanoTime();
            return t1 - t0;
        }

        public long doSomeWritingWithJSONObject() throws JSONException {
            t0 = System.nanoTime();
            JSONObject json = new JSONObject(store.readData(null)); // can throw JSONException
            JSONObject customer = json.getJSONObject("customer");  // can throw JSONException
            JSONObject browserInfo = json.getJSONObject("browser");  // can throw JSONException
            customer.put("name", "Jackson Doe");
            browserInfo.put("version", "10");
            store.saveData(json);
            t1 = System.nanoTime();
            return t1 - t0;
        }

        public long doSomeWritingWithJackson() throws IOException {
            t0 = System.nanoTime();
            KnownPart obj = store.readDataForUpdating(null, KnownPart.class);
            obj.customer.name = "Jackson Doe";
            obj.browser.version = "10";
            store.saveData(obj);
            t1 = System.nanoTime();
            return t1 - t0;
        }
    }

    private static class DataStore {

        private final String data;

        private DataStore(Customer customer, BrowserInfo browser) throws IOException, JSONException {
            StringWriter sw = new StringWriter(1000);
            try (JsonGenerator jgen = MAPPER.getJsonFactory().createJsonGenerator(sw)) {
                jgen.writeStartObject();
                writeBunchOfProperties(jgen);
                jgen.writeFieldName("customer");
                jgen.writeRawValue(MAPPER.writeValueAsString(customer));
                writeBunchOfProperties(jgen);
                jgen.writeFieldName("browser");
                jgen.writeRawValue(MAPPER.writeValueAsString(browser));
                writeBunchOfProperties(jgen);
                jgen.writeEndObject();
            }
            this.data = sw.toString();
        }

        private void writeBunchOfProperties(JsonGenerator jgen) throws IOException {
            int c = new Random().nextInt(3) + 1;
            for (int i = 0; i < c; ++i) {
                jgen.writeFieldName(RandomStringUtils.random(10));
                jgen.writeRawValue(JSON_LONG);
            }
        }

        public String readData(String query) {
            return data;
        }

        private void saveData(String json) {
            // TODO
        }

        public void saveData(JSONObject json) {
            saveData(json.toString());
        }

        public void saveData(Object obj) throws IOException {
            saveData(MAPPER.writeValueAsString(obj));
        }

        public <T> T readData(String query, Class<T> clazz) throws IOException {
            return MAPPER.readValue(readData(query), clazz);
        }

        public <T extends UnknownPart> T readDataForUpdating(String query, Class<T> clazz) throws IOException {
            ObjectNode root = (ObjectNode) MAPPER.readTree(readData(query));
            T obj = MAPPER.readValue(root, clazz);
            obj.tree = root;
            return obj;
        }
    }

    private static abstract class UnknownPart {

         ObjectNode tree;
    }

    private static class KnownPart extends UnknownPart {

        @JsonProperty
        private Customer customer;
        @JsonProperty
        private BrowserInfo browser;
    }

    private static class Customer {

        @JsonProperty
        private int id;
        @JsonProperty
        private String name;
        @JsonProperty
        private Address[] addresses; // just to make it more complex for this example

        public Customer(int id, String name, Address[] addresses) {
            this.id = id;
            this.name = name;
            this.addresses = addresses;
        }

        public Customer() {
        }
    }

    private static class Address {

        @JsonProperty
        private String street;
        @JsonProperty
        private String city;

        public Address(String street, String city) {
            this.street = street;
            this.city = city;
        }

        public Address() {
        }
    }

    private static class BrowserInfo {

        @JsonProperty
        private String agent;
        @JsonProperty
        private String version;

        public BrowserInfo(String agent, String version) {
            this.agent = agent;
            this.version = version;
        }

        public BrowserInfo() {
        }
    }

    private static class MyModule extends Module {

        @Override
        public String getModuleName() {
            return "MyModule";
        }

        @Override
        public Version version() {
            return new Version(0, 0, 1, "SNAPSHOT");
        }

        @Override
        public void setupModule(Module.SetupContext context) {
            context.addBeanSerializerModifier(new org.codehaus.jackson.map.ser.BeanSerializerModifier() {
                private UnknownPartSerializer cs;

                @Override
                public JsonSerializer modifySerializer(SerializationConfig config, BasicBeanDescription beanDesc, JsonSerializer<?> serializer) {
                    return UnknownPart.class.isAssignableFrom(beanDesc.getBeanClass())
                       ? new UnknownPartSerializer((BeanSerializerBase) serializer)
                       : serializer;
                }
            });
        }
    }

    private static class UnknownPartSerializer extends BeanSerializerBase {

        public UnknownPartSerializer(BeanSerializerBase src) {
            super(src);
        }

        @Override
        public void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
            UnknownPart up = (UnknownPart) bean;
            jgen.writeStartObject();
            serializeFields(up, jgen, provider);
            jgen.writeEndObject();
        }

        protected void serializeFields(UnknownPart bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
            final BeanPropertyWriter[] props;
            if (_filteredProps != null && provider.getSerializationView() != null) {
                props = _filteredProps;
            } else {
                props = _props;
            }
            int i = 0;
            try {
                for (final int len = props.length; i < len; ++i) {
                    BeanPropertyWriter prop = props[i];
                    if (prop != null) { // can have nulls in filtered list
                        prop.serializeAsField(bean, jgen, provider);
                        bean.tree.remove(prop.getName());
                    }
                }
                if (_anyGetterWriter != null) {
                    _anyGetterWriter.getAndSerialize(bean, jgen, provider);
                }
                Iterator<Entry<String, JsonNode>> it = bean.tree.getFields();
                while (it.hasNext()) {
                    Entry<String, JsonNode> e = it.next();
                    jgen.writeFieldName(e.getKey());
                    jgen.writeObject(e.getValue());
                }
            } catch (Exception e) {
                String name = (i == props.length) ? "[anySetter]"
                        : props[i].getName();
                wrapAndThrow(provider, e, bean, name);
            } catch (*Error e) {
                /* 04-Sep-2009, tatu: Dealing with this is tricky, since we do not
                 *   have many stack frames to spare... just one or two; can't
                 *   make many calls.
                 */
                JsonMappingException mapE = new JsonMappingException("Infinite recursion (*Error)", e);
                String name = (i == props.length) ? "[anySetter]"
                        : props[i].getName();
                mapE.prependPath(new JsonMappingException.Reference(bean, name));
                throw mapE;
            }
        }
    }
    private static Customer customer = new Customer(1, "John Doe", new Address[]{
        new Address("broadway av", "new york"),
        new Address("peachtree st", "atlanta")
    });
    private static BrowserInfo browser = new BrowserInfo("IE", "6.0");
    // some json found on the internet
    private static final String JSON_LONG = "{\"web-app\": {"
            + "\"servlet\": ["
            + "{"
            + "\"servlet-name\": \"cofaxCDS\","
            + "\"servlet-class\": \"org.cofax.cds.CDSServlet\","
            + "\"init-param\": {"
            + "\"configGlossary:installationAt\": \"Philadelphia, PA\","
            + "\"configGlossary:adminEmail\": \"ksm@pobox.com\","
            + "\"configGlossary:poweredBy\": \"Cofax\","
            + "\"configGlossary:poweredByIcon\": \"/images/cofax.gif\","
            + "\"configGlossary:staticPath\": \"/content/static\","
            + "\"templateProcessorClass\": \"org.cofax.WysiwygTemplate\","
            + "\"templateLoaderClass\": \"org.cofax.FilesTemplateLoader\","
            + "\"templatePath\": \"templates\","
            + "\"templateOverridePath\": \"\","
            + "\"defaultListTemplate\": \"listTemplate.htm\","
            + "\"defaultFileTemplate\": \"articleTemplate.htm\","
            + "\"useJSP\": false,"
            + "\"jspListTemplate\": \"listTemplate.jsp\","
            + "\"jspFileTemplate\": \"articleTemplate.jsp\","
            + "\"cachePackageTagsTrack\": 200,"
            + "\"cachePackageTagsStore\": 200,"
            + "\"cachePackageTagsRefresh\": 60,"
            + "\"cacheTemplatesTrack\": 100,"
            + "\"cacheTemplatesStore\": 50,"
            + "\"cacheTemplatesRefresh\": 15,"
            + "\"cachePagesTrack\": 200,"
            + "\"cachePagesStore\": 100,"
            + "\"cachePagesRefresh\": 10,"
            + "\"cachePagesDirtyRead\": 10,"
            + "\"searchEngineListTemplate\": \"forSearchEnginesList.htm\","
            + "\"searchEngineFileTemplate\": \"forSearchEngines.htm\","
            + "\"searchEngineRobotsDb\": \"WEB-INF/robots.db\","
            + "\"useDataStore\": true,"
            + "\"dataStoreClass\": \"org.cofax.SqlDataStore\","
            + "\"redirectionClass\": \"org.cofax.SqlRedirection\","
            + "\"dataStoreName\": \"cofax\","
            + "\"dataStoreDriver\": \"com.microsoft.jdbc.sqlserver.SQLServerDriver\","
            + "\"dataStoreUrl\": \"jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon\","
            + "\"dataStoreUser\": \"sa\","
            + "\"dataStorePassword\": \"dataStoreTestQuery\","
            + "\"dataStoreTestQuery\": \"SET NOCOUNT ON;select test='test';\","
            + "\"dataStoreLogFile\": \"/usr/local/tomcat/logs/datastore.log\","
            + "\"dataStoreInitConns\": 10,"
            + "\"dataStoreMaxConns\": 100,"
            + "\"dataStoreConnUsageLimit\": 100,"
            + "\"dataStoreLogLevel\": \"debug\","
            + "\"maxUrlLength\": 500}},"
            + "{"
            + "\"servlet-name\": \"cofaxEmail\","
            + "\"servlet-class\": \"org.cofax.cds.EmailServlet\","
            + "\"init-param\": {"
            + "\"mailHost\": \"mail1\","
            + "\"mailHostOverride\": \"mail2\"}},"
            + "{"
            + "\"servlet-name\": \"cofaxAdmin\","
            + "\"servlet-class\": \"org.cofax.cds.AdminServlet\"},"
            + ""
            + "{"
            + "\"servlet-name\": \"fileServlet\","
            + "\"servlet-class\": \"org.cofax.cds.FileServlet\"},"
            + "{"
            + "\"servlet-name\": \"cofaxTools\","
            + "\"servlet-class\": \"org.cofax.cms.CofaxToolsServlet\","
            + "\"init-param\": {"
            + "\"templatePath\": \"toolstemplates/\","
            + "\"log\": 1,"
            + "\"logLocation\": \"/usr/local/tomcat/logs/CofaxTools.log\","
            + "\"logMaxSize\": \"\","
            + "\"dataLog\": 1,"
            + "\"dataLogLocation\": \"/usr/local/tomcat/logs/dataLog.log\","
            + "\"dataLogMaxSize\": \"\","
            + "\"removePageCache\": \"/content/admin/remove?cache=pages&id=\","
            + "\"removeTemplateCache\": \"/content/admin/remove?cache=templates&id=\","
            + "\"fileTransferFolder\": \"/usr/local/tomcat/webapps/content/fileTransferFolder\","
            + "\"lookInContext\": 1,"
            + "\"adminGroupID\": 4,"
            + "\"betaServer\": true}}],"
            + "\"servlet-mapping\": {"
            + "\"cofaxCDS\": \"/\","
            + "\"cofaxEmail\": \"/cofaxutil/aemail/*\","
            + "\"cofaxAdmin\": \"/admin/*\","
            + "\"fileServlet\": \"/static/*\","
            + "\"cofaxTools\": \"/tools/*\"},"
            + ""
            + "\"taglib\": {"
            + "\"taglib-uri\": \"cofax.tld\","
            + "\"taglib-location\": \"/WEB-INF/tlds/cofax.tld\"}}}";
}

#2


2  

Victory! :) I have now an implementation for it and it is not quite simple. Benchmark Jackson vs JSONObject included.

胜利! :)我现在有一个实现它并不是很简单。基准Jackson与JSONObject包括在内。

Problem and solution described piece by piece:

问题和解决方案逐件描述:

First, I have a big json string in a data store that I want to partially deserialize and update. The deserialization has to be partial, but serialization full, so I don't lose the data I didn't deserialize. Here are the object I am using for the example:

首先,我在数据存储中有一个大的json字符串,我想部分反序列化和更新。反序列化必须是部分的,但序列化完整,所以我不会丢失我没有反序列化的数据。以下是我用于示例的对象:

   private static class KnownPart {

        @JsonProperty
        private Customer customer;
        @JsonProperty
        private BrowserInfo browser;
    }

    private static class Customer {

        @JsonProperty
        private int id;
        @JsonProperty
        private String name;
        @JsonProperty
        private Address[] addresses; // just to make it more complex for this example

        public Customer(int id, String name, Address[] addresses) {
            this.id = id;
            this.name = name;
            this.addresses = addresses;
        }

        public Customer() {
        }
    }

    private static class Address {

        @JsonProperty
        private String street;
        @JsonProperty
        private String city;

        public Address(String street, String city) {
            this.street = street;
            this.city = city;
        }

        public Address() {
        }
    }

    private static class BrowserInfo {

        @JsonProperty
        private String agent;
        @JsonProperty
        private String version;

        public BrowserInfo(String agent, String version) {
            this.agent = agent;
            this.version = version;
        }

        public BrowserInfo() {
        }
    }

KnownPart object contains a lot more than 2 properties, but I have no idea which ones exactly and I can not guarantee to keep track of which properties are added to the json string...

KnownPart对象包含超过2个属性,但我不知道究竟是哪些属性,我不能保证跟踪哪些属性被添加到json字符串...

I have a data store which has its data encoded in Json. The current implementation uses JSONObject to read/write and supports jackson to read json and map it to "real" Java object (POJOs and more complex objects). While writing full Json strings with jackson is not a problem, updating an existing string with a Java object that only represents a small part of it is much harder.

我有一个数据存储,其数据在Json中编码。当前实现使用JSONObject进行读/写,并支持jackson读取json并将其映射到“真实”Java对象(PO​​JO和更复杂的对象)。虽然使用jackson编写完整的Json字符串不是问题,但使用仅代表其中一小部分的Java对象更新现有字符串要困难得多。

Data store problem:

数据存储问题:

    private static class DataStore {

        private final String data;

        private DataStore(Customer customer, BrowserInfo browser) throws IOException, JSONException {
            StringWriter sw = new StringWriter(1000);
            try (JsonGenerator jgen = MAPPER.getJsonFactory().createJsonGenerator(sw)) {
                jgen.writeStartObject();
                writeBunchOfProperties(jgen);
                jgen.writeFieldName("customer");
                jgen.writeRawValue(MAPPER.writeValueAsString(customer));
                writeBunchOfProperties(jgen);
                jgen.writeFieldName("browser");
                jgen.writeRawValue(MAPPER.writeValueAsString(browser));
                writeBunchOfProperties(jgen);
                jgen.writeEndObject();
            }
            this.data = sw.toString();
        }

        private void writeBunchOfProperties(JsonGenerator jgen) throws IOException {
            int c = new Random().nextInt(3) + 1;
            for (int i = 0; i < c; ++i) {
                jgen.writeFieldName(RandomStringUtils.random(10));
                jgen.writeRawValue(JSON_LONG);
            }
        }

        public String readData(String query) {
            return data;
        }

        private void saveData(String json) {
            // Not implemented
        }

        public void saveData(JSONObject json) {
            saveData(json.toString());
        }

        public void saveData(Object obj) throws IOException {
            // problem: ?
        }

        public <T> T readData(String query, Class<T> clazz) throws IOException {
            return MAPPER.readValue(readData(query), clazz);
        }

        public <T> T readDataForUpdating(String query, Class<T> clazz) throws IOException {
            // problem: ?
        }

At the higher level, I want to be able to do something like:

在更高层次上,我希望能够做到这样的事情:

        public long doSomeWritingWithJackson() throws IOException {
            t0 = System.nanoTime();
            KnownPart obj = store.readDataForUpdating(null, KnownPart.class);
            obj.customer.name = "Jackson Doe";
            obj.browser.version = "10";
            store.saveData(obj);
            t1 = System.nanoTime();
            return t1 - t0;
        }

without losing the data I didn't read when I save. Obviously, I don't want to read a 2nd time the data because I'm reading this from a remote host and I don't want to cache it in some static/instance map because I need this to be still very effective in a highly concurrent environment.

我没有丢失我保存时没有读到的数据。显然,我不想第二次读取数据,因为我是从远程主机读取它而我不想将它缓存在某个静态/实例映射中,因为我需要它仍然非常有效高度并发的环境。

So the solution, in a few words is: - to read the tree of the json string first and use it to deserialize the Json object into the Java object. - store the deserialize object (in KnowPart class) and store the tree in some parent abstract class - write a Jackson module to customize how bean are serialized. The code is pretty much the same than the original one with the difference that when an attribute from the KnownPart is written, it's key is removed the tree which is in the UnknownPart object, then it is easy to write the unknown part...

所以解决方案,简而言之: - 首先读取json字符串的树,然后使用它将Json对象反序列化为Java对象。 - 存储deserialize对象(在KnowPart类中)并将树存储在某个父抽象类中 - 编写一个Jackson模块来自定义bean的序列化方式。代码与原始代码几乎相同,区别在于当写入KnownPart的属性时,它的键被删除了UnknownPart对象中的树,然后很容易编写未知部分...

The main object becomes:

主要对象变为:

    private static abstract class UnknownPart {

         ObjectNode tree;
    }

    private static class KnownPart extends UnknownPart {

        @JsonProperty
        private Customer customer;
        @JsonProperty
        private BrowserInfo browser;
    }

The module only deals with UnknownPart objects: private static class MyModule extends Module {

该模块仅处理UnknownPart对象:私有静态类MyModule扩展Module {

        @Override
        public String getModuleName() {
            return "MyModule";
        }

        @Override
        public Version version() {
            return new Version(0, 0, 1, "SNAPSHOT");
        }

        @Override
        public void setupModule(Module.SetupContext context) {
            context.addBeanSerializerModifier(new org.codehaus.jackson.map.ser.BeanSerializerModifier() {
                private UnknownPartSerializer cs;

                @Override
                public JsonSerializer modifySerializer(SerializationConfig config, BasicBeanDescription beanDesc, JsonSerializer<?> serializer) {
                   return UnknownPart.class.isAssignableFrom(beanDesc.getBeanClass())
                       ? new UnknownPartSerializer((BeanSerializerBase) serializer)
                       : serializer;
                }
            });
        }
    }

And the serializer is:

序列化器是:

    private static class UnknownPartSerializer extends BeanSerializerBase {

        public UnknownPartSerializer(BeanSerializerBase src) {
            super(src);
        }

        @Override
        public void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
            UnknownPart up = (UnknownPart) bean;
            jgen.writeStartObject();
            serializeFields(up, jgen, provider);
            jgen.writeEndObject();
        }

        protected void serializeFields(UnknownPart bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
            final BeanPropertyWriter[] props;
            if (_filteredProps != null && provider.getSerializationView() != null) {
                props = _filteredProps;
            } else {
                props = _props;
            }
            int i = 0;
            try {
                for (final int len = props.length; i < len; ++i) {
                    BeanPropertyWriter prop = props[i];
                    if (prop != null) { // can have nulls in filtered list
                        prop.serializeAsField(bean, jgen, provider);
                        bean.tree.remove(prop.getName()); // new
                    }
                }
                if (_anyGetterWriter != null) {
                    _anyGetterWriter.getAndSerialize(bean, jgen, provider);
                }
                // new:
                Iterator<Entry<String, JsonNode>> it = bean.tree.getFields();
                while (it.hasNext()) {
                    Entry<String, JsonNode> e = it.next();
                    jgen.writeFieldName(e.getKey());
                    jgen.writeObject(e.getValue());
                }
            } catch (Exception e) {
                String name = (i == props.length) ? "[anySetter]"
                        : props[i].getName();
                wrapAndThrow(provider, e, bean, name);
            } catch (*Error e) {
                /* 04-Sep-2009, tatu: Dealing with this is tricky, since we do not
                 *   have many stack frames to spare... just one or two; can't
                 *   make many calls.
                 */
                JsonMappingException mapE = new JsonMappingException("Infinite recursion (*Error)", e);
                String name = (i == props.length) ? "[anySetter]"
                        : props[i].getName();
                mapE.prependPath(new JsonMappingException.Reference(bean, name));
                throw mapE;
            }
        }
    }

In the same time, I wrote a benchmark to confirm (or not) that this solution is faster than JSONObject for big Json strings... The test compares: - read with JSONObject (without mapping) - read with Jackson - read with tree with Jackson - read/write with JSONObject - read/write with Jackson And it is indeed faster :)

同时,我写了一个基准来确认(或没有)这个解决方案比大Json字符串的JSONObject更快...测试比较: - 用JSONObject读取(没有映射) - 用杰克逊读 - 用树读取杰克逊 - 用JSONObject读/写 - 用杰克逊读/写它确实更快:)

After 1000 iterations, excluding potential class load or some initialization the JVM does, i get, in nano seconds:

经过1000次迭代,排除了潜在的类加载或JVM所做的一些初始化,我得到了,在纳秒内:

-- AVERAGE ----------------------------------------------------------------------------
|    1|        860,560|        157,772|        234,654|      1,595,018|        488,427|
---------------------------------------------------------------------------------------
|     |  RO JSONObject|     RO Jackson|    R/- Jackson| R/W JSONObject|    R/W Jackson|
---------------------------------------------------------------------------------------

#3


1  

The simplest solution which I can imagine - is deserializing your JSON into Map class (for example LinkedHashMap). Please, see my below example:

我能想象的最简单的解决方案是将JSON反序列化为Map类(例如LinkedHashMap)。请看下面的例子:

import java.util.LinkedHashMap;

import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonProgram {

    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();

        String json = "{\"a\":\"java.lang.Integer\",\"b\":\"time json\",\"c\":\"action json\",\"d\":[1,2,3]}";
        System.out.println(json);

        LinkedHashMap<String, Object> map = mapper.readValue(json, LinkedHashMap.class);
        map.put("b", "Override property or create new");

        System.out.println(mapper.writeValueAsString(map));
    }
}

Above program prints:

以上程序打印:

{"a":"java.lang.Integer","b":"time json","c":"action json","d":[1,2,3]}
{"a":"java.lang.Integer","b":"Override property or create new","c":"action json","d":[1,2,3]}

If you want to change inner property on the path you can implement it in this way:

如果要更改路径上的内部属性,可以通过以下方式实现它:

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;

import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonProgram {

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();

        String json = "{\"b\":{\"bb\":{\"bbb\":20}}}";
        System.out.println(json);

        LinkedHashMap<String, Object> map = mapper.readValue(json, LinkedHashMap.class);
        JsonUpdater updater = new JsonUpdater(map);
        updater.update(Arrays.asList("b", "bb", "bbb"), 4);

        System.out.println(mapper.writeValueAsString(map));
    }
}

class JsonUpdater {

    private LinkedHashMap<String, Object> jsonMap;

    public JsonUpdater(LinkedHashMap<String, Object> jsonMap) {
        this.jsonMap = jsonMap;
    }

    public boolean update(Collection<String> propertiesOnThePath, Object newValue) {
        LinkedList<String> path = new LinkedList<String>(propertiesOnThePath);
        String lastProperty = path.removeLast();

        LinkedHashMap<String, Object> objectMap = jsonMap;
        while (!path.isEmpty()) {
            String property = path.poll();
            if (!objectMap.containsKey(property)) {
                return false;
            }

            objectMap = (LinkedHashMap<String, Object>) objectMap.get(property);
        }

        if (!objectMap.containsKey(lastProperty)) {
            return false;
        }

        objectMap.put(lastProperty, newValue);

        return false;
    }
}

Above program prints:

以上程序打印:

{"b":{"bb":{"bbb":20}}}
{"b":{"bb":{"bbb":4}}}

As we can see: value was changed. But this solution has huge disadvantage - we have to deserialize all JSON. Few thousands bytes String is not a problem for Java, but if you really want to optimaze your program you can play with ObjectNode class and ObjectMapper#readTree method. Please see below source code:

我们可以看到:价值已经改变。但是这个解决方案有很大的缺点 - 我们必须反序列化所有JSON。几千字节字符串不是Java的问题,但如果你真的想要优化你的程序,你可以使用ObjectNode类和ObjectMapper#readTree方法。请看下面的源代码:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class JacksonProgram {

    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();

        String json = "{\"a\":\"java.lang.Integer\",\"b\":\"time json\",\"c\":\"action json\",\"d\":[1,2,3]}";
        System.out.println(json);

        ObjectNode jsonTree = (ObjectNode) mapper.readTree(json);
        jsonTree.put("b", "Override property or create new");
        System.out.println(jsonTree.toString());
    }
}

Above program prints:

以上程序打印:

{"a":"java.lang.Integer","b":"time json","c":"action json","d":[1,2,3]}
{"a":"java.lang.Integer","b":"Override property or create new","c":"action json","d":[1,2,3]}

I didn't do any comparison tests, but you can test which solution works faster for you.

我没有做任何比较测试,但你可以测试哪种解​​决方案更快。