001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.json;
018
019import java.util.ArrayList;
020import java.util.LinkedHashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.springframework.util.StringUtils;
025
026/**
027 * Really basic JSON parser for when you have nothing else available. Comes with some
028 * limitations with respect to the JSON specification (e.g. only supports String values),
029 * so users will probably prefer to have a library handle things instead (Jackson or Snake
030 * YAML are supported).
031 *
032 * @author Dave Syer
033 * @author Jean de Klerk
034 * @author Stephane Nicoll
035 * @since 1.2.0
036 * @see JsonParserFactory
037 */
038public class BasicJsonParser extends AbstractJsonParser {
039
040        @Override
041        public Map<String, Object> parseMap(String json) {
042                return parseMap(json, this::parseMapInternal);
043        }
044
045        @Override
046        public List<Object> parseList(String json) {
047                return parseList(json, this::parseListInternal);
048        }
049
050        private List<Object> parseListInternal(String json) {
051                List<Object> list = new ArrayList<>();
052                json = trimLeadingCharacter(trimTrailingCharacter(json, ']'), '[');
053                for (String value : tokenize(json)) {
054                        list.add(parseInternal(value));
055                }
056                return list;
057        }
058
059        private Object parseInternal(String json) {
060                if (json.startsWith("[")) {
061                        return parseListInternal(json);
062                }
063                if (json.startsWith("{")) {
064                        return parseMapInternal(json);
065                }
066                if (json.startsWith("\"")) {
067                        return trimTrailingCharacter(trimLeadingCharacter(json, '"'), '"');
068                }
069                try {
070                        return Long.valueOf(json);
071                }
072                catch (NumberFormatException ex) {
073                        // ignore
074                }
075                try {
076                        return Double.valueOf(json);
077                }
078                catch (NumberFormatException ex) {
079                        // ignore
080                }
081                return json;
082        }
083
084        private static String trimTrailingCharacter(String string, char c) {
085                if (!string.isEmpty() && string.charAt(string.length() - 1) == c) {
086                        return string.substring(0, string.length() - 1);
087                }
088                return string;
089        }
090
091        private static String trimLeadingCharacter(String string, char c) {
092                if (!string.isEmpty() && string.charAt(0) == c) {
093                        return string.substring(1);
094                }
095                return string;
096        }
097
098        private Map<String, Object> parseMapInternal(String json) {
099                Map<String, Object> map = new LinkedHashMap<>();
100                json = trimLeadingCharacter(trimTrailingCharacter(json, '}'), '{');
101                for (String pair : tokenize(json)) {
102                        String[] values = StringUtils.trimArrayElements(StringUtils.split(pair, ":"));
103                        String key = trimLeadingCharacter(trimTrailingCharacter(values[0], '"'), '"');
104                        Object value = parseInternal(values[1]);
105                        map.put(key, value);
106                }
107                return map;
108        }
109
110        private List<String> tokenize(String json) {
111                List<String> list = new ArrayList<>();
112                int index = 0;
113                int inObject = 0;
114                int inList = 0;
115                boolean inValue = false;
116                boolean inEscape = false;
117                StringBuilder build = new StringBuilder();
118                while (index < json.length()) {
119                        char current = json.charAt(index);
120                        if (inEscape) {
121                                build.append(current);
122                                index++;
123                                inEscape = false;
124                                continue;
125                        }
126                        if (current == '{') {
127                                inObject++;
128                        }
129                        if (current == '}') {
130                                inObject--;
131                        }
132                        if (current == '[') {
133                                inList++;
134                        }
135                        if (current == ']') {
136                                inList--;
137                        }
138                        if (current == '"') {
139                                inValue = !inValue;
140                        }
141                        if (current == ',' && inObject == 0 && inList == 0 && !inValue) {
142                                list.add(build.toString());
143                                build.setLength(0);
144                        }
145                        else if (current == '\\') {
146                                inEscape = true;
147                        }
148                        else {
149                                build.append(current);
150                        }
151                        index++;
152                }
153                if (build.length() > 0) {
154                        list.add(build.toString());
155                }
156                return list;
157        }
158
159}