如何将java.util.Properties与JSON文件串行化?

时间:2017-01-18 21:52:02

标签: java json serialization jackson gson

我有java.util.Properties类型的变量。我正在尝试将其写入JSON文件,并从该文件中读取。

Properties变量如下所示:

Properties inner3 = new Properties();
inner3.put("i1", 1);
inner3.put("i2", 100);

Properties inner2 = new Properties();
inner2.put("aStringProp", "aStringValue");
inner2.put("inner3", inner3);

Properties inner1 = new Properties();
inner1.put("aBoolProp", true);
inner1.put("inner2", inner2);

Properties topLevelProp = new Properties();
topLevelProp.put("count", 1000000);
topLevelProp.put("size", 1);
topLevelProp.put("inner1", inner1);

当然,当我将topLevelProp序列化为JSON时,我希望结果如下所示。

{
  "inner1": {
    "inner2": {
      "aStringProp": "aStringValue",
      "inner3": {
        "i2": 100,
        "i1": 1
      }
    },
    "aBoolProp": true
  },
  "size": 1,
  "count": 1000000
}

上面的JSON结果可以通过使用Gson以非常直接的方式生成,但是当它被提供相同的JSON字符串以进行去序列化时,它会失败。

Gson gson = new GsonBuilder().create();
String json = gson.toJson(topLevelProp); //{"inner1":{"inner2":{"aStringProp":"aStringValue","inner3":{"i2":100,"i1":1}},"aBoolProp":true},"size":1,"count":1000000}

//following line throws error: Expected a string but was BEGIN_OBJECT at line 1 column 12 path $.
Properties propObj = gson.fromJson(json, Properties.class); 

也和杰克逊一起试过:

ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true);
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
File file = new File("configs/config1.json");
mapper.writeValue(file, topLevelProp);

最后一行抛出错误:

  

com.fasterxml.jackson.databind.JsonMappingException:java.util.Properties无法强制转换为java.lang.String(通过引用链:java.util.Properties [" inner1"])

尝试从字符串中进行反序列化,如下所示,它失败并出现以下错误:

Properties jckProp = JsonSerializer.mapper.readValue(json, Properties.class);
  

无法从START_OBJECT标记中反序列化java.lang.String的实例    在[来源:{" inner1":{" inner2":{" aStringProp":" aStringValue"," inner3&#34 ;:{" I2":100," I1":1}}," aBoolProp":真}"大小":1, "计数":1000000}; line:1,column:11](通过引用链:java.util.Properties [" inner1"])

如何处理?

更新idea of cricket_007后找到com.fasterxml.jackson.databind.node.ObjectNode,可按以下方式使用:

ObjectNode jckProp = JsonSerializer.mapper.readValue(json, ObjectNode.class);
System.out.println(jckProp.get("size").asInt());
System.out.println("jckProp: " + jckProp);
System.out.println("jckProp.inner: " + jckProp.get("inner1"));

我认为这对我来说是前进的方式,因为我大多必须从JSON文件中读取。

3 个答案:

答案 0 :(得分:4)

你遇到的问题是你误用java.util.Properties:它不是一个多级树结构,而是一个简单的String-to-String映射。 因此,虽然从技术上讲可能会添加非String属性值(部分原因是这个类是在Java泛型之前添加的,这样可以提高类型安全性),但这不应该这样做。对于嵌套结构化,请使用java.util.Map或特定树数据结构。

至于Properties,javadocs例如说:

The Properties class represents a persistent set of properties.
The Properties can be saved to a stream or loaded from a stream.
Each key and its corresponding value in the property list is a string.
...
If the store or save method is called on a "compromised" Properties    
object that contains a non-String key or value, the call will fail. 

现在:如果你有这样的“妥协”Properties实例,那么与杰克逊或者Gson最好的赌注就是构建一个java.util.Map(或者更老的Hashtable),然后序列化它。这应该没有问题。

答案 1 :(得分:2)

正如上面StaxMan所述,你错误地使用了Properties类,而且由于缺乏类型信息,你因为使用它而面临严重的问题。但是,对于弱类型的地图,您可能也会遇到相同的情况。 如果这对您来说是必须的,那么您可以使用自定义Gson JsonDeserializer(请注意JSON数组问题):

final class PropertiesJsonDeserializer
        implements JsonDeserializer<Properties> {

    private static final JsonDeserializer<Properties> propertiesJsonDeserializer = new PropertiesJsonDeserializer();

    private PropertiesJsonDeserializer() {
    }

    static JsonDeserializer<Properties> getPropertiesJsonDeserializer() {
        return propertiesJsonDeserializer;
    }

    @Override
    public Properties deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
            throws JsonParseException {
        final Properties properties = new Properties();
        final JsonObject jsonObject = jsonElement.getAsJsonObject();
        for ( final Entry<String, JsonElement> e : jsonObject.entrySet() ) {
            properties.put(e.getKey(), parseValue(context, e.getValue()));
        }
        return properties;
    }

    private static Object parseValue(final JsonDeserializationContext context, final JsonElement valueElement) {
        if ( valueElement instanceof JsonObject ) {
            return context.deserialize(valueElement, Properties.class);
        }
        if ( valueElement instanceof JsonPrimitive ) {
            final JsonPrimitive valuePrimitive = valueElement.getAsJsonPrimitive();
            if ( valuePrimitive.isBoolean() ) {
                return context.deserialize(valueElement, Boolean.class);
            }
            if ( valuePrimitive.isNumber() ) {
                return context.deserialize(valueElement, Number.class); // depends on the JSON literal due to the lack of real number type info
            }
            if ( valuePrimitive.isString() ) {
                return context.deserialize(valueElement, String.class);
            }
            throw new AssertionError();
        }
        if ( valueElement instanceof JsonArray ) {
            throw new UnsupportedOperationException("Arrays are unsupported due to lack of type information (a generic list or a concrete type array?)");
        }
        if ( valueElement instanceof JsonNull ) {
            throw new UnsupportedOperationException("Nulls cannot be deserialized");
        }
        throw new AssertionError("Must never happen");
    }

}

因此,它可能会像这样使用:

private static final Gson gson = new GsonBuilder()
        .registerTypeAdapter(Properties.class, getPropertiesJsonDeserializer())
        .create();

public static void main(final String... args) {
    final Properties outgoingProperties = createProperties();
    out.println(outgoingProperties);
    final String json = gson.toJson(outgoingProperties);
    out.println(json);
    final Properties incomingProperties = gson.fromJson(json, Properties.class);
    out.println(incomingProperties);
}

private static Properties createProperties() {
    final Properties inner3 = new Properties();
    inner3.put("i1", 1);
    inner3.put("i2", 100);
    final Properties inner2 = new Properties();
    inner2.put("aStringProp", "aStringValue");
    inner2.put("inner3", inner3);
    final Properties inner1 = new Properties();
    inner1.put("aBoolProp", true);
    inner1.put("inner2", inner2);
    final Properties topLevelProp = new Properties();
    topLevelProp.put("count", 1000000);
    topLevelProp.put("size", 1);
    topLevelProp.put("inner1", inner1);
    return topLevelProp;
}

使用以下输出:

  

{inner1 = {inner2 = {aStringProp = aStringValue,inner3 = {i2 = 100,i1 = 1}},aBoolProp = true},size = 1,count = 1000000}
  {“inner1”:{“inner2”:{“aStringProp”:“aStringValue”,“inner3”:{“i2”:100,“i1”:1}},“aBoolProp”:true},“size”:1 , “算”:1000000}
  {inner1 = {inner2 = {aStringProp = aStringValue,inner3 = {i2 = 100,i1 = 1}},aBoolProp = true},size = 1,count = 1000000}

输入信息注入

如果您在结果JSON中注入类型信息,则可以保存一些类型信息。让我们假设您可以将数值存储为非基元,但JSON对象具有两个键,如_$T_$V来保存实际类型(确实是一个类,而不是任何java.reflect.Type,遗憾的是)和相关的值分别为了恢复属性的真实类型。这也可以应用于数组,但由于参数化以某种方式的实例缺少类型paremerization,因此仍然无法保持参数化类型(除非您可以通过{{1}到达它} instance):

Class

现在final class PropertiesJsonDeserializer implements JsonDeserializer<Properties> { private static final JsonDeserializer<Properties> propertiesJsonDeserializer = new PropertiesJsonDeserializer(); private PropertiesJsonDeserializer() { } static JsonDeserializer<Properties> getPropertiesJsonDeserializer() { return propertiesJsonDeserializer; } @Override public Properties deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) throws JsonParseException { final Properties properties = new Properties(); final JsonObject jsonObject = jsonElement.getAsJsonObject(); for ( final Entry<String, JsonElement> e : jsonObject.entrySet() ) { properties.put(e.getKey(), parseValue(context, e.getValue())); } return properties; } private static Object parseValue(final JsonDeserializationContext context, final JsonElement valueElement) { if ( valueElement instanceof JsonObject ) { return context.deserialize(valueElement, Properties.class); } if ( valueElement instanceof JsonPrimitive ) { final JsonPrimitive valuePrimitive = valueElement.getAsJsonPrimitive(); if ( valuePrimitive.isBoolean() ) { return context.deserialize(valueElement, Boolean.class); } if ( valuePrimitive.isNumber() ) { return context.deserialize(valueElement, Number.class); // depends on the JSON literal due to the lack of real number type info } if ( valuePrimitive.isString() ) { return context.deserialize(valueElement, String.class); } throw new AssertionError(); } if ( valueElement instanceof JsonArray ) { throw new UnsupportedOperationException("Arrays are unsupported due to lack of type information (a generic list or a concrete type array?)"); } if ( valueElement instanceof JsonNull ) { throw new UnsupportedOperationException("Nulls cannot be deserialized"); } throw new AssertionError("Must never happen"); } } final class TypeAwarePropertiesSerializer implements JsonSerializer<Properties> { private static final JsonSerializer<Properties> typeAwarePropertiesSerializer = new TypeAwarePropertiesSerializer(); private TypeAwarePropertiesSerializer() { } static JsonSerializer<Properties> getTypeAwarePropertiesSerializer() { return typeAwarePropertiesSerializer; } @Override public JsonElement serialize(final Properties properties, final Type type, final JsonSerializationContext context) { final JsonObject propertiesJson = new JsonObject(); for ( final Entry<Object, Object> entry : properties.entrySet() ) { final String property = (String) entry.getKey(); final Object value = entry.getValue(); if ( value instanceof Boolean ) { propertiesJson.addProperty(property, (Boolean) value); } else if ( value instanceof Character ) { propertiesJson.addProperty(property, (Character) value); } else if ( value instanceof Number ) { final JsonObject wrapperJson = newWrapperJson(value); wrapperJson.addProperty("_$V", (Number) value); propertiesJson.add(property, wrapperJson); } else if ( value instanceof String ) { propertiesJson.addProperty(property, (String) value); } else if ( value instanceof Properties || value instanceof Collection || value instanceof Map ) { propertiesJson.add(property, context.serialize(value)); } else if ( value != null ) { final Class<?> aClass = value.getClass(); if ( aClass.isArray() ) { final JsonObject wrapperJson = newWrapperJson(value); wrapperJson.add("_$V", context.serialize(value)); propertiesJson.add(property, wrapperJson); } else { throw new UnsupportedOperationException("Cannot process: " + value); } } else /* now the value is always null, Properties cannot hold nulls */ { throw new AssertionError("Must never happen"); } } return propertiesJson; } private static JsonObject newWrapperJson(final Object value) { final JsonObject wrapperJson = new JsonObject(); wrapperJson.addProperty("_$T", value.getClass().getName()); return wrapperJson; } } final class TypeAwarePropertiesDeserializer implements JsonDeserializer<Properties> { private static final JsonDeserializer<Properties> typeAwarePropertiesDeserializer = new TypeAwarePropertiesDeserializer(); private TypeAwarePropertiesDeserializer() { } static JsonDeserializer<Properties> getTypeAwarePropertiesDeserializer() { return typeAwarePropertiesDeserializer; } @Override public Properties deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) throws JsonParseException { try { final Properties properties = new Properties(); final JsonObject jsonObject = jsonElement.getAsJsonObject(); for ( final Entry<String, JsonElement> e : jsonObject.entrySet() ) { properties.put(e.getKey(), parseValue(context, e.getValue())); } return properties; } catch ( final ClassNotFoundException ex ) { throw new JsonParseException(ex); } } private static Object parseValue(final JsonDeserializationContext context, final JsonElement valueElement) throws ClassNotFoundException { if ( valueElement instanceof JsonObject ) { final JsonObject valueObject = valueElement.getAsJsonObject(); if ( isWrapperJson(valueObject) ) { return context.deserialize(getWrapperValueObject(valueObject), getWrapperClass(valueObject)); } return context.deserialize(valueElement, Properties.class); } if ( valueElement instanceof JsonPrimitive ) { final JsonPrimitive valuePrimitive = valueElement.getAsJsonPrimitive(); if ( valuePrimitive.isBoolean() ) { return context.deserialize(valueElement, Boolean.class); } if ( valuePrimitive.isNumber() ) { throw new AssertionError("Must never happen because of 'unboxing' above"); } if ( valuePrimitive.isString() ) { return context.deserialize(valueElement, String.class); } throw new AssertionError("Must never happen"); } if ( valueElement instanceof JsonArray ) { return context.deserialize(valueElement, Collection.class); } if ( valueElement instanceof JsonNull ) { throw new UnsupportedOperationException("Nulls cannot be deserialized"); } throw new AssertionError("Must never happen"); } private static boolean isWrapperJson(final JsonObject valueObject) { return valueObject.has("_$T") && valueObject.has("_$V"); } private static Class<?> getWrapperClass(final JsonObject valueObject) throws ClassNotFoundException { return Class.forName(valueObject.get("_$T").getAsJsonPrimitive().getAsString()); } private static JsonElement getWrapperValueObject(final JsonObject valueObject) { return valueObject.get("_$V"); } } 也可以填充:

topLevelProp

如果你应用了这些特殊的JSON反序列化器:

topLevelProp.put("ARRAY", new String[]{ "foo", "bar" });
topLevelProp.put("RAW_LIST", asList("foo", "bar"));

示例输出:

  

{RAW_LIST = [foo,bar],inner1 = {inner2 = {aStringProp = aStringValue,inner3 = {i2 = 100,i1 = 1}},aBoolProp = true},size = 1,count = 1000000,ARRAY = [Ljava.lang.String; @ b81eda8}
  { “RAW_LIST”:[ “foo” 的, “酒吧”], “inner1”:{ “inner2”:{ “aStringProp”: “aStringValue”, “inner3”:{ “I2”:{ “_ $ T”:” java.lang.Integer中”, “_ $ V”:100}, “I1”:{ “_ $ T”: “java.lang.Integer中”, “_ $ V”:1}}}, “aBoolProp”:真}, “大小”:{ “_ $ T”: “java.lang.Integer中”, “_ $ V”:1}, “计数”:{ “_ $ T”: “java.lang.Integer中的” “_ $ V”:1000000}, “阵列”:{ “_ $ T”: “[Ljava.lang.String;”, “_ $ V”:[ “foo” 的, “酒吧”]}}
  {RAW_LIST = [foo,bar],inner1 = {inner2 = {aStringProp = aStringValue,inner3 = {i2 = 100,i1 = 1}},aBoolProp = true},size = 1,count = 1000000,ARRAY = [Ljava。 lang.String; @ e2144e4}

总结两种方法,您可能希望消除弱类型的需要,并在可能的情况下引入显式的POJO映射。

答案 2 :(得分:0)

由于我只需要反序列化功能,即为传入的Json(在我的情况下是REST端点)生成Java属性,所以我很快就破解了该解决方案:

public class Configuration extends Properties {
    public void load(JsonElement json) {
        addJson("", json);
        return;
    }

    public void addJson(String root, JsonElement json) {

        // recursion for objects
        if (json instanceof JsonObject) {
            if (!root.equals("")) root += ".";
            final JsonObject jsonObject = json.getAsJsonObject();
            for ( final Entry<String, JsonElement> e : jsonObject.entrySet() ) {
            addJson(root + e.getKey(), e.getValue());
            }           
            return;
        }

        // recursion for arrays
        if (json instanceof JsonArray) {
            final JsonArray jsonArray = json.getAsJsonArray();
            if (!root.equals("")) root += ".";
            int count = 0;
            for(final JsonElement e : jsonArray) {
                addJson(root+count, e);
                count++;
            }
            return;
        }

        // leaves: add property
        this.setProperty(root, json.getAsString());
    }
}

如您所见,这正在扩展Properties类。当然,另一种选择是预先初始化Properties对象并将其传递给递归。

我希望这对某人有用:-)