操纵JSON中的元素:更改JSON字符串中元素的位置

时间:2016-12-22 10:36:07

标签: java json jackson gson

我想交换JSON字符串中元素的位置。这样做最有效的方法是什么? Jackson或Gson是否提供任何功能来改变JSON字符串中元素的位置?

之前:

{
    "firstName": "John",
    "lastName": "Smith",
    "age": 25,
    "address": {
        "streetAddress": "21 2nd Street",
        "city": "New York",
        "state": "NY",
        "postalCode": 10021
    },
    "phoneNumbers": [
        {
            "type": "home",
            "number": "212 555-1234"
        },
        {
            "type": "fax",
            "number": "646 555-4567" 
        }
    ] 
}

在:

{
    "lastName": "Smith",
    "firstName": "John",
    "age": 25,
    "address": {
        "city": "New York",
        "streetAddress": "21 2nd Street",
        "state": "NY",
        "postalCode": 10021
    },
    "phoneNumbers": [
        {
            "type": "home",
            "number": "212 555-1234"
        },
        {
            "type": "fax",
            "number": "646 555-4567" 
        }
    ] 
}

2 个答案:

答案 0 :(得分:1)

您可以在Java类中使用Jackson JsonPropertyOrder

@JsonPropertyOrder({ "firstName", "lastName", "age" })
public class MyClass { ... }

https://fasterxml.github.io/jackson-annotations/javadoc/2.2.0/com/fasterxml/jackson/annotation/JsonPropertyOrder.html

答案 1 :(得分:0)

  

最有效的方法是什么?

通常,答案是:流媒体。流式传输可能有效的原因是您不必将整个值存储在内存中,理论上,您可以处理无限的JSON流。

以下示例是使用Java 8和Gson编写的,但将其移植到Java 7及更低版本很容易。它不接受所有可能的情况,并且由于它没有额外的逻辑,它还交换$.phoneNumbers[].type$.phoneNumbers[].number(或它是否也匹配您的请求?)。无论如何,下面的代码是这样做的想法,可以自己改进。

Swap.java

此实用程序类提供了一种方法,可以从任何读取器读取并写入交换第一个JSON属性的任何编写器(数组被视为具有元素,而不是属性键/值条目)。它以FSM方式实现,因此看起来可能很臃肿。

final class Swap {

    private Swap() {
    }

    static void swapLeadingFields(final Reader from, final Writer to)
            throws IOException {
        final JsonReader reader = new JsonReader(from);
        final JsonWriter writer = new JsonWriter(to);
        final State state = new State();
        while ( reader.peek() != END_DOCUMENT ) {
            final JsonToken token = reader.peek();
            switch ( token ) {
            case BEGIN_ARRAY:
                reader.beginArray();
                state.push(ARRAY);
                writer.beginArray();
                break;
            case END_ARRAY:
                reader.endArray();
                state.pop();
                writer.endArray();
                break;
            case BEGIN_OBJECT:
                reader.beginObject();
                state.push(OBJECT_BEGIN);
                writer.beginObject();
                break;
            case END_OBJECT:
                switch ( state.mode() ) {
                case OBJECT_VALUE_1_FOUND:
                    state.accept(e -> writeProperty(writer, e.key1, e.value1));
                    break;
                case OBJECT_VALUE_2_FOUND:
                    state.accept(e -> {
                        writeProperty(writer, e.key2, e.value2);
                        writeProperty(writer, e.key1, e.value1);
                    });
                    break;
                case OBJECT_BEGIN:
                case OBJECT_SWAPPED:
                    // do nothing
                    break;
                case OBJECT_KEY_1_FOUND:
                case OBJECT_KEY_2_FOUND:
                case ARRAY:
                    throw new IllegalStateException(String.valueOf(state.mode()));
                default:
                    throw new AssertionError(state.mode());
                }
                reader.endObject();
                state.pop();
                writer.endObject();
                break;
            case NAME:
                final String name = reader.nextName();
                switch ( state.mode() ) {
                case OBJECT_BEGIN:
                    state.accept(e -> {
                        e.mode = OBJECT_KEY_1_FOUND;
                        e.key1 = name;
                    });
                    break;
                case OBJECT_VALUE_1_FOUND:
                    state.accept(e -> {
                        e.mode = OBJECT_KEY_2_FOUND;
                        e.key2 = name;
                    });
                    break;
                case OBJECT_VALUE_2_FOUND:
                    state.accept(e -> {
                        e.mode = OBJECT_SWAPPED;
                        writeProperty(writer, e.key2, e.value2);
                        writeProperty(writer, e.key1, e.value1);
                    });
                    writer.name(name);
                    break;
                case OBJECT_SWAPPED:
                    writer.name(name);
                    break;
                case OBJECT_KEY_1_FOUND:
                case OBJECT_KEY_2_FOUND:
                case ARRAY:
                    throw new IllegalStateException(String.valueOf(state.mode()));
                default:
                    throw new AssertionError(state.mode());
                }
                break;
            case STRING:
                handleSimpleValue(state, reader::nextString, writer::value);
                break;
            case NUMBER:
                handleSimpleValue(state, () -> parseBestGsonNumber(reader.nextString()), n -> writeBestNumber(writer, n));
                break;
            case BOOLEAN:
                handleSimpleValue(state, reader::nextBoolean, writer::value);
                break;
            case NULL:
                handleSimpleValue(state, () -> {
                    reader.nextNull();
                    return null;
                }, v -> writer.nullValue());
                break;
            case END_DOCUMENT:
                // do nothing
                break;
            default:
                throw new AssertionError(token);
            }
        }
    }

    private static <T> void handleSimpleValue(final State state, final ISupplier<? extends T> supplier, final IConsumer<? super T> consumer)
            throws IOException {
        final T value = supplier.get();
        switch ( state.mode() ) {
        case OBJECT_KEY_1_FOUND:
            state.accept(e -> {
                e.mode = OBJECT_VALUE_1_FOUND;
                e.value1 = value;
            });
            break;
        case OBJECT_KEY_2_FOUND:
            state.accept(e -> {
                e.mode = OBJECT_VALUE_2_FOUND;
                e.value2 = value;
            });
            break;
        case OBJECT_SWAPPED:
            consumer.accept(value);
            break;
        case OBJECT_BEGIN:
        case OBJECT_VALUE_1_FOUND:
        case OBJECT_VALUE_2_FOUND:
            throw new IllegalStateException(String.valueOf(state.mode()));
        case ARRAY:
        default:
            throw new AssertionError(state.mode());
        }
    }

    private static void writeProperty(final JsonWriter writer, final String key, final Object value)
            throws IOException {
        writer.name(key);
        if ( value instanceof String ) {
            writer.value((String) value);
        } else if ( value instanceof Number ) {
            writeBestNumber(writer, (Number) value);
        } else if ( value instanceof Boolean ) {
            writer.value((boolean) value);
        } else {
            throw new AssertionError(value.getClass());
        }
    }

    private static void writeBestNumber(final JsonWriter writer, final Number number)
            throws IOException {
        if ( number instanceof Double ) {
            writer.value((double) number);
        } else if ( number instanceof Long ) {
            writer.value((long) number);
        } else {
            writer.value(number);
        }
    }

}

State.java

原则上,州级只是一个带有一些状态友好方法的数据包。关于为什么它具有accept(IConsumer)而不是current()switch es(特别是嵌套的)的说明没有为局部变量提供良好的范围隔离机制。

final class State {

    enum Mode {

        OBJECT_BEGIN,
        OBJECT_KEY_1_FOUND,
        OBJECT_VALUE_1_FOUND,
        OBJECT_KEY_2_FOUND,
        OBJECT_VALUE_2_FOUND,
        OBJECT_SWAPPED,
        ARRAY

    }

    static final class Element {

        Mode mode;
        String key1;
        Object value1;
        String key2;
        Object value2;

        private Element(final Mode mode) {
            this.mode = mode;
        }

    }

    private final Stack<Element> stack = new Stack<>();

    void push(final Mode mode) {
        stack.push(new Element(mode));
    }

    void pop() {
        stack.pop();
    }

    Mode mode() {
        return stack.peek().mode;
    }

    void accept(final IConsumer<? super Element> consumer)
            throws IOException {
        consumer.accept(stack.peek());
    }

}

Numbers.java

这只是一个帮助类,试图检测&#34;最窄的&#34;数据类型以最简单的方式,但它不一定有助于保持源和目标的相同文字类型(例如,reader.nextDouble()可能会导致25.010021.0结果)。

final class Numbers {

    private Numbers() {
    }

    static Number parseBestGsonNumber(final String rawNumber) {
        try {
            return Integer.parseInt(rawNumber);
        } catch ( final NumberFormatException exParseInt ) {
            try {
                return Long.parseLong(rawNumber);
            } catch ( final NumberFormatException exParseLong ) {
                return Double.parseDouble(rawNumber);
            }
        }
    }

}

一些经过检查的异常友好接口

以下接口只在JDK 8中具有未经检查的异常对应,并且可以在必要时进行增强。

IConsumer.java

interface IConsumer<T> {

    void accept(T t)
            throws IOException;

}

ISupplier.java

interface ISupplier<T> {

    T get()
            throws IOException;

}

EntryPoint.java

演示

public final class EntryPoint {

    private EntryPoint() {
    }

    public static void main(final String... args)
            throws IOException {
        try ( InputStream inputStream = currentThread().getContextClassLoader().getResourceAsStream("data.json");
              final Reader from = new InputStreamReader(inputStream) ) {
            final Writer to = new OutputStreamWriter(System.out);
            try {
                swapLeadingFields(from, to);
            } finally {
                to.flush();
            }
        }
    }

}

漂亮的印刷结果:

{
    "lastName": "Smith",
    "firstName": "John",
    "age": 25,
    "address": {
        "city": "New York",
        "streetAddress": "21 2nd Street",
        "state": "NY",
        "postalCode": 10021
    },
    "phoneNumbers": [
        {
            "number": "212 555-1234",
            "type": "home"
        },
        {
            "number": "646 555-4567",
            "type": "fax"
        }
    ]
}