如何将java.util.Properties序列化到JSON文件和从JSON文件中序列化



我有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);

无法反序列化java.lang.String的实例,该实例超出START_OBECT令牌在[源:{"inner1":{"inner2":{"aStringProp":"aStringValue","inner3":{"i2":100,"i1":1}},"aPoolProp":true},"size":1,"count":1000000};行:1,列:11](通过引用链:java.util.Properties["inner1"])

如何处理?

更新:遵循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文件中读取。

问题是您滥用了java.util.Properties:它不是一个多级树结构,而是一个简单的字符串到字符串映射。因此,虽然在技术上可以添加非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实例,您与Jackson或Gson的最佳选择是构建一个java.util.Map(或者可能是旧的Hashtable),并对其进行序列化。这应该不会出现问题。

正如StaxMan在上面所说,您正在滥用Properties类,并且由于缺乏类型信息,这样使用它会遇到严重问题。然而,对于弱类型映射,您可能也会遇到同样的情况如果这是必须的,那么您可以使用自定义的GsonJsonDeserializer(注意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={inner={aStringProp=aStringValue,inner3={i2=100,i1=1}},aPoolProp=true},size=1,count=1000000}
{"inner1":{值,inner3={i2=100,i1=1}},aPoolProp=true},size=1,count=1000000}


类型信息注入

不过,如果在结果JSON中注入类型信息,则可以保存一些类型信息。假设您可以将数值存储为非基元,而是具有两个键(如_$T_$V)的JSON对象,以分别保存实际类型(不幸的是,实际上是一个类,而不是任何java.reflect.Type)和关联值,从而恢复属性的真实类型。这也可以应用于数组,但由于对以某种方式参数化的实例缺乏类型聚合,因此仍然无法保存参数化类型(除非您可以通过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也可以填充:

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

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

private static final Gson typeAwareGson = new GsonBuilder()
.registerTypeAdapter(Properties.class, getTypeAwarePropertiesSerializer())
.registerTypeAdapter(Properties.class, getTypeAwarePropertiesDeserializer())
.create();

样本输出:

{RAW_LIST=[foo,bar],inner1={inner2={aStringProp=aStringValue,inner3={i2=100,i1=1}},aPoolProp=true},size=1,count=1000000,ARRAY=[Ljava.lang.String;@b81eda8}
{"RAW_LIST":["foo","bar"],"inner1":{:"java.lang.Integer","_$V":100},"i1":{"_$T":"java.lang.Integer","_$V":1}}}},"aPoolProp":true},"size":{"_$T":"java.lang.Integer","_$V":1},"count":{oolProp=true},size=1,count=1000000,ARRAY=[Ljava.lang.String;@e2144e4}

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

由于我只需要一个反序列化功能,即为传入的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对象,并将其传递到递归中。

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

最新更新