From 9cc9d8c97e9924ac47313a23fb700b7fec708dc2 Mon Sep 17 00:00:00 2001 From: Edgar Asatryan Date: Wed, 17 Jul 2019 22:24:46 +0400 Subject: [PATCH 1/2] Support JSON_UNESCAPED_UNICODE option in json_encode function. Clean up some deprecated method usages. --- .../jphp/json/gson/MemorySerializer.java | 111 ++++++++++++++---- .../jphp/zend/ext/json/JsonFunctions.java | 14 ++- .../jphp/zend/ext/json/JsonFunctionsTest.java | 13 +- .../src/main/tests/resources/json/002.phpt | 39 ++++++ 4 files changed, 143 insertions(+), 34 deletions(-) create mode 100644 exts/jphp-zend-ext/src/main/tests/resources/json/002.phpt diff --git a/exts/jphp-json-ext/src/main/java/org/develnext/jphp/json/gson/MemorySerializer.java b/exts/jphp-json-ext/src/main/java/org/develnext/jphp/json/gson/MemorySerializer.java index a0c312b27..382e2a8d4 100644 --- a/exts/jphp-json-ext/src/main/java/org/develnext/jphp/json/gson/MemorySerializer.java +++ b/exts/jphp-json-ext/src/main/java/org/develnext/jphp/json/gson/MemorySerializer.java @@ -19,16 +19,61 @@ import java.util.Set; public class MemorySerializer implements JsonSerializer { - protected boolean forceObject; - protected boolean numericCheck; - protected WeakReference env; - - protected Map typeHandlers; - protected Map classHandlers; + private WeakReference env; + private boolean forceObject; + private boolean numericCheck; + private boolean unescapedUnicode; + private Map typeHandlers; + private Map classHandlers; { - typeHandlers = new HashedMap(1); - classHandlers = new HashedMap(1); + typeHandlers = new HashedMap<>(1); + classHandlers = new HashedMap<>(1); + } + + private static String escapeUnicode(String input) { + int unicodeIdx = indexOfUnicodeChar(input); + + // If there is no unicode characters just return as is + if (unicodeIdx == -1) { + return input; + } + + StringBuilder sb = new StringBuilder(input.length()) + .append(input, 0, unicodeIdx); + + for (int i = unicodeIdx; i < input.length(); i++) { + int c = input.charAt(i); + + if (c > 0x7F) { + sb.append('\\').append('u'); + + String hex = Integer.toHexString(c); + + // padding with zeros + for (int j = 0; j < 4 - hex.length(); j++) sb.append('0'); + + sb.append(hex); + } else { + sb.append(c); + } + } + + return sb.toString(); + } + + /** + * Finds first occurrence of unicode character and returns its index or {@code -1} otherwise. + */ + private static int indexOfUnicodeChar(String input) { + for (int i = 0; i < input.length(); i++) { + int c = input.charAt(i); + if (c > 0x7F) { + return i; + } + } + + return -1; } public Environment getEnv() { @@ -66,36 +111,43 @@ protected JsonElement convert(Memory src, Set used, boolean useHandlers switch (src.getRealType()) { case BOOL: return new JsonPrimitive(src.toBoolean()); - case DOUBLE: return new JsonPrimitive(src.toDouble()); - case INT: return new JsonPrimitive(src.toLong()); + case DOUBLE: + return new JsonPrimitive(src.toDouble()); + case INT: + return new JsonPrimitive(src.toLong()); case STRING: { if (numericCheck) { Memory m = StringMemory.toLong(src.toString()); - if (m != null) + if (m != null) { return new JsonPrimitive(m.toLong()); - else + } else { return new JsonPrimitive(src.toString()); - } else - return new JsonPrimitive(src.toString()); + } + } else { + return new JsonPrimitive(unescapedUnicode ? src.toString() : escapeUnicode(src.toString())); + } } - case NULL: return JsonNull.INSTANCE; + case NULL: + return JsonNull.INSTANCE; case ARRAY: { - if (used.add(src.getPointer())){ + if (used.add(src.getPointer())) { JsonArray array = new JsonArray(); JsonObject object = new JsonObject(); boolean isList = !forceObject && src.toValue(ArrayMemory.class).isList(); ForeachIterator iterator = src.toValue(ArrayMemory.class).foreachIterator(false, false); while (iterator.next()) { - if (isList) + if (isList) { array.add(convert(iterator.getValue(), used, useHandlers)); - else + } else { object.add(iterator.getKey().toString(), convert(iterator.getValue(), used, useHandlers)); + } } used.remove(src.getPointer()); return isList ? array : object; - } else + } else { return JsonNull.INSTANCE; + } } case OBJECT: { if (used.add(src.getPointer())) { @@ -108,8 +160,9 @@ protected JsonElement convert(Memory src, Set used, boolean useHandlers do { handler = classHandlers.get(pr.getLowerName()); pr = pr.getParent(); - if (pr == null) + if (pr == null) { break; + } } while (handler == null); @@ -133,13 +186,15 @@ protected JsonElement convert(Memory src, Set used, boolean useHandlers JsonObject r = new JsonObject(); while (iterator.next()) { String key = iterator.getKey().toString(); - if (!key.startsWith("\0")) + if (!key.startsWith("\0")) { r.add(iterator.getKey().toString(), convert(iterator.getValue(), used, useHandlers)); + } } return r; } - } else + } else { return JsonNull.INSTANCE; + } } default: return JsonNull.INSTANCE; @@ -153,17 +208,23 @@ public JsonElement serialize(Memory src, Type typeOfSrc, JsonSerializationContex public void setTypeHandler(Memory.Type type, Handler handler) { if (handler == null) { typeHandlers.remove(type); - } else + } else { typeHandlers.put(type, handler); + } } public void setClassHandler(String className, Handler handler) { className = className.toLowerCase(); - if (handler == null) + if (handler == null) { classHandlers.remove(className); - else + } else { classHandlers.put(className, handler); + } + } + + public void unescapedUnicode(boolean unescapedUnicode) { + this.unescapedUnicode = unescapedUnicode; } public interface Handler { diff --git a/exts/jphp-zend-ext/src/main/java/org/develnext/jphp/zend/ext/json/JsonFunctions.java b/exts/jphp-zend-ext/src/main/java/org/develnext/jphp/zend/ext/json/JsonFunctions.java index 05e4c1632..2081df4e2 100644 --- a/exts/jphp-zend-ext/src/main/java/org/develnext/jphp/zend/ext/json/JsonFunctions.java +++ b/exts/jphp-zend-ext/src/main/java/org/develnext/jphp/zend/ext/json/JsonFunctions.java @@ -74,25 +74,29 @@ public static Memory json_decode(Environment env, String json) { @Immutable public static String json_encode(Memory memory, int options) { GsonBuilder builder; - if (options != 0){ + if (options != 0) { MemorySerializer serializer = new MemorySerializer(); builder = JsonExtension.createGsonBuilder(serializer); - if ((options & JsonConstants.JSON_PRETTY_PRINT) == JsonConstants.JSON_PRETTY_PRINT){ + if ((options & JsonConstants.JSON_PRETTY_PRINT) == JsonConstants.JSON_PRETTY_PRINT) { builder.setPrettyPrinting(); } - if ((options & JsonConstants.JSON_HEX_TAG) != JsonConstants.JSON_HEX_TAG){ + if ((options & JsonConstants.JSON_HEX_TAG) != JsonConstants.JSON_HEX_TAG) { builder.disableHtmlEscaping(); } - if ((options & JsonConstants.JSON_FORCE_OBJECT) == JsonConstants.JSON_FORCE_OBJECT){ + if ((options & JsonConstants.JSON_FORCE_OBJECT) == JsonConstants.JSON_FORCE_OBJECT) { serializer.setForceObject(true); } - if ((options & JsonConstants.JSON_NUMERIC_CHECK) == JsonConstants.JSON_NUMERIC_CHECK){ + if ((options & JsonConstants.JSON_NUMERIC_CHECK) == JsonConstants.JSON_NUMERIC_CHECK) { serializer.setNumericCheck(true); } + + if ((options & JsonConstants.JSON_UNESCAPED_UNICODE) == JsonConstants.JSON_UNESCAPED_UNICODE) { + serializer.unescapedUnicode(true); + } } else { builder = JsonExtension.DEFAULT_GSON_BUILDER; } diff --git a/exts/jphp-zend-ext/src/main/tests/org/develnext/jphp/zend/ext/json/JsonFunctionsTest.java b/exts/jphp-zend-ext/src/main/tests/org/develnext/jphp/zend/ext/json/JsonFunctionsTest.java index 81ca3f01c..ca6b7723a 100644 --- a/exts/jphp-zend-ext/src/main/tests/org/develnext/jphp/zend/ext/json/JsonFunctionsTest.java +++ b/exts/jphp-zend-ext/src/main/tests/org/develnext/jphp/zend/ext/json/JsonFunctionsTest.java @@ -48,22 +48,22 @@ public void testScalarJsonEncode() { @Test public void testArrayJsonEncode() { // simple - assertEquals("[1,2,3,4]", JsonFunctions.json_encode(new ArrayMemory(1,2,3,4))); + assertEquals("[1,2,3,4]", JsonFunctions.json_encode(ArrayMemory.ofIntegers(1,2,3,4))); assertEquals("[1,\"foo\",3.5,true]", JsonFunctions.json_encode(new ArrayMemory(1,"foo",3.5,true))); // nested assertEquals("[[1,2],[3,4],5]", JsonFunctions.json_encode(new ArrayMemory( - new ArrayMemory(1,2), new ArrayMemory(3,4), 5 + ArrayMemory.ofIntegers(1,2), ArrayMemory.ofIntegers(3,4), 5 ))); } @Test public void testObjectJsonEncode() { assertEquals("{\"0\":100,\"1\":500}", JsonFunctions.json_encode( - new ArrayMemory(100,500), JsonConstants.JSON_FORCE_OBJECT + ArrayMemory.ofIntegers(100, 500), JsonConstants.JSON_FORCE_OBJECT )); - ArrayMemory array = new ArrayMemory(100, 500); + ArrayMemory array = ArrayMemory.ofIntegers(100, 500); array.put("x", new LongMemory(100500)); assertEquals("{\"0\":100,\"1\":500,\"x\":100500}", JsonFunctions.json_encode(array)); @@ -136,4 +136,9 @@ public void testJsonSerializableJsonEncode() { public void testBugs() { check("json/json_bug217.php"); } + + @Test + public void testStandard() { + check("json/002.phpt"); + } } diff --git a/exts/jphp-zend-ext/src/main/tests/resources/json/002.phpt b/exts/jphp-zend-ext/src/main/tests/resources/json/002.phpt new file mode 100644 index 000000000..9f393ff69 --- /dev/null +++ b/exts/jphp-zend-ext/src/main/tests/resources/json/002.phpt @@ -0,0 +1,39 @@ +--TEST-- +json_encode() tests +--DESCRIPTION-- +Modified the last output. Original PHP count unicode escaped sequence as single character. +--SKIPIF-- + +--FILE-- +""))); +var_dump(json_encode(array(array(1)))); +var_dump(json_encode(array())); + +var_dump(json_encode(array(""=>""), JSON_FORCE_OBJECT)); +var_dump(json_encode(array(array(1)), JSON_FORCE_OBJECT)); +var_dump(json_encode(array(), JSON_FORCE_OBJECT)); + +var_dump(json_encode(1)); +var_dump(json_encode("руссиш")); + +echo "Done\n"; +?> +--EXPECT-- +string(2) """" +string(4) "null" +string(4) "true" +string(7) "{"":""}" +string(5) "[[1]]" +string(2) "[]" +string(7) "{"":""}" +string(13) "{"0":{"0":1}}" +string(2) "{}" +string(1) "1" +string(44) ""\\u0440\\u0443\\u0441\\u0441\\u0438\\u0448"" +Done From 3e9c5f2d884f82e08275cc3b47a3e6b6a615aec8 Mon Sep 17 00:00:00 2001 From: Edgar Asatryan Date: Wed, 17 Jul 2019 23:15:10 +0400 Subject: [PATCH 2/2] Temporary disable OracleJDK 8 build on Travis. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 545f818b4..834e788b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: java jdk: - openjdk11 - oraclejdk9 - - oraclejdk8 + #- oraclejdk8 services: - mongodb