我如何从json中恢复对象的地图?

时间:2013-05-17 12:38:49

标签: java json jsonserializer serializer

我有这个班级

public static class SomeClass {
    public SomeClass(String field) {
        this.field = field;
    }

    private final String field;

    public String getField() {
        return field;
    }
}

我也有这个测试(已编辑

@Test
public void testStringifyMapOfObjects() {
    Map<String, SomeClass> original = Maps.newTreeMap();
    original.put("first", new SomeClass("a"));
    original.put("second", new SomeClass("b"));
    String encoded = JsonUtil.toJson(original);
    Map<String, SomeClass> actual = JsonUtil.fromJson(encoded, Map.class);
    Assert.assertEquals("{'first':{'field':'a'},'second':{'field':'b'}}", encoded.replaceAll("\\s", "").replaceAll("\"", "'"));
    Assert.assertEquals(original.get("first"), actual.get("first"));
}

测试失败,

junit.framework.AssertionFailedError: expected:<eu.ec.dgempl.eessi.facade.transport.test.TestToolTest$SomeClass@6e3ed98c> but was:<{field=a}>
    at junit.framework.Assert.fail(Assert.java:47)
    at junit.framework.Assert.failNotEquals(Assert.java:277)
    at junit.framework.Assert.assertEquals(Assert.java:64)
    at junit.framework.Assert.assertEquals(Assert.java:71)
    at eu.ec.dgempl.eessi.facade.transport.test.TestToolTest.testStringifyMapOfObjects(TestToolTest.java:90)

我可以让json正确地将对象序列化为地图的值,还是应该使用别的东西?

编辑

public class JsonUtil {
    private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(JsonUtil.class);

    public static <T> String toJson(T data) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(Feature.INDENT_OUTPUT, true);
        try {
            return mapper.writeValueAsString(data);
        } catch (IOException e) {
            LOG.warn("can't format a json object from [" + data + "]", e);
            return null;
        }
        //
        // return Json.stringify(Json.toJson(data));
    }

    public static <T> T fromJson(String description, Class<T> theClass) {
        try {
            JsonNode parse = new ObjectMapper().readValue(description, JsonNode.class);
            T fromJson = new ObjectMapper().treeToValue(parse, theClass);
            return fromJson;
        } catch (JsonParseException e) {
            // throw new RuntimeException("can't parse a json object of type " + theClass.getName() + " from [" + description + "]", e);
            LOG.warn("can't parse a json object from [" + description + "]", e);
            return null;
        } catch (JsonMappingException e) {
            // throw new RuntimeException("can't parse a json object of type " + theClass.getName() + " from [" + description + "]", e);
            LOG.warn("can't parse a json object from [" + description + "]", e);
            return null;
        } catch (IOException e) {
            // throw new RuntimeException("can't parse a json object of type " + theClass.getName() + " from [" + description + "]", e);
            LOG.warn("can't parse a json object from [" + description + "]", e);
            return null;
        }
    }
}

2 个答案:

答案 0 :(得分:3)

您遇到了与Java泛型相关的问题。总而言之,当将数据反序列化为不可重定义的类型(也就是在运行时无法获得实际类型信息的类型)时,您需要使用超类型令牌。通过阅读这些SO帖子,您可以获得有关超类型令牌(以及您需要使用它的原因)的更多详细信息:

还有杰克逊的文件:

基本问题是,当您使用典型的通用对象时,对象的实际类型参数在运行时不可用。因此,Jackson不知道将您的数据实例化和反序列化为哪个实际类

解决问题的最简单方法是为JSON实用程序类添加一个重载,它接受一个类型引用(而不是Class<T>)。例如:

public static <T> T fromJson(String json, TypeReference<T> typeRef) {
     if(json == null || typeRef == null) return null;

     return new ObjectMapper().readValue(json, typeRef);
}

如此使用:

Map<String, SomeClass> actual = JsonUtil.fromJson(
    encoded,
    new TypeReference<Map<String, SomeClass>>(){});

答案 1 :(得分:0)

我发现最简单的解决方案是创建一个包含地图的“容器”类。这可能是因为容器具有足够的地图类型细节,而不是直接使用地图的情况。

public static class SomeClass {
    private final String field;

    private SomeClass() {
        this("wrong");
    }
    public SomeClass(String field) {
        this.field = field;
    }

    public String getField() {
        return field;
    }
    @Override
    public String toString() {
        return "SomeClass[" + field + "]";
    }
}

public static class SomeClassContainer {
    private final Map<String, SomeClass> all = Maps.newTreeMap();

    public Map<String, SomeClass> getAll() {
        return all;
    }
}

在此之后...更新的测试是

@Test
public void testStringifyMapOfObjects() {
    SomeClassContainer original = new SomeClassContainer();
    original.getAll().put("first", new SomeClass("a"));
    original.getAll().put("second", new SomeClass("b"));
    String encoded = JsonUtil.toJson(original);
    System.out.println(encoded);
    SomeClassContainer actual = JsonUtil.fromJson(encoded, SomeClassContainer.class);
    System.out.println(ObjectUtils.toString(actual));
    Assert.assertEquals("{'all':{'first':{'field':'a'},'second':{'field':'b'}}}", encoded.replaceAll("\\s", "").replaceAll("[\"]", "'"));
    Assert.assertEquals("class eu.ec.dgempl.eessi.facade.transport.test.TestToolTest$SomeClass", actual.getAll().get("first").getClass().toString());
    Assert.assertEquals(original.getAll().get("first").toString(), actual.getAll().get("first").toString());
    Assert.assertEquals(original.getAll().get("second").toString(), actual.getAll().get("second").toString());
}