├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs ├── org.eclipse.jdt.core.prefs └── org.eclipse.jdt.ui.prefs ├── LICENSE ├── README.md ├── pom.xml └── src └── com └── esotericsoftware └── jsonbeans ├── Json.java ├── JsonException.java ├── JsonReader.java ├── JsonSerializable.java ├── JsonSerializer.java ├── JsonValue.java ├── JsonWriter.java ├── Null.java ├── ObjectMap.java ├── OrderedMap.java ├── OutputType.java └── ReadOnlySerializer.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary Files 2 | *~ 3 | .*.swp 4 | .DS_STORE 5 | 6 | bin/ 7 | target/ 8 | .idea/ 9 | *.iml 10 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | jsonbeans 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/com/esotericsoftware/jsonbeans/JsonReader.java=UTF-8 3 | encoding/=Cp1252 4 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled 3 | org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore 4 | org.eclipse.jdt.core.compiler.annotation.nonnull=com.esotericsoftware.jsonbeans.Null.NonNull 5 | org.eclipse.jdt.core.compiler.annotation.nonnull.secondary= 6 | org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=com.esotericsoftware.jsonbeans.Null.NonNullByDefault 7 | org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= 8 | org.eclipse.jdt.core.compiler.annotation.nullable=com.esotericsoftware.jsonbeans.Null 9 | org.eclipse.jdt.core.compiler.annotation.nullable.secondary=com.esotericsoftware.spine.launcher.utils.Null,com.esotericsoftware.spine.server.utils.Null,com.esotericsoftware.spine.editor.utils.Null 10 | org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled 11 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 12 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 13 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 14 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 15 | org.eclipse.jdt.core.compiler.compliance=1.8 16 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 17 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 18 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 19 | org.eclipse.jdt.core.compiler.doc.comment.support=enabled 20 | org.eclipse.jdt.core.compiler.problem.APILeak=warning 21 | org.eclipse.jdt.core.compiler.problem.annotatedTypeArgumentToUnannotated=info 22 | org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning 23 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 24 | org.eclipse.jdt.core.compiler.problem.autoboxing=ignore 25 | org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning 26 | org.eclipse.jdt.core.compiler.problem.deadCode=ignore 27 | org.eclipse.jdt.core.compiler.problem.deprecation=ignore 28 | org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled 29 | org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled 30 | org.eclipse.jdt.core.compiler.problem.discouragedReference=warning 31 | org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore 32 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 33 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 34 | org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore 35 | org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore 36 | org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled 37 | org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore 38 | org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning 39 | org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning 40 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 41 | org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning 42 | org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled 43 | org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning 44 | org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore 45 | org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore 46 | org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning 47 | org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled 48 | org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled 49 | org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled 50 | org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private 51 | org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore 52 | org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning 53 | org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore 54 | org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore 55 | org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled 56 | org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore 57 | org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore 58 | org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=enabled 59 | org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public 60 | org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag 61 | org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore 62 | org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=enabled 63 | org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private 64 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore 65 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled 66 | org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore 67 | org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore 68 | org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning 69 | org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning 70 | org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore 71 | org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning 72 | org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning 73 | org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=info 74 | org.eclipse.jdt.core.compiler.problem.nullReference=warning 75 | org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning 76 | org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning 77 | org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning 78 | org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore 79 | org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning 80 | org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning 81 | org.eclipse.jdt.core.compiler.problem.potentialNullReference=info 82 | org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore 83 | org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore 84 | org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning 85 | org.eclipse.jdt.core.compiler.problem.redundantNullCheck=info 86 | org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore 87 | org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore 88 | org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore 89 | org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore 90 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning 91 | org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled 92 | org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning 93 | org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled 94 | org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled 95 | org.eclipse.jdt.core.compiler.problem.suppressWarningsNotFullyAnalysed=ignore 96 | org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled 97 | org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning 98 | org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning 99 | org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning 100 | org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled 101 | org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=ignore 102 | org.eclipse.jdt.core.compiler.problem.unclosedCloseable=ignore 103 | org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore 104 | org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning 105 | org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning 106 | org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled 107 | org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info 108 | org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore 109 | org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore 110 | org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore 111 | org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning 112 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore 113 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled 114 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled 115 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=enabled 116 | org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore 117 | org.eclipse.jdt.core.compiler.problem.unusedImport=ignore 118 | org.eclipse.jdt.core.compiler.problem.unusedLabel=warning 119 | org.eclipse.jdt.core.compiler.problem.unusedLocal=ignore 120 | org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore 121 | org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore 122 | org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled 123 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=enabled 124 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=enabled 125 | org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=ignore 126 | org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore 127 | org.eclipse.jdt.core.compiler.problem.unusedWarningToken=ignore 128 | org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning 129 | org.eclipse.jdt.core.compiler.release=enabled 130 | org.eclipse.jdt.core.compiler.source=1.8 131 | org.eclipse.jdt.core.formatter.align_type_members_on_columns=false 132 | org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16 133 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 134 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 135 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 136 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 137 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 138 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 139 | org.eclipse.jdt.core.formatter.alignment_for_assignment=0 140 | org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16 141 | org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 142 | org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 143 | org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 144 | org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 145 | org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16 146 | org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 147 | org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 148 | org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16 149 | org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 150 | org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 151 | org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 152 | org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 153 | org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16 154 | org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 155 | org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 156 | org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 157 | org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 158 | org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 159 | org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 160 | org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 161 | org.eclipse.jdt.core.formatter.blank_lines_after_package=1 162 | org.eclipse.jdt.core.formatter.blank_lines_before_field=0 163 | org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 164 | org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 165 | org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 166 | org.eclipse.jdt.core.formatter.blank_lines_before_method=1 167 | org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 168 | org.eclipse.jdt.core.formatter.blank_lines_before_package=1 169 | org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 170 | org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 171 | org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line 172 | org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line 173 | org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line 174 | org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line 175 | org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line 176 | org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line 177 | org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line 178 | org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line 179 | org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line 180 | org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line 181 | org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line 182 | org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line 183 | org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false 184 | org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false 185 | org.eclipse.jdt.core.formatter.comment.format_block_comments=true 186 | org.eclipse.jdt.core.formatter.comment.format_header=false 187 | org.eclipse.jdt.core.formatter.comment.format_html=true 188 | org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true 189 | org.eclipse.jdt.core.formatter.comment.format_line_comments=true 190 | org.eclipse.jdt.core.formatter.comment.format_source_code=true 191 | org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true 192 | org.eclipse.jdt.core.formatter.comment.indent_root_tags=true 193 | org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=do not insert 194 | org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert 195 | org.eclipse.jdt.core.formatter.comment.line_length=130 196 | org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true 197 | org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=false 198 | org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false 199 | org.eclipse.jdt.core.formatter.compact_else_if=true 200 | org.eclipse.jdt.core.formatter.continuation_indentation=1 201 | org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=1 202 | org.eclipse.jdt.core.formatter.disabling_tag=@off 203 | org.eclipse.jdt.core.formatter.enabling_tag=@on 204 | org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false 205 | org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true 206 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true 207 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true 208 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true 209 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true 210 | org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true 211 | org.eclipse.jdt.core.formatter.indent_empty_lines=false 212 | org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true 213 | org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true 214 | org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true 215 | org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false 216 | org.eclipse.jdt.core.formatter.indentation.size=4 217 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=do not insert 218 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert 219 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert 220 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert 221 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert 222 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert 223 | org.eclipse.jdt.core.formatter.insert_new_line_after_label=insert 224 | org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert 225 | org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert 226 | org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert 227 | org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert 228 | org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert 229 | org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert 230 | org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert 231 | org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert 232 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert 233 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert 234 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert 235 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert 236 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert 237 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert 238 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert 239 | org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert 240 | org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert 241 | org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert 242 | org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert 243 | org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert 244 | org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert 245 | org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert 246 | org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert 247 | org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert 248 | org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=do not insert 249 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert 250 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert 251 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert 252 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert 253 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert 254 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert 255 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert 256 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert 257 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert 258 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert 259 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert 260 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert 261 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert 262 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert 263 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert 264 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert 265 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert 266 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert 267 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert 268 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert 269 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert 270 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert 271 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert 272 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert 273 | org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert 274 | org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert 275 | org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert 276 | org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert 277 | org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert 278 | org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert 279 | org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert 280 | org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert 281 | org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert 282 | org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert 283 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert 284 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert 285 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert 286 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert 287 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert 288 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert 289 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert 290 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert 291 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert 292 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert 293 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert 294 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert 295 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert 296 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert 297 | org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert 298 | org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert 299 | org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert 300 | org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert 301 | org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert 302 | org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert 303 | org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert 304 | org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert 305 | org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert 306 | org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert 307 | org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert 308 | org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert 309 | org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert 310 | org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert 311 | org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert 312 | org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert 313 | org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert 314 | org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert 315 | org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert 316 | org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert 317 | org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert 318 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert 319 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert 320 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert 321 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert 322 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert 323 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert 324 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert 325 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert 326 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert 327 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert 328 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert 329 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert 330 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert 331 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert 332 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert 333 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert 334 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert 335 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert 336 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert 337 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert 338 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert 339 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert 340 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert 341 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert 342 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert 343 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert 344 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert 345 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert 346 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert 347 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert 348 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert 349 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert 350 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert 351 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert 352 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert 353 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert 354 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert 355 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert 356 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert 357 | org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert 358 | org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert 359 | org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert 360 | org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert 361 | org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert 362 | org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert 363 | org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert 364 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert 365 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert 366 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert 367 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert 368 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert 369 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert 370 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert 371 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert 372 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert 373 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert 374 | org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert 375 | org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert 376 | org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert 377 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert 378 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert 379 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert 380 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=insert 381 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert 382 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert 383 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert 384 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=insert 385 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert 386 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert 387 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert 388 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert 389 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert 390 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert 391 | org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert 392 | org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert 393 | org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert 394 | org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert 395 | org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert 396 | org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert 397 | org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert 398 | org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert 399 | org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert 400 | org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert 401 | org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert 402 | org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert 403 | org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert 404 | org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert 405 | org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert 406 | org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert 407 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert 408 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert 409 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert 410 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert 411 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert 412 | org.eclipse.jdt.core.formatter.join_lines_in_comments=true 413 | org.eclipse.jdt.core.formatter.join_wrapped_lines=true 414 | org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false 415 | org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false 416 | org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true 417 | org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false 418 | org.eclipse.jdt.core.formatter.lineSplit=130 419 | org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=true 420 | org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=true 421 | org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 422 | org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 423 | org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true 424 | org.eclipse.jdt.core.formatter.tabulation.char=tab 425 | org.eclipse.jdt.core.formatter.tabulation.size=3 426 | org.eclipse.jdt.core.formatter.use_on_off_tags=true 427 | org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false 428 | org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true 429 | org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true 430 | org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true 431 | org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true 432 | org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true 433 | org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true 434 | org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true 435 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.ui.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | formatter_profile=_libgdx 3 | formatter_settings_version=12 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Nathan Sweet 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | * Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## JsonBeans 2 | 3 | Please use the [JsonBeans discussion group](http://groups.google.com/group/jsonbeans-users) for support. 4 | 5 | - [Overview](#Overview) 6 | - [Writing object graphs](#writing-object-graphs) 7 | - [Reading object graphs](#reading-object-graphs) 8 | - [Customizing serialization](#customizing-serialization) 9 | - [Event based parsing](#event-based-parsing) 10 | 11 | ## Overview 12 | 13 | JsonBeans is a lightweight library that makes it easy to serialize and deserialize Java object graphs to and from JSON. The JAR is 45k and has no dependencies. Four small classes make up the important parts of the library: 14 | 15 | - `JsonWriter`: A builder style API for emitting JSON. 16 | - `JsonReader`: Parses JSON and builds a DOM of `JsonValue` objects. 17 | - `JsonValue`: Describes a JSON object, array, string, float, long, boolean, or null. 18 | - `Json`: Reads and writes arbitrary object graphs using `JsonReader` and `JsonWriter`. 19 | 20 | ## Writing object graphs 21 | 22 | The `Json` class uses reflection to automatically serialize objects to JSON. For example, here are two classes (getters/setters and constructors omitted): 23 | 24 | ```java 25 | public class Person { 26 | private String name; 27 | private int age; 28 | private ArrayList numbers; 29 | } 30 | 31 | public class PhoneNumber { 32 | private String name; 33 | private String number; 34 | } 35 | ``` 36 | 37 | Example object graph using these classes: 38 | 39 | ```java 40 | Person person = new Person(); 41 | person.setName("Nate"); 42 | person.setAge(31); 43 | ArrayList numbers = new ArrayList(); 44 | numbers.add(new PhoneNumber("Home", "206-555-1234")); 45 | numbers.add(new PhoneNumber("Work", "425-555-4321")); 46 | person.setNumbers(numbers); 47 | ``` 48 | 49 | The JsonBeans code to serialize this object graph: 50 | 51 | ```java 52 | Json json = new Json(); 53 | System.out.println(json.toJson(person)); 54 | ``` 55 | ```json 56 | {"numbers":[{"class":"com.example.PhoneNumber","name":"Home","number":"206-555-1234"},{"class":"com.example.PhoneNumber","name":"Work","number":"425-555-4321"}],"age":31,"name":"Nate"} 57 | ``` 58 | 59 | That is compact, but hardly legible. The `prettyPrint` method can be used: 60 | 61 | ```java 62 | Json json = new Json(); 63 | System.out.println(json.prettyPrint(person)); 64 | ``` 65 | ```json 66 | { 67 | "name": "Nate", 68 | "age": 31, 69 | "numbers": [ 70 | { 71 | "name": "Home", 72 | "class": "com.example.PhoneNumber", 73 | "number": "206-555-1234" 74 | }, 75 | { 76 | "name": "Work", 77 | "class": "com.example.PhoneNumber", 78 | "number": "425-555-4321" 79 | } 80 | ] 81 | } 82 | ``` 83 | 84 | Note that the class for the `PhoneNumber` objects in the `ArrayList numbers` field appears in the JSON. This is required to recreate the object graph from the JSON because `ArrayList` can hold any type of object. Class names are only output when they are required for deserialization. If the field was `ArrayList numbers` then class names would only appear when an item in the list extends `PhoneNumber`. If you know the concrete type or aren't using generics, you can avoid class names being written by telling the `Json` class the types: 85 | 86 | ```java 87 | Json json = new Json(); 88 | json.setElementType(Person.class, "numbers", PhoneNumber.class); 89 | System.out.println(json.prettyPrint(person)); 90 | ``` 91 | ```json 92 | { 93 | "name": "Nate", 94 | "age": 31, 95 | "numbers": [ 96 | { 97 | "name": "Home", 98 | "number": "206-555-1234" 99 | }, 100 | { 101 | "name": "Work", 102 | "number": "425-555-4321" 103 | } 104 | ] 105 | } 106 | ``` 107 | 108 | When writing the class cannot be avoided, an alias can be given: 109 | 110 | ```java 111 | Json json = new Json(); 112 | json.addClassTag("phoneNumber", PhoneNumber.class); 113 | System.out.println(json.prettyPrint(person)); 114 | ``` 115 | ```json 116 | { 117 | "name": "Nate", 118 | "age": 31, 119 | "numbers": [ 120 | { 121 | "name": "Home", 122 | "class": "phoneNumber", 123 | "number": "206-555-1234" 124 | }, 125 | { 126 | "name": "Work", 127 | "class": "phoneNumber", 128 | "number": "425-555-4321" 129 | } 130 | ] 131 | } 132 | ``` 133 | 134 | JsonBeans can write and read both JSON and a couple JSON-like formats. It supports "javascript", where the object property names are only quoted when needed. It also supports a "minimal" format, where both object property names and values are only quoted when needed. 135 | 136 | ```java 137 | Json json = new Json(); 138 | json.setOutputType(OutputType.minimal); 139 | json.setElementType(Person.class, "numbers", PhoneNumber.class); 140 | System.out.println(json.prettyPrint(person)); 141 | ``` 142 | ```json 143 | { 144 | name: Nate, 145 | age: 31, 146 | numbers: [ 147 | { 148 | name: Home, 149 | number: "206-555-1234" 150 | }, 151 | { 152 | name: Work, 153 | number: "425-555-4321" 154 | } 155 | ] 156 | } 157 | ``` 158 | 159 | ## Reading object graphs 160 | 161 | The Json class uses reflection to automatically deserialize objects from JSON. Here is how to deserialize the JSON from the previous examples: 162 | 163 | ```java 164 | Json json = new Json(); 165 | String text = json.toJson(person); 166 | Person person2 = json.fromJson(Person.class, text); 167 | ``` 168 | 169 | The type passed to `fromJson` is the type of the root of the object graph. From this, JsonBeans can determine the types of all the fields and all other objects encountered, recursively. The "knownType" and "elementType" of the root can be passed to `toJson`. This is useful if the type of the root object is not known: 170 | 171 | ```java 172 | Json json = new Json(); 173 | json.setOutputType(OutputType.minimal); 174 | String text = json.toJson(person, Object.class); 175 | System.out.println(json.prettyPrint(text)); 176 | Object person2 = json.fromJson(Object.class, text); 177 | ``` 178 | ```json 179 | { 180 | class: com.example.Person, 181 | name: Nate, 182 | age: 31, 183 | numbers: [ 184 | { 185 | name: Home, 186 | class: com.example.PhoneNumber, 187 | number: "206-555-1234" 188 | }, 189 | { 190 | name: Work, 191 | class: com.example.PhoneNumber, 192 | number: "425-555-4321" 193 | } 194 | ] 195 | } 196 | ``` 197 | 198 | To read the JSON as a DOM of maps, arrays, and values, the `JsonReader` class can be used: 199 | 200 | ```java 201 | Json json = new Json(); 202 | String text = json.toJson(person, Object.class); 203 | JsonValue root = new JsonReader().parse(text); 204 | ``` 205 | 206 | The `JsonValue` describes a JSON object, array, string, float, long, boolean, or null. 207 | 208 | ## Customizing serialization 209 | 210 | Serialization can be customized by either having the class to be serialized implement the `Json.Serializable` interface, or by registering a `Json.Serializer` with the `Json` instance. This example writes the phone numbers as an object with a single field: 211 | 212 | ```java 213 | static public class PhoneNumber implements Json.Serializable { 214 | private String name; 215 | private String number; 216 | 217 | public void write (Json json) { 218 | json.writeValue(name, number); 219 | } 220 | 221 | public void read (Json json, JsonValue jsonMap) { 222 | name = jsonMap.child().name(); 223 | number = jsonMap.child().asString(); 224 | } 225 | } 226 | 227 | Json json = new Json(); 228 | json.setElementType(Person.class, "numbers", PhoneNumber.class); 229 | String text = json.prettyPrint(person); 230 | System.out.println(text); 231 | Person person2 = json.fromJson(Person.class, text); 232 | ``` 233 | ```json 234 | { 235 | "name": "Nate", 236 | "age": 31 237 | "numbers": [ 238 | { 239 | "Home": "206-555-1234" 240 | }, 241 | { 242 | "Work": "425-555-4321" 243 | } 244 | ] 245 | } 246 | ``` 247 | 248 | In the `Json.Serializable` interface methods, the `Json` instance is given. It has many methods to read and write data to the JSON. When using `Json.Serializable`, the surrounding JSON object is handled automatically in the `write` method. This is why the `read` method always receives a `JsonMap`. 249 | 250 | `Json.Serializer` provides more control over what is output, requiring `writeObjectStart` and `writeObjectEnd` to be called to achieve the same effect. A JSON array or a simple value could be output instead of an object. `Json.Serializer` also allows the object creation to be customized. 251 | 252 | ```java 253 | Json json = new Json(); 254 | json.setSerializer(PhoneNumber.class, new Json.Serializer() { 255 | public void write (Json json, PhoneNumber number, Class knownType) { 256 | json.writeObjectStart(); 257 | json.writeValue(number.name, number.number); 258 | json.writeObjectEnd(); 259 | } 260 | 261 | public PhoneNumber read (Json json, JsonValue jsonData, Class type) { 262 | PhoneNumber number = new PhoneNumber(); 263 | number.setName(jsonData.child().name()); 264 | number.setNumber(jsonData.child().asString()); 265 | return number; 266 | } 267 | }); 268 | json.setElementType(Person.class, "numbers", PhoneNumber.class); 269 | String text = json.prettyPrint(person); 270 | System.out.println(text); 271 | Person person2 = json.fromJson(Person.class, text); 272 | ``` 273 | 274 | ## Event based parsing 275 | 276 | The `JsonReader` class reads JSON and has protected methods that are called as JSON objects, arrays, strings, numbers, and booleans are encountered. By default, these methods build a DOM out of `JsonValue` objects. These methods can be overridden to do your own event based JSON handling. 277 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 5 | 4.0.0 6 | com.esotericsoftware 7 | jsonbeans 8 | 1.0-SNAPSHOT 9 | jar 10 | JsonBeans 11 | Java object graphs, to and from JSON automatically 12 | https://github.com/EsotericSoftware/jsonbeans 13 | 14 | 15 | 16 | New BSD License 17 | http://www.opensource.org/licenses/bsd-license.php 18 | repo 19 | 20 | 21 | 22 | 23 | https://github.com/EsotericSoftware/jsonbeans 24 | scm:git:git@github.com:EsotericSoftware/jsonbeans.git 25 | scm:git:git@github.com:EsotericSoftware/jsonbeans.git 26 | 27 | 28 | 29 | 30 | nathan.sweet 31 | Nathan Sweet 32 | nathan.sweet@gmail.com 33 | 34 | 35 | 36 | 37 | UTF-8 38 | 39 | 40 | 41 | 42 | src 43 | 44 | 45 | 46 | 47 | maven-resources-plugin 48 | 2.5 49 | 50 | 51 | default-resources 52 | none 53 | 54 | 55 | default-testResources 56 | none 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/com/esotericsoftware/jsonbeans/Json.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011 See AUTHORS file. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | 17 | package com.esotericsoftware.jsonbeans; 18 | 19 | import java.io.File; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.io.OutputStreamWriter; 24 | import java.io.Reader; 25 | import java.io.StringWriter; 26 | import java.io.Writer; 27 | import java.lang.reflect.Array; 28 | import java.lang.reflect.Constructor; 29 | import java.lang.reflect.Field; 30 | import java.lang.reflect.GenericArrayType; 31 | import java.lang.reflect.Modifier; 32 | import java.lang.reflect.ParameterizedType; 33 | import java.lang.reflect.Type; 34 | import java.util.ArrayList; 35 | import java.util.Arrays; 36 | import java.util.Collection; 37 | import java.util.Collections; 38 | import java.util.HashMap; 39 | import java.util.Map; 40 | 41 | import com.esotericsoftware.jsonbeans.JsonValue.PrettyPrintSettings; 42 | import com.esotericsoftware.jsonbeans.ObjectMap.Entry; 43 | 44 | /** Reads/writes Java objects to/from JSON, automatically. See the wiki for usage: 45 | * https://github.com/libgdx/libgdx/wiki/Reading-and-writing-JSON 46 | * @author Nathan Sweet */ 47 | public class Json { 48 | static private final boolean debug = false; 49 | 50 | private JsonWriter writer; 51 | private String typeName = "class"; 52 | private boolean usePrototypes = true; 53 | private OutputType outputType; 54 | private boolean quoteLongValues; 55 | private boolean ignoreUnknownFields; 56 | private boolean ignoreDeprecated; 57 | private boolean readDeprecated; 58 | private boolean enumNames = true; 59 | private boolean sortFields; 60 | private JsonSerializer defaultSerializer; 61 | private final ObjectMap> typeToFields = new ObjectMap(); 62 | private final ObjectMap tagToClass = new ObjectMap(); 63 | private final ObjectMap classToTag = new ObjectMap(); 64 | private final ObjectMap classToSerializer = new ObjectMap(); 65 | private final ObjectMap classToDefaultValues = new ObjectMap(); 66 | private final Object[] equals1 = {null}, equals2 = {null}; 67 | 68 | public Json () { 69 | outputType = OutputType.minimal; 70 | } 71 | 72 | public Json (OutputType outputType) { 73 | this.outputType = outputType; 74 | } 75 | 76 | /** When true, fields in the JSON that are not found on the class will not throw a {@link JsonException}. Default is false. */ 77 | public void setIgnoreUnknownFields (boolean ignoreUnknownFields) { 78 | this.ignoreUnknownFields = ignoreUnknownFields; 79 | } 80 | 81 | public boolean getIgnoreUnknownFields () { 82 | return ignoreUnknownFields; 83 | } 84 | 85 | /** When true, fields with the {@link Deprecated} annotation will not be read or written. Default is false. 86 | * @see #setReadDeprecated(boolean) 87 | * @see #setDeprecated(Class, String, boolean) */ 88 | public void setIgnoreDeprecated (boolean ignoreDeprecated) { 89 | this.ignoreDeprecated = ignoreDeprecated; 90 | } 91 | 92 | /** When true, fields with the {@link Deprecated} annotation will be read (but not written) when 93 | * {@link #setIgnoreDeprecated(boolean)} is true. Default is false. 94 | * @see #setDeprecated(Class, String, boolean) */ 95 | public void setReadDeprecated (boolean readDeprecated) { 96 | this.readDeprecated = readDeprecated; 97 | } 98 | 99 | /** Default is {@link OutputType#minimal}. 100 | * @see JsonWriter#setOutputType(OutputType) */ 101 | public void setOutputType (OutputType outputType) { 102 | this.outputType = outputType; 103 | } 104 | 105 | /** Default is false. 106 | * @see JsonWriter#setQuoteLongValues(boolean) */ 107 | public void setQuoteLongValues (boolean quoteLongValues) { 108 | this.quoteLongValues = quoteLongValues; 109 | } 110 | 111 | /** When true, {@link Enum#name()} is used to write enum values. When false, {@link Enum#toString()} is used which may not be 112 | * unique. Default is true. */ 113 | public void setEnumNames (boolean enumNames) { 114 | this.enumNames = enumNames; 115 | } 116 | 117 | /** Sets a tag to use instead of the fully qualifier class name. This can make the JSON easier to read. */ 118 | public void addClassTag (String tag, Class type) { 119 | tagToClass.put(tag, type); 120 | classToTag.put(type, tag); 121 | } 122 | 123 | /** Returns the class for the specified tag, or null. */ 124 | public @Null Class getClass (String tag) { 125 | return tagToClass.get(tag); 126 | } 127 | 128 | /** Returns the tag for the specified class, or null. */ 129 | public @Null String getTag (Class type) { 130 | return classToTag.get(type); 131 | } 132 | 133 | /** Sets the name of the JSON field to store the Java class name or class tag when required to avoid ambiguity during 134 | * deserialization. Set to null to never output this information, but be warned that deserialization may fail. Default is 135 | * "class". */ 136 | public void setTypeName (@Null String typeName) { 137 | this.typeName = typeName; 138 | } 139 | 140 | /** Sets the serializer to use when the type being deserialized is not known (null). */ 141 | public void setDefaultSerializer (@Null JsonSerializer defaultSerializer) { 142 | this.defaultSerializer = defaultSerializer; 143 | } 144 | 145 | /** Registers a serializer to use for the specified type instead of the default behavior of serializing all of an objects 146 | * fields. */ 147 | public void setSerializer (Class type, JsonSerializer serializer) { 148 | classToSerializer.put(type, serializer); 149 | } 150 | 151 | public JsonSerializer getSerializer (Class type) { 152 | return classToSerializer.get(type); 153 | } 154 | 155 | /** When true, field values that are identical to a newly constructed instance are not written. Default is true. */ 156 | public void setUsePrototypes (boolean usePrototypes) { 157 | this.usePrototypes = usePrototypes; 158 | } 159 | 160 | /** Sets the type of elements in a collection. When the element type is known, the class for each element in the collection 161 | * does not need to be written unless different from the element type. */ 162 | public void setElementType (Class type, String fieldName, Class elementType) { 163 | FieldMetadata metadata = getFields(type).get(fieldName); 164 | if (metadata == null) throw new JsonException("Field not found: " + fieldName + " (" + type.getName() + ")"); 165 | metadata.elementType = elementType; 166 | } 167 | 168 | /** The specified field will be treated as if it has or does not have the {@link Deprecated} annotation. 169 | * @see #setIgnoreDeprecated(boolean) 170 | * @see #setReadDeprecated(boolean) */ 171 | public void setDeprecated (Class type, String fieldName, boolean deprecated) { 172 | FieldMetadata metadata = getFields(type).get(fieldName); 173 | if (metadata == null) throw new JsonException("Field not found: " + fieldName + " (" + type.getName() + ")"); 174 | metadata.deprecated = deprecated; 175 | } 176 | 177 | /** When true, fields are sorted alphabetically when written, otherwise the source code order is used. Default is false. */ 178 | public void setSortFields (boolean sortFields) { 179 | this.sortFields = sortFields; 180 | } 181 | 182 | private OrderedMap getFields (Class type) { 183 | OrderedMap fields = typeToFields.get(type); 184 | if (fields != null) return fields; 185 | 186 | ArrayList classHierarchy = new ArrayList(); 187 | Class nextClass = type; 188 | while (nextClass != Object.class) { 189 | classHierarchy.add(nextClass); 190 | nextClass = nextClass.getSuperclass(); 191 | } 192 | ArrayList allFields = new ArrayList(); 193 | for (int i = classHierarchy.size() - 1; i >= 0; i--) 194 | Collections.addAll(allFields, classHierarchy.get(i).getDeclaredFields()); 195 | 196 | OrderedMap nameToField = new OrderedMap(allFields.size()); 197 | for (int i = 0, n = allFields.size(); i < n; i++) { 198 | Field field = allFields.get(i); 199 | 200 | int modifiers = field.getModifiers(); 201 | if (Modifier.isTransient(modifiers)) continue; 202 | if (Modifier.isStatic(modifiers)) continue; 203 | if (field.isSynthetic()) continue; 204 | 205 | if (!field.isAccessible()) { 206 | try { 207 | field.setAccessible(true); 208 | } catch (RuntimeException ex) { 209 | continue; 210 | } 211 | } 212 | 213 | nameToField.put(field.getName(), new FieldMetadata(field)); 214 | } 215 | if (sortFields) Collections.sort(nameToField.keys); 216 | typeToFields.put(type, nameToField); 217 | return nameToField; 218 | } 219 | 220 | public String toJson (@Null Object object) { 221 | return toJson(object, object == null ? null : object.getClass(), (Class)null); 222 | } 223 | 224 | public String toJson (@Null Object object, @Null Class knownType) { 225 | return toJson(object, knownType, (Class)null); 226 | } 227 | 228 | /** @param knownType May be null if the type is unknown. 229 | * @param elementType May be null if the type is unknown. */ 230 | public String toJson (@Null Object object, @Null Class knownType, @Null Class elementType) { 231 | StringWriter buffer = new StringWriter(); 232 | toJson(object, knownType, elementType, buffer); 233 | return buffer.toString(); 234 | } 235 | 236 | public void toJson (@Null Object object, File file) { 237 | toJson(object, object == null ? null : object.getClass(), null, file); 238 | } 239 | 240 | /** @param knownType May be null if the type is unknown. */ 241 | public void toJson (@Null Object object, @Null Class knownType, File file) { 242 | toJson(object, knownType, null, file); 243 | } 244 | 245 | /** @param knownType May be null if the type is unknown. 246 | * @param elementType May be null if the type is unknown. */ 247 | public void toJson (@Null Object object, @Null Class knownType, @Null Class elementType, File file) { 248 | Writer writer = null; 249 | try { 250 | writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8"); 251 | toJson(object, knownType, elementType, writer); 252 | } catch (Exception ex) { 253 | throw new JsonException("Error writing file: " + file, ex); 254 | } finally { 255 | try { 256 | if (writer != null) writer.close(); 257 | } catch (IOException ignored) { 258 | } 259 | } 260 | } 261 | 262 | public void toJson (@Null Object object, Writer writer) { 263 | toJson(object, object == null ? null : object.getClass(), null, writer); 264 | } 265 | 266 | /** @param knownType May be null if the type is unknown. */ 267 | public void toJson (@Null Object object, @Null Class knownType, Writer writer) { 268 | toJson(object, knownType, null, writer); 269 | } 270 | 271 | /** @param knownType May be null if the type is unknown. 272 | * @param elementType May be null if the type is unknown. */ 273 | public void toJson (@Null Object object, @Null Class knownType, @Null Class elementType, Writer writer) { 274 | setWriter(writer); 275 | try { 276 | writeValue(object, knownType, elementType); 277 | } finally { 278 | if (this.writer != null) { 279 | try { 280 | this.writer.close(); 281 | } catch (IOException ignored) { 282 | } 283 | } 284 | this.writer = null; 285 | } 286 | } 287 | 288 | /** Sets the writer where JSON output will be written. This is only necessary when not using the toJson methods. */ 289 | public void setWriter (Writer writer) { 290 | if (!(writer instanceof JsonWriter)) writer = new JsonWriter(writer); 291 | this.writer = (JsonWriter)writer; 292 | this.writer.setOutputType(outputType); 293 | this.writer.setQuoteLongValues(quoteLongValues); 294 | } 295 | 296 | public JsonWriter getWriter () { 297 | return writer; 298 | } 299 | 300 | /** Writes all fields of the specified object to the current JSON object. */ 301 | public void writeFields (Object object) { 302 | Class type = object.getClass(); 303 | 304 | Object[] defaultValues = getDefaultValues(type); 305 | 306 | OrderedMap fields = getFields(type); 307 | int defaultIndex = 0; 308 | ArrayList fieldNames = fields.orderedKeys(); 309 | for (int i = 0, n = fieldNames.size(); i < n; i++) { 310 | FieldMetadata metadata = fields.get(fieldNames.get(i)); 311 | if (ignoreDeprecated && metadata.deprecated) continue; 312 | Field field = metadata.field; 313 | try { 314 | Object value = field.get(object); 315 | if (defaultValues != null) { 316 | Object defaultValue = defaultValues[defaultIndex++]; 317 | if (value == null && defaultValue == null) continue; 318 | if (value != null && defaultValue != null) { 319 | if (value.equals(defaultValue)) continue; 320 | if (value.getClass().isArray() && defaultValue.getClass().isArray()) { 321 | equals1[0] = value; 322 | equals2[0] = defaultValue; 323 | if (Arrays.deepEquals(equals1, equals2)) continue; 324 | } 325 | } 326 | } 327 | 328 | if (debug) System.out.println("Writing field: " + field.getName() + " (" + type.getName() + ")"); 329 | writer.name(field.getName()); 330 | writeValue(value, field.getType(), metadata.elementType); 331 | } catch (IllegalAccessException ex) { 332 | throw new JsonException("Error accessing field: " + field.getName() + " (" + type.getName() + ")", ex); 333 | } catch (JsonException ex) { 334 | ex.addTrace(field + " (" + type.getName() + ")"); 335 | throw ex; 336 | } catch (Exception runtimeEx) { 337 | JsonException ex = new JsonException(runtimeEx); 338 | ex.addTrace(field + " (" + type.getName() + ")"); 339 | throw ex; 340 | } 341 | } 342 | } 343 | 344 | private @Null Object[] getDefaultValues (Class type) { 345 | if (!usePrototypes) return null; 346 | if (classToDefaultValues.containsKey(type)) return classToDefaultValues.get(type); 347 | Object object; 348 | try { 349 | object = newInstance(type); 350 | } catch (Exception ex) { 351 | classToDefaultValues.put(type, null); 352 | return null; 353 | } 354 | 355 | OrderedMap fields = getFields(type); 356 | Object[] values = new Object[fields.size]; 357 | classToDefaultValues.put(type, values); 358 | 359 | int defaultIndex = 0; 360 | ArrayList fieldNames = fields.orderedKeys(); 361 | for (int i = 0, n = fieldNames.size(); i < n; i++) { 362 | FieldMetadata metadata = fields.get(fieldNames.get(i)); 363 | if (ignoreDeprecated && metadata.deprecated) continue; 364 | Field field = metadata.field; 365 | try { 366 | values[defaultIndex++] = field.get(object); 367 | } catch (IllegalAccessException ex) { 368 | throw new JsonException("Error accessing field: " + field.getName() + " (" + type.getName() + ")", ex); 369 | } catch (JsonException ex) { 370 | ex.addTrace(field + " (" + type.getName() + ")"); 371 | throw ex; 372 | } catch (RuntimeException runtimeEx) { 373 | JsonException ex = new JsonException(runtimeEx); 374 | ex.addTrace(field + " (" + type.getName() + ")"); 375 | throw ex; 376 | } 377 | } 378 | return values; 379 | } 380 | 381 | /** @see #writeField(Object, String, String, Class) */ 382 | public void writeField (Object object, String name) { 383 | writeField(object, name, name, null); 384 | } 385 | 386 | /** @param elementType May be null if the type is unknown. 387 | * @see #writeField(Object, String, String, Class) */ 388 | public void writeField (Object object, String name, @Null Class elementType) { 389 | writeField(object, name, name, elementType); 390 | } 391 | 392 | /** @see #writeField(Object, String, String, Class) */ 393 | public void writeField (Object object, String fieldName, String jsonName) { 394 | writeField(object, fieldName, jsonName, null); 395 | } 396 | 397 | /** Writes the specified field to the current JSON object. 398 | * @param elementType May be null if the type is unknown. */ 399 | public void writeField (Object object, String fieldName, String jsonName, @Null Class elementType) { 400 | Class type = object.getClass(); 401 | FieldMetadata metadata = getFields(type).get(fieldName); 402 | if (metadata == null) throw new JsonException("Field not found: " + fieldName + " (" + type.getName() + ")"); 403 | Field field = metadata.field; 404 | if (elementType == null) elementType = metadata.elementType; 405 | try { 406 | if (debug) System.out.println("Writing field: " + field.getName() + " (" + type.getName() + ")"); 407 | writer.name(jsonName); 408 | writeValue(field.get(object), field.getType(), elementType); 409 | } catch (IllegalAccessException ex) { 410 | throw new JsonException("Error accessing field: " + field.getName() + " (" + type.getName() + ")", ex); 411 | } catch (JsonException ex) { 412 | ex.addTrace(field + " (" + type.getName() + ")"); 413 | throw ex; 414 | } catch (Exception runtimeEx) { 415 | JsonException ex = new JsonException(runtimeEx); 416 | ex.addTrace(field + " (" + type.getName() + ")"); 417 | throw ex; 418 | } 419 | } 420 | 421 | /** Writes the value as a field on the current JSON object, without writing the actual class. 422 | * @param value May be null. 423 | * @see #writeValue(String, Object, Class, Class) */ 424 | public void writeValue (String name, @Null Object value) { 425 | try { 426 | writer.name(name); 427 | } catch (IOException ex) { 428 | throw new JsonException(ex); 429 | } 430 | if (value == null) 431 | writeValue(value, null, null); 432 | else 433 | writeValue(value, value.getClass(), null); 434 | } 435 | 436 | /** Writes the value as a field on the current JSON object, writing the class of the object if it differs from the specified 437 | * known type. 438 | * @param value May be null. 439 | * @param knownType May be null if the type is unknown. 440 | * @see #writeValue(String, Object, Class, Class) */ 441 | public void writeValue (String name, @Null Object value, @Null Class knownType) { 442 | try { 443 | writer.name(name); 444 | } catch (IOException ex) { 445 | throw new JsonException(ex); 446 | } 447 | writeValue(value, knownType, null); 448 | } 449 | 450 | /** Writes the value as a field on the current JSON object, writing the class of the object if it differs from the specified 451 | * known type. The specified element type is used as the default type for collections. 452 | * @param value May be null. 453 | * @param knownType May be null if the type is unknown. 454 | * @param elementType May be null if the type is unknown. */ 455 | public void writeValue (String name, @Null Object value, @Null Class knownType, @Null Class elementType) { 456 | try { 457 | writer.name(name); 458 | } catch (IOException ex) { 459 | throw new JsonException(ex); 460 | } 461 | writeValue(value, knownType, elementType); 462 | } 463 | 464 | /** Writes the value, without writing the class of the object. 465 | * @param value May be null. */ 466 | public void writeValue (@Null Object value) { 467 | if (value == null) 468 | writeValue(value, null, null); 469 | else 470 | writeValue(value, value.getClass(), null); 471 | } 472 | 473 | /** Writes the value, writing the class of the object if it differs from the specified known type. 474 | * @param value May be null. 475 | * @param knownType May be null if the type is unknown. */ 476 | public void writeValue (@Null Object value, @Null Class knownType) { 477 | writeValue(value, knownType, null); 478 | } 479 | 480 | /** Writes the value, writing the class of the object if it differs from the specified known type. The specified element type 481 | * is used as the default type for collections. 482 | * @param value May be null. 483 | * @param knownType May be null if the type is unknown. 484 | * @param elementType May be null if the type is unknown. */ 485 | public void writeValue (@Null Object value, @Null Class knownType, @Null Class elementType) { 486 | try { 487 | if (value == null) { 488 | writer.value(null); 489 | return; 490 | } 491 | 492 | if ((knownType != null && knownType.isPrimitive()) || knownType == String.class || knownType == Integer.class 493 | || knownType == Boolean.class || knownType == Float.class || knownType == Long.class || knownType == Double.class 494 | || knownType == Short.class || knownType == Byte.class || knownType == Character.class) { 495 | writer.value(value); 496 | return; 497 | } 498 | 499 | Class actualType = value.getClass(); 500 | 501 | if (actualType.isPrimitive() || actualType == String.class || actualType == Integer.class || actualType == Boolean.class 502 | || actualType == Float.class || actualType == Long.class || actualType == Double.class || actualType == Short.class 503 | || actualType == Byte.class || actualType == Character.class) { 504 | writeObjectStart(actualType, null); 505 | writeValue("value", value); 506 | writeObjectEnd(); 507 | return; 508 | } 509 | 510 | if (value instanceof JsonSerializable) { 511 | writeObjectStart(actualType, knownType); 512 | ((JsonSerializable)value).write(this); 513 | writeObjectEnd(); 514 | return; 515 | } 516 | 517 | JsonSerializer serializer = classToSerializer.get(actualType); 518 | if (serializer != null) { 519 | serializer.write(this, value, knownType); 520 | return; 521 | } 522 | 523 | // JSON array special cases. 524 | if (value instanceof ArrayList) { 525 | if (knownType != null && actualType != knownType && actualType != ArrayList.class) 526 | throw new JsonException("Serialization of an Array other than the known type is not supported.\n" // 527 | + "Known type: " + knownType + "\nActual type: " + actualType); 528 | writeArrayStart(); 529 | ArrayList array = (ArrayList)value; 530 | for (int i = 0, n = array.size(); i < n; i++) 531 | writeValue(array.get(i), elementType, null); 532 | writeArrayEnd(); 533 | return; 534 | } 535 | if (value instanceof Collection) { 536 | if (typeName != null && actualType != ArrayList.class && (knownType == null || knownType != actualType)) { 537 | writeObjectStart(actualType, knownType); 538 | writeArrayStart("items"); 539 | for (Object item : (Collection)value) 540 | writeValue(item, elementType, null); 541 | writeArrayEnd(); 542 | writeObjectEnd(); 543 | } else { 544 | writeArrayStart(); 545 | for (Object item : (Collection)value) 546 | writeValue(item, elementType, null); 547 | writeArrayEnd(); 548 | } 549 | return; 550 | } 551 | if (actualType.isArray()) { 552 | if (elementType == null) elementType = actualType.getComponentType(); 553 | int length = Array.getLength(value); 554 | writeArrayStart(); 555 | for (int i = 0; i < length; i++) 556 | writeValue(Array.get(value, i), elementType, null); 557 | writeArrayEnd(); 558 | return; 559 | } 560 | 561 | // JSON object special cases. 562 | if (value instanceof ObjectMap) { 563 | if (knownType == null) knownType = ObjectMap.class; 564 | writeObjectStart(actualType, knownType); 565 | for (Entry entry : ((ObjectMap)value).entries()) { 566 | writer.name(convertToString(entry.key)); 567 | writeValue(entry.value, elementType, null); 568 | } 569 | writeObjectEnd(); 570 | return; 571 | } 572 | if (value instanceof Map) { 573 | if (knownType == null) knownType = HashMap.class; 574 | writeObjectStart(actualType, knownType); 575 | for (Map.Entry entry : ((Map)value).entrySet()) { 576 | writer.name(convertToString(entry.getKey())); 577 | writeValue(entry.getValue(), elementType, null); 578 | } 579 | writeObjectEnd(); 580 | return; 581 | } 582 | 583 | // Enum special case. 584 | if (Enum.class.isAssignableFrom(actualType)) { 585 | if (typeName != null && (knownType == null || knownType != actualType)) { 586 | // Ensures that enums with specific implementations (abstract logic) serialize correctly. 587 | if (actualType.getEnumConstants() == null) actualType = actualType.getSuperclass(); 588 | 589 | writeObjectStart(actualType, null); 590 | writer.name("value"); 591 | writer.value(convertToString((Enum)value)); 592 | writeObjectEnd(); 593 | } else { 594 | writer.value(convertToString((Enum)value)); 595 | } 596 | return; 597 | } 598 | 599 | writeObjectStart(actualType, knownType); 600 | writeFields(value); 601 | writeObjectEnd(); 602 | } catch (IOException ex) { 603 | throw new JsonException(ex); 604 | } 605 | } 606 | 607 | public void writeObjectStart (String name) { 608 | try { 609 | writer.name(name); 610 | } catch (IOException ex) { 611 | throw new JsonException(ex); 612 | } 613 | writeObjectStart(); 614 | } 615 | 616 | /** @param knownType May be null if the type is unknown. */ 617 | public void writeObjectStart (String name, Class actualType, @Null Class knownType) { 618 | try { 619 | writer.name(name); 620 | } catch (IOException ex) { 621 | throw new JsonException(ex); 622 | } 623 | writeObjectStart(actualType, knownType); 624 | } 625 | 626 | public void writeObjectStart () { 627 | try { 628 | writer.object(); 629 | } catch (IOException ex) { 630 | throw new JsonException(ex); 631 | } 632 | } 633 | 634 | /** Starts writing an object, writing the actualType to a field if needed. 635 | * @param knownType May be null if the type is unknown. */ 636 | public void writeObjectStart (Class actualType, @Null Class knownType) { 637 | try { 638 | writer.object(); 639 | } catch (IOException ex) { 640 | throw new JsonException(ex); 641 | } 642 | if (knownType == null || knownType != actualType) writeType(actualType); 643 | } 644 | 645 | public void writeObjectEnd () { 646 | try { 647 | writer.pop(); 648 | } catch (IOException ex) { 649 | throw new JsonException(ex); 650 | } 651 | } 652 | 653 | public void writeArrayStart (String name) { 654 | try { 655 | writer.name(name); 656 | writer.array(); 657 | } catch (IOException ex) { 658 | throw new JsonException(ex); 659 | } 660 | } 661 | 662 | public void writeArrayStart () { 663 | try { 664 | writer.array(); 665 | } catch (IOException ex) { 666 | throw new JsonException(ex); 667 | } 668 | } 669 | 670 | public void writeArrayEnd () { 671 | try { 672 | writer.pop(); 673 | } catch (IOException ex) { 674 | throw new JsonException(ex); 675 | } 676 | } 677 | 678 | public void writeType (Class type) { 679 | if (typeName == null) return; 680 | String className = getTag(type); 681 | if (className == null) className = type.getName(); 682 | try { 683 | writer.set(typeName, className); 684 | } catch (IOException ex) { 685 | throw new JsonException(ex); 686 | } 687 | if (debug) System.out.println("Writing type: " + type.getName()); 688 | } 689 | 690 | /** @param type May be null if the type is unknown. 691 | * @return May be null. */ 692 | public @Null T fromJson (Class type, Reader reader) { 693 | return readValue(type, null, new JsonReader().parse(reader)); 694 | } 695 | 696 | /** @param type May be null if the type is unknown. 697 | * @param elementType May be null if the type is unknown. 698 | * @return May be null. */ 699 | public @Null T fromJson (Class type, Class elementType, Reader reader) { 700 | return readValue(type, elementType, new JsonReader().parse(reader)); 701 | } 702 | 703 | /** @param type May be null if the type is unknown. 704 | * @return May be null. */ 705 | public @Null T fromJson (Class type, InputStream input) { 706 | return readValue(type, null, new JsonReader().parse(input)); 707 | } 708 | 709 | /** @param type May be null if the type is unknown. 710 | * @param elementType May be null if the type is unknown. 711 | * @return May be null. */ 712 | public @Null T fromJson (Class type, Class elementType, InputStream input) { 713 | return readValue(type, elementType, new JsonReader().parse(input)); 714 | } 715 | 716 | /** @param type May be null if the type is unknown. 717 | * @return May be null. */ 718 | public @Null T fromJson (Class type, File file) { 719 | try { 720 | return readValue(type, null, new JsonReader().parse(file)); 721 | } catch (Exception ex) { 722 | throw new JsonException("Error reading file: " + file, ex); 723 | } 724 | } 725 | 726 | /** @param type May be null if the type is unknown. 727 | * @param elementType May be null if the type is unknown. 728 | * @return May be null. */ 729 | public @Null T fromJson (Class type, Class elementType, File file) { 730 | try { 731 | return readValue(type, elementType, new JsonReader().parse(file)); 732 | } catch (Exception ex) { 733 | throw new JsonException("Error reading file: " + file, ex); 734 | } 735 | } 736 | 737 | /** @param type May be null if the type is unknown. 738 | * @return May be null. */ 739 | public @Null T fromJson (Class type, char[] data, int offset, int length) { 740 | return readValue(type, null, new JsonReader().parse(data, offset, length)); 741 | } 742 | 743 | /** @param type May be null if the type is unknown. 744 | * @param elementType May be null if the type is unknown. 745 | * @return May be null. */ 746 | public @Null T fromJson (Class type, Class elementType, char[] data, int offset, int length) { 747 | return readValue(type, elementType, new JsonReader().parse(data, offset, length)); 748 | } 749 | 750 | /** @param type May be null if the type is unknown. 751 | * @return May be null. */ 752 | public @Null T fromJson (Class type, String json) { 753 | return readValue(type, null, new JsonReader().parse(json)); 754 | } 755 | 756 | /** @param type May be null if the type is unknown. 757 | * @return May be null. */ 758 | public @Null T fromJson (Class type, Class elementType, String json) { 759 | return readValue(type, elementType, new JsonReader().parse(json)); 760 | } 761 | 762 | public void readField (Object object, String name, JsonValue jsonData) { 763 | readField(object, name, name, null, jsonData); 764 | } 765 | 766 | public void readField (Object object, String name, @Null Class elementType, JsonValue jsonData) { 767 | readField(object, name, name, elementType, jsonData); 768 | } 769 | 770 | public void readField (Object object, String fieldName, String jsonName, JsonValue jsonData) { 771 | readField(object, fieldName, jsonName, null, jsonData); 772 | } 773 | 774 | /** @param elementType May be null if the type is unknown. */ 775 | public void readField (Object object, String fieldName, String jsonName, @Null Class elementType, JsonValue jsonMap) { 776 | Class type = object.getClass(); 777 | FieldMetadata metadata = getFields(type).get(fieldName); 778 | if (metadata == null) throw new JsonException("Field not found: " + fieldName + " (" + type.getName() + ")"); 779 | Field field = metadata.field; 780 | if (elementType == null) elementType = metadata.elementType; 781 | readField(object, field, jsonName, elementType, jsonMap); 782 | } 783 | 784 | /** @param object May be null if the field is static. 785 | * @param elementType May be null if the type is unknown. */ 786 | public void readField (@Null Object object, Field field, String jsonName, @Null Class elementType, JsonValue jsonMap) { 787 | JsonValue jsonValue = jsonMap.get(jsonName); 788 | if (jsonValue == null) return; 789 | try { 790 | field.set(object, readValue(field.getType(), elementType, jsonValue)); 791 | } catch (IllegalAccessException ex) { 792 | throw new JsonException("Error accessing field: " + field.getName() + " (" + field.getDeclaringClass().getName() + ")", 793 | ex); 794 | } catch (JsonException ex) { 795 | ex.addTrace(field.getName() + " (" + field.getDeclaringClass().getName() + ")"); 796 | throw ex; 797 | } catch (RuntimeException runtimeEx) { 798 | JsonException ex = new JsonException(runtimeEx); 799 | ex.addTrace(jsonValue.trace()); 800 | ex.addTrace(field.getName() + " (" + field.getDeclaringClass().getName() + ")"); 801 | throw ex; 802 | } 803 | } 804 | 805 | public void readFields (Object object, JsonValue jsonMap) { 806 | Class type = object.getClass(); 807 | OrderedMap fields = getFields(type); 808 | for (JsonValue child = jsonMap.child; child != null; child = child.next) { 809 | FieldMetadata metadata = fields.get(child.name().replace(" ", "_")); 810 | if (metadata == null) { 811 | if (child.name.equals(typeName)) continue; 812 | if (ignoreUnknownFields || ignoreUnknownField(type, child.name)) { 813 | if (debug) System.out.println("Ignoring unknown field: " + child.name + " (" + type.getName() + ")"); 814 | continue; 815 | } else { 816 | JsonException ex = new JsonException("Field not found: " + child.name + " (" + type.getName() + ")"); 817 | ex.addTrace(child.trace()); 818 | throw ex; 819 | } 820 | } else { 821 | if (ignoreDeprecated && !readDeprecated && metadata.deprecated) continue; 822 | } 823 | Field field = metadata.field; 824 | try { 825 | field.set(object, readValue(field.getType(), metadata.elementType, child)); 826 | } catch (IllegalAccessException ex) { 827 | throw new JsonException("Error accessing field: " + field.getName() + " (" + type.getName() + ")", ex); 828 | } catch (JsonException ex) { 829 | ex.addTrace(field.getName() + " (" + type.getName() + ")"); 830 | throw ex; 831 | } catch (RuntimeException runtimeEx) { 832 | JsonException ex = new JsonException(runtimeEx); 833 | ex.addTrace(child.trace()); 834 | ex.addTrace(field.getName() + " (" + type.getName() + ")"); 835 | throw ex; 836 | } 837 | } 838 | } 839 | 840 | /** Called for each unknown field name encountered by {@link #readFields(Object, JsonValue)} when {@link #ignoreUnknownFields} 841 | * is false to determine whether the unknown field name should be ignored. 842 | * @param type The object type being read. 843 | * @param fieldName A field name encountered in the JSON for which there is no matching class field. 844 | * @return true if the field name should be ignored and an exception won't be thrown by 845 | * {@link #readFields(Object, JsonValue)}. */ 846 | protected boolean ignoreUnknownField (Class type, String fieldName) { 847 | return false; 848 | } 849 | 850 | /** @param type May be null if the type is unknown. 851 | * @return May be null. */ 852 | public @Null T readValue (String name, @Null Class type, JsonValue jsonMap) { 853 | return readValue(type, null, jsonMap.get(name)); 854 | } 855 | 856 | /** @param type May be null if the type is unknown. 857 | * @return May be null. */ 858 | public @Null T readValue (String name, @Null Class type, T defaultValue, JsonValue jsonMap) { 859 | JsonValue jsonValue = jsonMap.get(name); 860 | if (jsonValue == null) return defaultValue; 861 | return readValue(type, null, jsonValue); 862 | } 863 | 864 | /** @param type May be null if the type is unknown. 865 | * @param elementType May be null if the type is unknown. 866 | * @return May be null. */ 867 | public @Null T readValue (String name, @Null Class type, @Null Class elementType, JsonValue jsonMap) { 868 | return readValue(type, elementType, jsonMap.get(name)); 869 | } 870 | 871 | /** @param type May be null if the type is unknown. 872 | * @param elementType May be null if the type is unknown. 873 | * @return May be null. */ 874 | public @Null T readValue (String name, @Null Class type, @Null Class elementType, T defaultValue, JsonValue jsonMap) { 875 | JsonValue jsonValue = jsonMap.get(name); 876 | return readValue(type, elementType, defaultValue, jsonValue); 877 | } 878 | 879 | /** @param type May be null if the type is unknown. 880 | * @param elementType May be null if the type is unknown. 881 | * @return May be null. */ 882 | public @Null T readValue (@Null Class type, @Null Class elementType, T defaultValue, JsonValue jsonData) { 883 | if (jsonData == null) return defaultValue; 884 | return readValue(type, elementType, jsonData); 885 | } 886 | 887 | /** @param type May be null if the type is unknown. 888 | * @return May be null. */ 889 | public @Null T readValue (@Null Class type, JsonValue jsonData) { 890 | return readValue(type, null, jsonData); 891 | } 892 | 893 | /** @param type May be null if the type is unknown. 894 | * @param elementType May be null if the type is unknown. 895 | * @return May be null. */ 896 | public @Null T readValue (@Null Class type, @Null Class elementType, JsonValue jsonData) { 897 | if (jsonData == null) return null; 898 | 899 | if (jsonData.isObject()) { 900 | String className = typeName == null ? null : jsonData.getString(typeName, null); 901 | if (className != null) { 902 | type = getClass(className); 903 | if (type == null) { 904 | try { 905 | type = (Class)Class.forName(className); 906 | } catch (ClassNotFoundException ex) { 907 | throw new JsonException(ex); 908 | } 909 | } 910 | } 911 | 912 | if (type == null) { 913 | if (defaultSerializer != null) return (T)defaultSerializer.read(this, jsonData, type); 914 | return (T)jsonData; 915 | } 916 | 917 | if (typeName != null && Collection.class.isAssignableFrom(type)) { 918 | // JSON object wrapper to specify type. 919 | jsonData = jsonData.get("items"); 920 | if (jsonData == null) 921 | throw new JsonException("Unable to convert object to collection: " + jsonData + " (" + type.getName() + ")"); 922 | } else { 923 | JsonSerializer serializer = classToSerializer.get(type); 924 | if (serializer != null) return (T)serializer.read(this, jsonData, type); 925 | 926 | if (type == String.class || type == Integer.class || type == Boolean.class || type == Float.class 927 | || type == Long.class || type == Double.class || type == Short.class || type == Byte.class 928 | || type == Character.class || Enum.class.isAssignableFrom(type)) { 929 | return readValue("value", type, jsonData); 930 | } 931 | 932 | Object object = newInstance(type); 933 | 934 | if (object instanceof JsonSerializable) { 935 | ((JsonSerializable)object).read(this, jsonData); 936 | return (T)object; 937 | } 938 | 939 | // JSON object special cases. 940 | if (object instanceof ObjectMap) { 941 | ObjectMap result = (ObjectMap)object; 942 | for (JsonValue child = jsonData.child; child != null; child = child.next) 943 | result.put(child.name, readValue(elementType, null, child)); 944 | return (T)result; 945 | } 946 | if (object instanceof Map) { 947 | Map result = (Map)object; 948 | for (JsonValue child = jsonData.child; child != null; child = child.next) { 949 | if (child.name.equals(typeName)) continue; 950 | result.put(child.name, readValue(elementType, null, child)); 951 | } 952 | return (T)result; 953 | } 954 | 955 | readFields(object, jsonData); 956 | return (T)object; 957 | } 958 | } 959 | 960 | if (type != null) { 961 | JsonSerializer serializer = classToSerializer.get(type); 962 | if (serializer != null) return (T)serializer.read(this, jsonData, type); 963 | 964 | if (JsonSerializable.class.isAssignableFrom(type)) { 965 | // A Serializable may be read as an array, string, etc, even though it will be written as an object. 966 | Object object = newInstance(type); 967 | ((JsonSerializable)object).read(this, jsonData); 968 | return (T)object; 969 | } 970 | } 971 | 972 | if (jsonData.isArray()) { 973 | // JSON array special cases. 974 | if (type == null || type == Object.class) type = (Class)ArrayList.class; 975 | if (Collection.class.isAssignableFrom(type)) { 976 | Collection result = type.isInterface() ? new ArrayList() : (Collection)newInstance(type); 977 | for (JsonValue child = jsonData.child; child != null; child = child.next) 978 | result.add(readValue(elementType, null, child)); 979 | return (T)result; 980 | } 981 | if (type.isArray()) { 982 | Class componentType = type.getComponentType(); 983 | if (elementType == null) elementType = componentType; 984 | Object result = Array.newInstance(componentType, jsonData.size); 985 | int i = 0; 986 | for (JsonValue child = jsonData.child; child != null; child = child.next) 987 | Array.set(result, i++, readValue(elementType, null, child)); 988 | return (T)result; 989 | } 990 | throw new JsonException("Unable to convert value to required type: " + jsonData + " (" + type.getName() + ")"); 991 | } 992 | 993 | if (jsonData.isNumber()) { 994 | try { 995 | if (type == null || type == float.class || type == Float.class) return (T)(Float)jsonData.asFloat(); 996 | if (type == int.class || type == Integer.class) return (T)(Integer)jsonData.asInt(); 997 | if (type == long.class || type == Long.class) return (T)(Long)jsonData.asLong(); 998 | if (type == double.class || type == Double.class) return (T)(Double)jsonData.asDouble(); 999 | if (type == String.class) return (T)jsonData.asString(); 1000 | if (type == short.class || type == Short.class) return (T)(Short)jsonData.asShort(); 1001 | if (type == byte.class || type == Byte.class) return (T)(Byte)jsonData.asByte(); 1002 | } catch (NumberFormatException ignored) { 1003 | } 1004 | jsonData = new JsonValue(jsonData.asString()); 1005 | } 1006 | 1007 | if (jsonData.isBoolean()) { 1008 | try { 1009 | if (type == null || type == boolean.class || type == Boolean.class) return (T)(Boolean)jsonData.asBoolean(); 1010 | } catch (NumberFormatException ignored) { 1011 | } 1012 | jsonData = new JsonValue(jsonData.asString()); 1013 | } 1014 | 1015 | if (jsonData.isString()) { 1016 | String string = jsonData.asString(); 1017 | if (type == null || type == String.class) return (T)string; 1018 | try { 1019 | if (type == int.class || type == Integer.class) return (T)Integer.valueOf(string); 1020 | if (type == float.class || type == Float.class) return (T)Float.valueOf(string); 1021 | if (type == long.class || type == Long.class) return (T)Long.valueOf(string); 1022 | if (type == double.class || type == Double.class) return (T)Double.valueOf(string); 1023 | if (type == short.class || type == Short.class) return (T)Short.valueOf(string); 1024 | if (type == byte.class || type == Byte.class) return (T)Byte.valueOf(string); 1025 | } catch (NumberFormatException ignored) { 1026 | } 1027 | if (type == boolean.class || type == Boolean.class) return (T)Boolean.valueOf(string); 1028 | if (type == char.class || type == Character.class) return (T)(Character)string.charAt(0); 1029 | if (Enum.class.isAssignableFrom(type)) { 1030 | Enum[] constants = (Enum[])type.getEnumConstants(); 1031 | for (int i = 0, n = constants.length; i < n; i++) { 1032 | Enum e = constants[i]; 1033 | if (string.equals(convertToString(e))) return (T)e; 1034 | } 1035 | } 1036 | if (type == CharSequence.class) return (T)string; 1037 | throw new JsonException("Unable to convert value to required type: " + jsonData + " (" + type.getName() + ")"); 1038 | } 1039 | 1040 | return null; 1041 | } 1042 | 1043 | /** Each field on the to object is set to the value for the field with the same name on the from 1044 | * object. The to object must have at least all the fields of the from object with the same name and 1045 | * type. */ 1046 | public void copyFields (Object from, Object to) { 1047 | OrderedMap toFields = getFields(to.getClass()); 1048 | for (ObjectMap.Entry entry : getFields(from.getClass())) { 1049 | FieldMetadata toField = toFields.get(entry.key); 1050 | Field fromField = entry.value.field; 1051 | if (toField == null) throw new JsonException("To object is missing field: " + entry.key); 1052 | try { 1053 | toField.field.set(to, fromField.get(from)); 1054 | } catch (IllegalAccessException ex) { 1055 | throw new JsonException("Error copying field: " + fromField.getName(), ex); 1056 | } 1057 | } 1058 | } 1059 | 1060 | private String convertToString (Enum e) { 1061 | return enumNames ? e.name() : e.toString(); 1062 | } 1063 | 1064 | private String convertToString (Object object) { 1065 | if (object instanceof Enum) return convertToString((Enum)object); 1066 | if (object instanceof Class) return ((Class)object).getName(); 1067 | return String.valueOf(object); 1068 | } 1069 | 1070 | protected Object newInstance (Class type) { 1071 | try { 1072 | return type.newInstance(); 1073 | } catch (Exception ex) { 1074 | try { 1075 | // Try a private constructor. 1076 | Constructor constructor = type.getDeclaredConstructor(); 1077 | constructor.setAccessible(true); 1078 | return constructor.newInstance(); 1079 | } catch (SecurityException ignored) { 1080 | } catch (IllegalAccessException ignored) { 1081 | if (Enum.class.isAssignableFrom(type)) { 1082 | if (type.getEnumConstants() == null) type = type.getSuperclass(); 1083 | return type.getEnumConstants()[0]; 1084 | } 1085 | if (type.isArray()) 1086 | throw new JsonException("Encountered JSON object when expected array of type: " + type.getName(), ex); 1087 | else if (type.isMemberClass() && !Modifier.isStatic(type.getModifiers())) 1088 | throw new JsonException("Class cannot be created (non-static member class): " + type.getName(), ex); 1089 | else 1090 | throw new JsonException("Class cannot be created (missing no-arg constructor): " + type.getName(), ex); 1091 | } catch (Exception privateConstructorException) { 1092 | ex = privateConstructorException; 1093 | } 1094 | throw new JsonException("Error constructing instance of class: " + type.getName(), ex); 1095 | } 1096 | } 1097 | 1098 | public String prettyPrint (@Null Object object) { 1099 | return prettyPrint(object, 0); 1100 | } 1101 | 1102 | public String prettyPrint (String json) { 1103 | return prettyPrint(json, 0); 1104 | } 1105 | 1106 | public String prettyPrint (@Null Object object, int singleLineColumns) { 1107 | return prettyPrint(toJson(object), singleLineColumns); 1108 | } 1109 | 1110 | public String prettyPrint (String json, int singleLineColumns) { 1111 | return new JsonReader().parse(json).prettyPrint(outputType, singleLineColumns); 1112 | } 1113 | 1114 | public String prettyPrint (@Null Object object, PrettyPrintSettings settings) { 1115 | return prettyPrint(toJson(object), settings); 1116 | } 1117 | 1118 | public String prettyPrint (String json, PrettyPrintSettings settings) { 1119 | return new JsonReader().parse(json).prettyPrint(settings); 1120 | } 1121 | 1122 | static private class FieldMetadata { 1123 | final Field field; 1124 | Class elementType; 1125 | boolean deprecated; 1126 | 1127 | public FieldMetadata (Field field) { 1128 | this.field = field; 1129 | int index = (ObjectMap.class.isAssignableFrom(field.getType()) || Map.class.isAssignableFrom(field.getType())) ? 1 : 0; 1130 | this.elementType = getElementType(field, index); 1131 | deprecated = field.isAnnotationPresent(Deprecated.class); 1132 | } 1133 | 1134 | /** If the type of the field is parameterized, returns the Class object representing the parameter type at the specified 1135 | * index, null otherwise. */ 1136 | static private @Null Class getElementType (Field field, int index) { 1137 | Type genericType = field.getGenericType(); 1138 | if (genericType instanceof ParameterizedType) { 1139 | Type[] actualTypes = ((ParameterizedType)genericType).getActualTypeArguments(); 1140 | if (actualTypes.length - 1 >= index) { 1141 | Type actualType = actualTypes[index]; 1142 | if (actualType instanceof Class) 1143 | return (Class)actualType; 1144 | else if (actualType instanceof ParameterizedType) 1145 | return (Class)((ParameterizedType)actualType).getRawType(); 1146 | else if (actualType instanceof GenericArrayType) { 1147 | Type componentType = ((GenericArrayType)actualType).getGenericComponentType(); 1148 | if (componentType instanceof Class) return Array.newInstance((Class)componentType, 0).getClass(); 1149 | } 1150 | } 1151 | } 1152 | return null; 1153 | } 1154 | } 1155 | } 1156 | -------------------------------------------------------------------------------- /src/com/esotericsoftware/jsonbeans/JsonException.java: -------------------------------------------------------------------------------- 1 | 2 | package com.esotericsoftware.jsonbeans; 3 | 4 | /** Indicates an error during serialization due to misconfiguration or during deserialization due to invalid input data. 5 | * @author Nathan Sweet */ 6 | public class JsonException extends RuntimeException { 7 | private StringBuilder trace; 8 | 9 | public JsonException () { 10 | super(); 11 | } 12 | 13 | public JsonException (String message, Throwable cause) { 14 | super(message, cause); 15 | } 16 | 17 | public JsonException (String message) { 18 | super(message); 19 | } 20 | 21 | public JsonException (Throwable cause) { 22 | super("", cause); 23 | } 24 | 25 | /** Returns true if any of the exceptions that caused this exception are of the specified type. */ 26 | public boolean causedBy (Class type) { 27 | return causedBy(this, type); 28 | } 29 | 30 | private boolean causedBy (Throwable ex, Class type) { 31 | Throwable cause = ex.getCause(); 32 | if (cause == null || cause == ex) return false; 33 | if (type.isAssignableFrom(cause.getClass())) return true; 34 | return causedBy(cause, type); 35 | } 36 | 37 | public String getMessage () { 38 | if (trace == null) return super.getMessage(); 39 | StringBuilder sb = new StringBuilder(512); 40 | sb.append(super.getMessage()); 41 | if (sb.length() > 0) sb.append('\n'); 42 | sb.append("Serialization trace:"); 43 | sb.append(trace); 44 | return sb.toString(); 45 | } 46 | 47 | /** Adds information to the exception message about where in the the object graph serialization failure occurred. Serializers 48 | * can catch {@link JsonException}, add trace information, and rethrow the exception. */ 49 | public void addTrace (String info) { 50 | if (info == null) throw new IllegalArgumentException("info cannot be null."); 51 | if (trace == null) trace = new StringBuilder(512); 52 | trace.append('\n'); 53 | trace.append(info); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/com/esotericsoftware/jsonbeans/JsonReader.java: -------------------------------------------------------------------------------- 1 | // line 1 "JsonReader.rl" 2 | // Do not edit this file! Generated by Ragel. 3 | // Ragel.exe -G2 -J -o JsonReader.java JsonReader.rl 4 | /******************************************************************************* 5 | * Copyright 2011 See AUTHORS file. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | ******************************************************************************/ 19 | 20 | package com.esotericsoftware.jsonbeans; 21 | 22 | import java.io.File; 23 | import java.io.FileInputStream; 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.io.InputStreamReader; 27 | import java.io.Reader; 28 | import java.util.ArrayList; 29 | 30 | import com.esotericsoftware.jsonbeans.JsonValue.ValueType; 31 | 32 | /** Lightweight JSON parser.
33 | *
34 | * The default behavior is to parse the JSON into a DOM containing {@link JsonValue} objects. Extend this class and override 35 | * methods to perform event driven parsing. When this is done, the parse methods will return null. 36 | * @author Nathan Sweet */ 37 | public class JsonReader { 38 | public JsonValue parse (String json) { 39 | char[] data = json.toCharArray(); 40 | return parse(data, 0, data.length); 41 | } 42 | 43 | public JsonValue parse (Reader reader) { 44 | try { 45 | char[] data = new char[1024]; 46 | int offset = 0; 47 | while (true) { 48 | int length = reader.read(data, offset, data.length - offset); 49 | if (length == -1) break; 50 | if (length == 0) { 51 | char[] newData = new char[data.length * 2]; 52 | System.arraycopy(data, 0, newData, 0, data.length); 53 | data = newData; 54 | } else 55 | offset += length; 56 | } 57 | return parse(data, 0, offset); 58 | } catch (IOException ex) { 59 | throw new JsonException(ex); 60 | } finally { 61 | try { 62 | reader.close(); 63 | } catch (IOException ignored) { 64 | } 65 | } 66 | } 67 | 68 | public JsonValue parse (InputStream input) { 69 | try { 70 | return parse(new InputStreamReader(input, "UTF-8")); 71 | } catch (IOException ex) { 72 | throw new JsonException(ex); 73 | } finally { 74 | try { 75 | input.close(); 76 | } catch (IOException ignored) { 77 | } 78 | } 79 | } 80 | 81 | public JsonValue parse (File file) { 82 | try { 83 | return parse(new InputStreamReader(new FileInputStream(file), "UTF-8")); 84 | } catch (Exception ex) { 85 | throw new JsonException("Error parsing file: " + file, ex); 86 | } 87 | } 88 | 89 | public JsonValue parse (char[] data, int offset, int length) { 90 | int cs, p = offset, pe = length, eof = pe, top = 0; 91 | int[] stack = new int[4]; 92 | 93 | int s = 0; 94 | ArrayList names = new ArrayList(8); 95 | boolean needsUnescape = false, stringIsName = false, stringIsUnquoted = false; 96 | RuntimeException parseRuntimeEx = null; 97 | 98 | boolean debug = false; 99 | if (debug) System.out.println(); 100 | 101 | try { 102 | 103 | // line 3 "JsonReader.java" 104 | { 105 | cs = json_start; 106 | top = 0; 107 | } 108 | 109 | // line 8 "JsonReader.java" 110 | { 111 | int _klen; 112 | int _trans = 0; 113 | int _acts; 114 | int _nacts; 115 | int _keys; 116 | int _goto_targ = 0; 117 | 118 | _goto: 119 | while (true) { 120 | switch (_goto_targ) { 121 | case 0: 122 | if (p == pe) { 123 | _goto_targ = 4; 124 | continue _goto; 125 | } 126 | if (cs == 0) { 127 | _goto_targ = 5; 128 | continue _goto; 129 | } 130 | case 1: 131 | _match: 132 | do { 133 | _keys = _json_key_offsets[cs]; 134 | _trans = _json_index_offsets[cs]; 135 | _klen = _json_single_lengths[cs]; 136 | if (_klen > 0) { 137 | int _lower = _keys; 138 | int _mid; 139 | int _upper = _keys + _klen - 1; 140 | while (true) { 141 | if (_upper < _lower) break; 142 | 143 | _mid = _lower + ((_upper - _lower) >> 1); 144 | if (data[p] < _json_trans_keys[_mid]) 145 | _upper = _mid - 1; 146 | else if (data[p] > _json_trans_keys[_mid]) 147 | _lower = _mid + 1; 148 | else { 149 | _trans += (_mid - _keys); 150 | break _match; 151 | } 152 | } 153 | _keys += _klen; 154 | _trans += _klen; 155 | } 156 | 157 | _klen = _json_range_lengths[cs]; 158 | if (_klen > 0) { 159 | int _lower = _keys; 160 | int _mid; 161 | int _upper = _keys + (_klen << 1) - 2; 162 | while (true) { 163 | if (_upper < _lower) break; 164 | 165 | _mid = _lower + (((_upper - _lower) >> 1) & ~1); 166 | if (data[p] < _json_trans_keys[_mid]) 167 | _upper = _mid - 2; 168 | else if (data[p] > _json_trans_keys[_mid + 1]) 169 | _lower = _mid + 2; 170 | else { 171 | _trans += ((_mid - _keys) >> 1); 172 | break _match; 173 | } 174 | } 175 | _trans += _klen; 176 | } 177 | } while (false); 178 | 179 | _trans = _json_indicies[_trans]; 180 | cs = _json_trans_targs[_trans]; 181 | 182 | if (_json_trans_actions[_trans] != 0) { 183 | _acts = _json_trans_actions[_trans]; 184 | _nacts = (int)_json_actions[_acts++]; 185 | while (_nacts-- > 0) { 186 | switch (_json_actions[_acts++]) { 187 | case 0: 188 | // line 110 "JsonReader.rl" 189 | { 190 | stringIsName = true; 191 | } 192 | break; 193 | case 1: 194 | // line 113 "JsonReader.rl" 195 | { 196 | String value = new String(data, s, p - s); 197 | if (needsUnescape) value = unescape(value); 198 | outer: 199 | if (stringIsName) { 200 | stringIsName = false; 201 | if (debug) System.out.println("name: " + value); 202 | names.add(value); 203 | } else { 204 | String name = names.size() > 0 ? names.remove(names.size() - 1) : null; 205 | if (stringIsUnquoted) { 206 | if (value.equals("true")) { 207 | if (debug) System.out.println("boolean: " + name + "=true"); 208 | bool(name, true); 209 | break outer; 210 | } else if (value.equals("false")) { 211 | if (debug) System.out.println("boolean: " + name + "=false"); 212 | bool(name, false); 213 | break outer; 214 | } else if (value.equals("null")) { 215 | string(name, null); 216 | break outer; 217 | } 218 | boolean couldBeDouble = false, couldBeLong = true; 219 | outer2: 220 | for (int i = s; i < p; i++) { 221 | switch (data[i]) { 222 | case '0': 223 | case '1': 224 | case '2': 225 | case '3': 226 | case '4': 227 | case '5': 228 | case '6': 229 | case '7': 230 | case '8': 231 | case '9': 232 | case '-': 233 | case '+': 234 | break; 235 | case '.': 236 | case 'e': 237 | case 'E': 238 | couldBeDouble = true; 239 | couldBeLong = false; 240 | break; 241 | default: 242 | couldBeDouble = false; 243 | couldBeLong = false; 244 | break outer2; 245 | } 246 | } 247 | if (couldBeDouble) { 248 | try { 249 | if (debug) System.out.println("double: " + name + "=" + Double.parseDouble(value)); 250 | number(name, Double.parseDouble(value), value); 251 | break outer; 252 | } catch (NumberFormatException ignored) { 253 | } 254 | } else if (couldBeLong) { 255 | if (debug) System.out.println("double: " + name + "=" + Double.parseDouble(value)); 256 | try { 257 | number(name, Long.parseLong(value), value); 258 | break outer; 259 | } catch (NumberFormatException ignored) { 260 | } 261 | } 262 | } 263 | if (debug) System.out.println("string: " + name + "=" + value); 264 | string(name, value); 265 | } 266 | stringIsUnquoted = false; 267 | s = p; 268 | } 269 | break; 270 | case 2: 271 | // line 187 "JsonReader.rl" 272 | { 273 | String name = names.size() > 0 ? names.remove(names.size() - 1) : null; 274 | if (debug) System.out.println("startObject: " + name); 275 | startObject(name); 276 | { 277 | if (top == stack.length) { 278 | int[] newStack = new int[stack.length * 2]; 279 | System.arraycopy(stack, 0, newStack, 0, stack.length); 280 | stack = newStack; 281 | } 282 | { 283 | stack[top++] = cs; 284 | cs = 5; 285 | _goto_targ = 2; 286 | if (true) continue _goto; 287 | } 288 | } 289 | } 290 | break; 291 | case 3: 292 | // line 193 "JsonReader.rl" 293 | { 294 | if (debug) System.out.println("endObject"); 295 | pop(); 296 | { 297 | cs = stack[--top]; 298 | _goto_targ = 2; 299 | if (true) continue _goto; 300 | } 301 | } 302 | break; 303 | case 4: 304 | // line 198 "JsonReader.rl" 305 | { 306 | String name = names.size() > 0 ? names.remove(names.size() - 1) : null; 307 | if (debug) System.out.println("startArray: " + name); 308 | startArray(name); 309 | { 310 | if (top == stack.length) { 311 | int[] newStack = new int[stack.length * 2]; 312 | System.arraycopy(stack, 0, newStack, 0, stack.length); 313 | stack = newStack; 314 | } 315 | { 316 | stack[top++] = cs; 317 | cs = 23; 318 | _goto_targ = 2; 319 | if (true) continue _goto; 320 | } 321 | } 322 | } 323 | break; 324 | case 5: 325 | // line 204 "JsonReader.rl" 326 | { 327 | if (debug) System.out.println("endArray"); 328 | pop(); 329 | { 330 | cs = stack[--top]; 331 | _goto_targ = 2; 332 | if (true) continue _goto; 333 | } 334 | } 335 | break; 336 | case 6: 337 | // line 209 "JsonReader.rl" 338 | { 339 | int start = p - 1; 340 | if (data[p++] == '/') { 341 | while (p != eof && data[p] != '\n') 342 | p++; 343 | p--; 344 | } else { 345 | while (p + 1 < eof && data[p] != '*' || data[p + 1] != '/') 346 | p++; 347 | p++; 348 | } 349 | if (debug) System.out.println("comment " + new String(data, start, p - start)); 350 | } 351 | break; 352 | case 7: 353 | // line 222 "JsonReader.rl" 354 | { 355 | if (debug) System.out.println("unquotedChars"); 356 | s = p; 357 | needsUnescape = false; 358 | stringIsUnquoted = true; 359 | if (stringIsName) { 360 | outer: 361 | while (true) { 362 | switch (data[p]) { 363 | case '\\': 364 | needsUnescape = true; 365 | break; 366 | case '/': 367 | if (p + 1 == eof) break; 368 | char c = data[p + 1]; 369 | if (c == '/' || c == '*') break outer; 370 | break; 371 | case ':': 372 | case '\r': 373 | case '\n': 374 | break outer; 375 | } 376 | if (debug) System.out.println("unquotedChar (name): '" + data[p] + "'"); 377 | p++; 378 | if (p == eof) break; 379 | } 380 | } else { 381 | outer: 382 | while (true) { 383 | switch (data[p]) { 384 | case '\\': 385 | needsUnescape = true; 386 | break; 387 | case '/': 388 | if (p + 1 == eof) break; 389 | char c = data[p + 1]; 390 | if (c == '/' || c == '*') break outer; 391 | break; 392 | case '}': 393 | case ']': 394 | case ',': 395 | case '\r': 396 | case '\n': 397 | break outer; 398 | } 399 | if (debug) System.out.println("unquotedChar (value): '" + data[p] + "'"); 400 | p++; 401 | if (p == eof) break; 402 | } 403 | } 404 | p--; 405 | while (Character.isSpace(data[p])) 406 | p--; 407 | } 408 | break; 409 | case 8: 410 | // line 276 "JsonReader.rl" 411 | { 412 | if (debug) System.out.println("quotedChars"); 413 | s = ++p; 414 | needsUnescape = false; 415 | outer: 416 | while (true) { 417 | switch (data[p]) { 418 | case '\\': 419 | needsUnescape = true; 420 | p++; 421 | break; 422 | case '"': 423 | break outer; 424 | } 425 | // if (debug) System.out.println("quotedChar: '" + data[p] + "'"); 426 | p++; 427 | if (p == eof) break; 428 | } 429 | p--; 430 | } 431 | break; 432 | // line 313 "JsonReader.java" 433 | } 434 | } 435 | } 436 | 437 | case 2: 438 | if (cs == 0) { 439 | _goto_targ = 5; 440 | continue _goto; 441 | } 442 | if (++p != pe) { 443 | _goto_targ = 1; 444 | continue _goto; 445 | } 446 | case 4: 447 | if (p == eof) { 448 | int __acts = _json_eof_actions[cs]; 449 | int __nacts = (int)_json_actions[__acts++]; 450 | while (__nacts-- > 0) { 451 | switch (_json_actions[__acts++]) { 452 | case 1: 453 | // line 113 "JsonReader.rl" 454 | { 455 | String value = new String(data, s, p - s); 456 | if (needsUnescape) value = unescape(value); 457 | outer: 458 | if (stringIsName) { 459 | stringIsName = false; 460 | if (debug) System.out.println("name: " + value); 461 | names.add(value); 462 | } else { 463 | String name = names.size() > 0 ? names.remove(names.size() - 1) : null; 464 | if (stringIsUnquoted) { 465 | if (value.equals("true")) { 466 | if (debug) System.out.println("boolean: " + name + "=true"); 467 | bool(name, true); 468 | break outer; 469 | } else if (value.equals("false")) { 470 | if (debug) System.out.println("boolean: " + name + "=false"); 471 | bool(name, false); 472 | break outer; 473 | } else if (value.equals("null")) { 474 | string(name, null); 475 | break outer; 476 | } 477 | boolean couldBeDouble = false, couldBeLong = true; 478 | outer2: 479 | for (int i = s; i < p; i++) { 480 | switch (data[i]) { 481 | case '0': 482 | case '1': 483 | case '2': 484 | case '3': 485 | case '4': 486 | case '5': 487 | case '6': 488 | case '7': 489 | case '8': 490 | case '9': 491 | case '-': 492 | case '+': 493 | break; 494 | case '.': 495 | case 'e': 496 | case 'E': 497 | couldBeDouble = true; 498 | couldBeLong = false; 499 | break; 500 | default: 501 | couldBeDouble = false; 502 | couldBeLong = false; 503 | break outer2; 504 | } 505 | } 506 | if (couldBeDouble) { 507 | try { 508 | if (debug) System.out.println("double: " + name + "=" + Double.parseDouble(value)); 509 | number(name, Double.parseDouble(value), value); 510 | break outer; 511 | } catch (NumberFormatException ignored) { 512 | } 513 | } else if (couldBeLong) { 514 | if (debug) System.out.println("double: " + name + "=" + Double.parseDouble(value)); 515 | try { 516 | number(name, Long.parseLong(value), value); 517 | break outer; 518 | } catch (NumberFormatException ignored) { 519 | } 520 | } 521 | } 522 | if (debug) System.out.println("string: " + name + "=" + value); 523 | string(name, value); 524 | } 525 | stringIsUnquoted = false; 526 | s = p; 527 | } 528 | break; 529 | // line 411 "JsonReader.java" 530 | } 531 | } 532 | } 533 | 534 | case 5: 535 | } 536 | break; 537 | } 538 | } 539 | 540 | // line 312 "JsonReader.rl" 541 | 542 | } catch (RuntimeException ex) { 543 | parseRuntimeEx = ex; 544 | } 545 | 546 | JsonValue root = this.root; 547 | this.root = null; 548 | current = null; 549 | lastChild.clear(); 550 | 551 | if (p < pe) { 552 | int lineNumber = 1; 553 | for (int i = 0; i < p; i++) 554 | if (data[i] == '\n') lineNumber++; 555 | int start = Math.max(0, p - 32); 556 | throw new JsonException("Error parsing JSON on line " + lineNumber + " near: " + new String(data, start, p - start) 557 | + "*ERROR*" + new String(data, p, Math.min(64, pe - p)), parseRuntimeEx); 558 | } 559 | if (elements.size() != 0) { 560 | JsonValue element = elements.get(elements.size() - 1); 561 | elements.clear(); 562 | if (element != null && element.isObject()) 563 | throw new JsonException("Error parsing JSON, unmatched brace."); 564 | else 565 | throw new JsonException("Error parsing JSON, unmatched bracket."); 566 | } 567 | if (parseRuntimeEx != null) throw new JsonException("Error parsing JSON: " + new String(data), parseRuntimeEx); 568 | return root; 569 | } 570 | 571 | // line 421 "JsonReader.java" 572 | private static byte[] init__json_actions_0 () { 573 | return new byte[] {0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8, 2, 0, 7, 2, 0, 8, 2, 1, 3, 2, 1, 5}; 574 | } 575 | 576 | private static final byte _json_actions[] = init__json_actions_0(); 577 | 578 | private static short[] init__json_key_offsets_0 () { 579 | return new short[] {0, 0, 11, 13, 14, 16, 25, 31, 37, 39, 50, 57, 64, 73, 74, 83, 85, 87, 96, 98, 100, 101, 103, 105, 116, 580 | 123, 130, 141, 142, 153, 155, 157, 168, 170, 172, 174, 179, 184, 184}; 581 | } 582 | 583 | private static final short _json_key_offsets[] = init__json_key_offsets_0(); 584 | 585 | private static char[] init__json_trans_keys_0 () { 586 | return new char[] {13, 32, 34, 44, 47, 58, 91, 93, 123, 9, 10, 42, 47, 34, 42, 47, 13, 32, 34, 44, 47, 58, 125, 9, 10, 13, 587 | 32, 47, 58, 9, 10, 13, 32, 47, 58, 9, 10, 42, 47, 13, 32, 34, 44, 47, 58, 91, 93, 123, 9, 10, 9, 10, 13, 32, 44, 47, 125, 588 | 9, 10, 13, 32, 44, 47, 125, 13, 32, 34, 44, 47, 58, 125, 9, 10, 34, 13, 32, 34, 44, 47, 58, 125, 9, 10, 42, 47, 42, 47, 589 | 13, 32, 34, 44, 47, 58, 125, 9, 10, 42, 47, 42, 47, 34, 42, 47, 42, 47, 13, 32, 34, 44, 47, 58, 91, 93, 123, 9, 10, 9, 590 | 10, 13, 32, 44, 47, 93, 9, 10, 13, 32, 44, 47, 93, 13, 32, 34, 44, 47, 58, 91, 93, 123, 9, 10, 34, 13, 32, 34, 44, 47, 591 | 58, 91, 93, 123, 9, 10, 42, 47, 42, 47, 13, 32, 34, 44, 47, 58, 91, 93, 123, 9, 10, 42, 47, 42, 47, 42, 47, 13, 32, 47, 592 | 9, 10, 13, 32, 47, 9, 10, 0}; 593 | } 594 | 595 | private static final char _json_trans_keys[] = init__json_trans_keys_0(); 596 | 597 | private static byte[] init__json_single_lengths_0 () { 598 | return new byte[] {0, 9, 2, 1, 2, 7, 4, 4, 2, 9, 7, 7, 7, 1, 7, 2, 2, 7, 2, 2, 1, 2, 2, 9, 7, 7, 9, 1, 9, 2, 2, 9, 2, 2, 2, 599 | 3, 3, 0, 0}; 600 | } 601 | 602 | private static final byte _json_single_lengths[] = init__json_single_lengths_0(); 603 | 604 | private static byte[] init__json_range_lengths_0 () { 605 | return new byte[] {0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 606 | 1, 1, 0, 0}; 607 | } 608 | 609 | private static final byte _json_range_lengths[] = init__json_range_lengths_0(); 610 | 611 | private static short[] init__json_index_offsets_0 () { 612 | return new short[] {0, 0, 11, 14, 16, 19, 28, 34, 40, 43, 54, 62, 70, 79, 81, 90, 93, 96, 105, 108, 111, 113, 116, 119, 130, 613 | 138, 146, 157, 159, 170, 173, 176, 187, 190, 193, 196, 201, 206, 207}; 614 | } 615 | 616 | private static final short _json_index_offsets[] = init__json_index_offsets_0(); 617 | 618 | private static byte[] init__json_indicies_0 () { 619 | return new byte[] {1, 1, 2, 3, 4, 3, 5, 3, 6, 1, 0, 7, 7, 3, 8, 3, 9, 9, 3, 11, 11, 12, 13, 14, 3, 15, 11, 10, 16, 16, 17, 620 | 18, 16, 3, 19, 19, 20, 21, 19, 3, 22, 22, 3, 21, 21, 24, 3, 25, 3, 26, 3, 27, 21, 23, 28, 29, 29, 28, 30, 31, 32, 3, 33, 621 | 34, 34, 33, 13, 35, 15, 3, 34, 34, 12, 36, 37, 3, 15, 34, 10, 16, 3, 36, 36, 12, 3, 38, 3, 3, 36, 10, 39, 39, 3, 40, 40, 622 | 3, 13, 13, 12, 3, 41, 3, 15, 13, 10, 42, 42, 3, 43, 43, 3, 28, 3, 44, 44, 3, 45, 45, 3, 47, 47, 48, 49, 50, 3, 51, 52, 623 | 53, 47, 46, 54, 55, 55, 54, 56, 57, 58, 3, 59, 60, 60, 59, 49, 61, 52, 3, 60, 60, 48, 62, 63, 3, 51, 52, 53, 60, 46, 54, 624 | 3, 62, 62, 48, 3, 64, 3, 51, 3, 53, 62, 46, 65, 65, 3, 66, 66, 3, 49, 49, 48, 3, 67, 3, 51, 52, 53, 49, 46, 68, 68, 3, 625 | 69, 69, 3, 70, 70, 3, 8, 8, 71, 8, 3, 72, 72, 73, 72, 3, 3, 3, 0}; 626 | } 627 | 628 | private static final byte _json_indicies[] = init__json_indicies_0(); 629 | 630 | private static byte[] init__json_trans_targs_0 () { 631 | return new byte[] {35, 1, 3, 0, 4, 36, 36, 36, 36, 1, 6, 5, 13, 17, 22, 37, 7, 8, 9, 7, 8, 9, 7, 10, 20, 21, 11, 11, 11, 12, 632 | 17, 19, 37, 11, 12, 19, 14, 16, 15, 14, 12, 18, 17, 11, 9, 5, 24, 23, 27, 31, 34, 25, 38, 25, 25, 26, 31, 33, 38, 25, 26, 633 | 33, 28, 30, 29, 28, 26, 32, 31, 25, 23, 2, 36, 2}; 634 | } 635 | 636 | private static final byte _json_trans_targs[] = init__json_trans_targs_0(); 637 | 638 | private static byte[] init__json_trans_actions_0 () { 639 | return new byte[] {13, 0, 15, 0, 0, 7, 3, 11, 1, 11, 17, 0, 20, 0, 0, 5, 1, 1, 1, 0, 0, 0, 11, 13, 15, 0, 7, 3, 1, 1, 1, 1, 640 | 23, 0, 0, 0, 0, 0, 0, 11, 11, 0, 11, 11, 11, 11, 13, 0, 15, 0, 0, 7, 9, 3, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 11, 11, 0, 641 | 11, 11, 11, 1, 0, 0}; 642 | } 643 | 644 | private static final byte _json_trans_actions[] = init__json_trans_actions_0(); 645 | 646 | private static byte[] init__json_eof_actions_0 () { 647 | return new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 648 | 1, 0, 0, 0}; 649 | } 650 | 651 | private static final byte _json_eof_actions[] = init__json_eof_actions_0(); 652 | 653 | static final int json_start = 1; 654 | static final int json_first_final = 35; 655 | static final int json_error = 0; 656 | 657 | static final int json_en_object = 5; 658 | static final int json_en_array = 23; 659 | static final int json_en_main = 1; 660 | 661 | // line 343 "JsonReader.rl" 662 | 663 | private final ArrayList elements = new ArrayList(8); 664 | private final ArrayList lastChild = new ArrayList(8); 665 | private JsonValue root, current; 666 | 667 | /** @param name May be null. */ 668 | private void addChild (@Null String name, JsonValue child) { 669 | child.setName(name); 670 | if (current == null) { 671 | current = child; 672 | root = child; 673 | } else if (current.isArray() || current.isObject()) { 674 | child.parent = current; 675 | if (current.size == 0) 676 | current.child = child; 677 | else { 678 | JsonValue last = lastChild.remove(elements.size() - 1); 679 | last.next = child; 680 | child.prev = last; 681 | } 682 | lastChild.add(child); 683 | current.size++; 684 | } else 685 | root = current; 686 | } 687 | 688 | /** @param name May be null. */ 689 | protected void startObject (@Null String name) { 690 | JsonValue value = new JsonValue(ValueType.object); 691 | if (current != null) addChild(name, value); 692 | elements.add(value); 693 | current = value; 694 | } 695 | 696 | /** @param name May be null. */ 697 | protected void startArray (@Null String name) { 698 | JsonValue value = new JsonValue(ValueType.array); 699 | if (current != null) addChild(name, value); 700 | elements.add(value); 701 | current = value; 702 | } 703 | 704 | protected void pop () { 705 | root = elements.remove(elements.size() - 1); 706 | if (current.size > 0) lastChild.remove(lastChild.size() - 1); 707 | current = elements.size() > 0 ? elements.get(elements.size() - 1) : null; 708 | } 709 | 710 | protected void string (String name, String value) { 711 | addChild(name, new JsonValue(value)); 712 | } 713 | 714 | protected void number (String name, double value, String stringValue) { 715 | addChild(name, new JsonValue(value, stringValue)); 716 | } 717 | 718 | protected void number (String name, long value, String stringValue) { 719 | addChild(name, new JsonValue(value, stringValue)); 720 | } 721 | 722 | protected void bool (String name, boolean value) { 723 | addChild(name, new JsonValue(value)); 724 | } 725 | 726 | private String unescape (String value) { 727 | int length = value.length(); 728 | StringBuilder buffer = new StringBuilder(length + 16); 729 | for (int i = 0; i < length;) { 730 | char c = value.charAt(i++); 731 | if (c != '\\') { 732 | buffer.append(c); 733 | continue; 734 | } 735 | if (i == length) break; 736 | c = value.charAt(i++); 737 | if (c == 'u') { 738 | buffer.append(Character.toChars(Integer.parseInt(value.substring(i, i + 4), 16))); 739 | i += 4; 740 | continue; 741 | } 742 | switch (c) { 743 | case '"': 744 | case '\\': 745 | case '/': 746 | break; 747 | case 'b': 748 | c = '\b'; 749 | break; 750 | case 'f': 751 | c = '\f'; 752 | break; 753 | case 'n': 754 | c = '\n'; 755 | break; 756 | case 'r': 757 | c = '\r'; 758 | break; 759 | case 't': 760 | c = '\t'; 761 | break; 762 | default: 763 | throw new JsonException("Illegal escaped character: \\" + c); 764 | } 765 | buffer.append(c); 766 | } 767 | return buffer.toString(); 768 | } 769 | } 770 | -------------------------------------------------------------------------------- /src/com/esotericsoftware/jsonbeans/JsonSerializable.java: -------------------------------------------------------------------------------- 1 | 2 | package com.esotericsoftware.jsonbeans; 3 | 4 | public interface JsonSerializable { 5 | public void write (Json json); 6 | 7 | public void read (Json json, JsonValue jsonData); 8 | } 9 | -------------------------------------------------------------------------------- /src/com/esotericsoftware/jsonbeans/JsonSerializer.java: -------------------------------------------------------------------------------- 1 | 2 | package com.esotericsoftware.jsonbeans; 3 | 4 | public interface JsonSerializer { 5 | public void write (Json json, T object, Class knownType); 6 | 7 | public T read (Json json, JsonValue jsonData, Class type); 8 | } 9 | -------------------------------------------------------------------------------- /src/com/esotericsoftware/jsonbeans/JsonValue.java: -------------------------------------------------------------------------------- 1 | 2 | package com.esotericsoftware.jsonbeans; 3 | 4 | import java.io.IOException; 5 | import java.io.Writer; 6 | import java.util.Iterator; 7 | import java.util.NoSuchElementException; 8 | 9 | /** Container for a JSON object, array, string, double, long, boolean, or null. 10 | *

11 | * JsonValue children are a linked list. Iteration of arrays or objects is easily done using a for loop, either with the enhanced 12 | * for loop syntactic sugar or like the example below. This is much more efficient than accessing children by index when there are 13 | * many children.
14 | * 15 | *

  16 |  * JsonValue map = ...;
  17 |  * for (JsonValue entry = map.child; entry != null; entry = entry.next)
  18 |  * 	System.out.println(entry.name + " = " + entry.asString());
  19 |  * 
20 | * 21 | * @author Nathan Sweet */ 22 | public class JsonValue implements Iterable { 23 | private ValueType type; 24 | 25 | /** May be null. */ 26 | private String stringValue; 27 | private double doubleValue; 28 | private long longValue; 29 | 30 | public String name; 31 | /** May be null. */ 32 | public JsonValue child, parent; 33 | /** May be null. When changing this field the parent {@link #size()} may need to be changed. */ 34 | public JsonValue next, prev; 35 | public int size; 36 | 37 | public JsonValue (ValueType type) { 38 | this.type = type; 39 | } 40 | 41 | /** @param value May be null. */ 42 | public JsonValue (@Null String value) { 43 | set(value); 44 | } 45 | 46 | public JsonValue (double value) { 47 | set(value, null); 48 | } 49 | 50 | public JsonValue (long value) { 51 | set(value, null); 52 | } 53 | 54 | public JsonValue (double value, String stringValue) { 55 | set(value, stringValue); 56 | } 57 | 58 | public JsonValue (long value, String stringValue) { 59 | set(value, stringValue); 60 | } 61 | 62 | public JsonValue (boolean value) { 63 | set(value); 64 | } 65 | 66 | /** Returns the child at the specified index. This requires walking the linked list to the specified entry, see 67 | * {@link JsonValue} for how to iterate efficiently. 68 | * @return May be null. */ 69 | public @Null JsonValue get (int index) { 70 | JsonValue current = child; 71 | while (current != null && index > 0) { 72 | index--; 73 | current = current.next; 74 | } 75 | return current; 76 | } 77 | 78 | /** Returns the child with the specified name. 79 | * @return May be null. */ 80 | public @Null JsonValue get (String name) { 81 | JsonValue current = child; 82 | while (current != null && (current.name == null || !current.name.equalsIgnoreCase(name))) 83 | current = current.next; 84 | return current; 85 | } 86 | 87 | /** Returns true if a child with the specified name exists. */ 88 | public boolean has (String name) { 89 | return get(name) != null; 90 | } 91 | 92 | /** Returns the child at the specified index. This requires walking the linked list to the specified entry, see 93 | * {@link JsonValue} for how to iterate efficiently. 94 | * @throws IllegalArgumentException if the child was not found. */ 95 | public JsonValue require (int index) { 96 | JsonValue current = child; 97 | while (current != null && index > 0) { 98 | index--; 99 | current = current.next; 100 | } 101 | if (current == null) throw new IllegalArgumentException("Child not found with index: " + index); 102 | return current; 103 | } 104 | 105 | /** Returns the child with the specified name. 106 | * @throws IllegalArgumentException if the child was not found. */ 107 | public JsonValue require (String name) { 108 | JsonValue current = child; 109 | while (current != null && (current.name == null || !current.name.equalsIgnoreCase(name))) 110 | current = current.next; 111 | if (current == null) throw new IllegalArgumentException("Child not found with name: " + name); 112 | return current; 113 | } 114 | 115 | /** Removes the child with the specified index. This requires walking the linked list to the specified entry, see 116 | * {@link JsonValue} for how to iterate efficiently. 117 | * @return May be null. */ 118 | public @Null JsonValue remove (int index) { 119 | JsonValue child = get(index); 120 | if (child == null) return null; 121 | if (child.prev == null) { 122 | this.child = child.next; 123 | if (this.child != null) this.child.prev = null; 124 | } else { 125 | child.prev.next = child.next; 126 | if (child.next != null) child.next.prev = child.prev; 127 | } 128 | size--; 129 | return child; 130 | } 131 | 132 | /** Removes the child with the specified name. 133 | * @return May be null. */ 134 | public @Null JsonValue remove (String name) { 135 | JsonValue child = get(name); 136 | if (child == null) return null; 137 | if (child.prev == null) { 138 | this.child = child.next; 139 | if (this.child != null) this.child.prev = null; 140 | } else { 141 | child.prev.next = child.next; 142 | if (child.next != null) child.next.prev = child.prev; 143 | } 144 | size--; 145 | return child; 146 | } 147 | 148 | /** Removes this value from its parent. */ 149 | public void remove () { 150 | if (parent == null) throw new IllegalStateException(); 151 | if (prev == null) { 152 | parent.child = next; 153 | if (parent.child != null) parent.child.prev = null; 154 | } else { 155 | prev.next = next; 156 | if (next != null) next.prev = prev; 157 | } 158 | parent.size--; 159 | } 160 | 161 | /** Returns true if there are one or more children in the array or object. */ 162 | public boolean notEmpty () { 163 | return size > 0; 164 | } 165 | 166 | /** Returns true if there are not children in the array or object. */ 167 | public boolean isEmpty () { 168 | return size == 0; 169 | } 170 | 171 | /** @deprecated Use {@link #size} instead. Returns this number of children in the array or object. */ 172 | @Deprecated 173 | public int size () { 174 | return size; 175 | } 176 | 177 | /** Returns this value as a string. 178 | * @return May be null if this value is null. 179 | * @throws IllegalStateException if this an array or object. */ 180 | public @Null String asString () { 181 | switch (type) { 182 | case stringValue: 183 | return stringValue; 184 | case doubleValue: 185 | return stringValue != null ? stringValue : Double.toString(doubleValue); 186 | case longValue: 187 | return stringValue != null ? stringValue : Long.toString(longValue); 188 | case booleanValue: 189 | return longValue != 0 ? "true" : "false"; 190 | case nullValue: 191 | return null; 192 | } 193 | throw new IllegalStateException("Value cannot be converted to string: " + type); 194 | } 195 | 196 | /** Returns this value as a float. 197 | * @throws IllegalStateException if this an array or object. */ 198 | public float asFloat () { 199 | switch (type) { 200 | case stringValue: 201 | return Float.parseFloat(stringValue); 202 | case doubleValue: 203 | return (float)doubleValue; 204 | case longValue: 205 | return longValue; 206 | case booleanValue: 207 | return longValue != 0 ? 1 : 0; 208 | } 209 | throw new IllegalStateException("Value cannot be converted to float: " + type); 210 | } 211 | 212 | /** Returns this value as a double. 213 | * @throws IllegalStateException if this an array or object. */ 214 | public double asDouble () { 215 | switch (type) { 216 | case stringValue: 217 | return Double.parseDouble(stringValue); 218 | case doubleValue: 219 | return doubleValue; 220 | case longValue: 221 | return longValue; 222 | case booleanValue: 223 | return longValue != 0 ? 1 : 0; 224 | } 225 | throw new IllegalStateException("Value cannot be converted to double: " + type); 226 | } 227 | 228 | /** Returns this value as a long. 229 | * @throws IllegalStateException if this an array or object. */ 230 | public long asLong () { 231 | switch (type) { 232 | case stringValue: 233 | return Long.parseLong(stringValue); 234 | case doubleValue: 235 | return (long)doubleValue; 236 | case longValue: 237 | return longValue; 238 | case booleanValue: 239 | return longValue != 0 ? 1 : 0; 240 | } 241 | throw new IllegalStateException("Value cannot be converted to long: " + type); 242 | } 243 | 244 | /** Returns this value as an int. 245 | * @throws IllegalStateException if this an array or object. */ 246 | public int asInt () { 247 | switch (type) { 248 | case stringValue: 249 | return Integer.parseInt(stringValue); 250 | case doubleValue: 251 | return (int)doubleValue; 252 | case longValue: 253 | return (int)longValue; 254 | case booleanValue: 255 | return longValue != 0 ? 1 : 0; 256 | } 257 | throw new IllegalStateException("Value cannot be converted to int: " + type); 258 | } 259 | 260 | /** Returns this value as a boolean. 261 | * @throws IllegalStateException if this an array or object. */ 262 | public boolean asBoolean () { 263 | switch (type) { 264 | case stringValue: 265 | return stringValue.equalsIgnoreCase("true"); 266 | case doubleValue: 267 | return doubleValue != 0; 268 | case longValue: 269 | return longValue != 0; 270 | case booleanValue: 271 | return longValue != 0; 272 | } 273 | throw new IllegalStateException("Value cannot be converted to boolean: " + type); 274 | } 275 | 276 | /** Returns this value as a byte. 277 | * @throws IllegalStateException if this an array or object. */ 278 | public byte asByte () { 279 | switch (type) { 280 | case stringValue: 281 | return Byte.parseByte(stringValue); 282 | case doubleValue: 283 | return (byte)doubleValue; 284 | case longValue: 285 | return (byte)longValue; 286 | case booleanValue: 287 | return longValue != 0 ? (byte)1 : 0; 288 | } 289 | throw new IllegalStateException("Value cannot be converted to byte: " + type); 290 | } 291 | 292 | /** Returns this value as a short. 293 | * @throws IllegalStateException if this an array or object. */ 294 | public short asShort () { 295 | switch (type) { 296 | case stringValue: 297 | return Short.parseShort(stringValue); 298 | case doubleValue: 299 | return (short)doubleValue; 300 | case longValue: 301 | return (short)longValue; 302 | case booleanValue: 303 | return longValue != 0 ? (short)1 : 0; 304 | } 305 | throw new IllegalStateException("Value cannot be converted to short: " + type); 306 | } 307 | 308 | /** Returns this value as a char. 309 | * @throws IllegalStateException if this an array or object. */ 310 | public char asChar () { 311 | switch (type) { 312 | case stringValue: 313 | return stringValue.length() == 0 ? 0 : stringValue.charAt(0); 314 | case doubleValue: 315 | return (char)doubleValue; 316 | case longValue: 317 | return (char)longValue; 318 | case booleanValue: 319 | return longValue != 0 ? (char)1 : 0; 320 | } 321 | throw new IllegalStateException("Value cannot be converted to char: " + type); 322 | } 323 | 324 | /** Returns the children of this value as a newly allocated String array. 325 | * @throws IllegalStateException if this is not an array. */ 326 | public String[] asStringArray () { 327 | if (type != ValueType.array) throw new IllegalStateException("Value is not an array: " + type); 328 | String[] array = new String[size]; 329 | int i = 0; 330 | for (JsonValue value = child; value != null; value = value.next, i++) { 331 | String v; 332 | switch (value.type) { 333 | case stringValue: 334 | v = value.stringValue; 335 | break; 336 | case doubleValue: 337 | v = stringValue != null ? stringValue : Double.toString(value.doubleValue); 338 | break; 339 | case longValue: 340 | v = stringValue != null ? stringValue : Long.toString(value.longValue); 341 | break; 342 | case booleanValue: 343 | v = value.longValue != 0 ? "true" : "false"; 344 | break; 345 | case nullValue: 346 | v = null; 347 | break; 348 | default: 349 | throw new IllegalStateException("Value cannot be converted to string: " + value.type); 350 | } 351 | array[i] = v; 352 | } 353 | return array; 354 | } 355 | 356 | /** Returns the children of this value as a newly allocated float array. 357 | * @throws IllegalStateException if this is not an array. */ 358 | public float[] asFloatArray () { 359 | if (type != ValueType.array) throw new IllegalStateException("Value is not an array: " + type); 360 | float[] array = new float[size]; 361 | int i = 0; 362 | for (JsonValue value = child; value != null; value = value.next, i++) { 363 | float v; 364 | switch (value.type) { 365 | case stringValue: 366 | v = Float.parseFloat(value.stringValue); 367 | break; 368 | case doubleValue: 369 | v = (float)value.doubleValue; 370 | break; 371 | case longValue: 372 | v = value.longValue; 373 | break; 374 | case booleanValue: 375 | v = value.longValue != 0 ? 1 : 0; 376 | break; 377 | default: 378 | throw new IllegalStateException("Value cannot be converted to float: " + value.type); 379 | } 380 | array[i] = v; 381 | } 382 | return array; 383 | } 384 | 385 | /** Returns the children of this value as a newly allocated double array. 386 | * @throws IllegalStateException if this is not an array. */ 387 | public double[] asDoubleArray () { 388 | if (type != ValueType.array) throw new IllegalStateException("Value is not an array: " + type); 389 | double[] array = new double[size]; 390 | int i = 0; 391 | for (JsonValue value = child; value != null; value = value.next, i++) { 392 | double v; 393 | switch (value.type) { 394 | case stringValue: 395 | v = Double.parseDouble(value.stringValue); 396 | break; 397 | case doubleValue: 398 | v = value.doubleValue; 399 | break; 400 | case longValue: 401 | v = value.longValue; 402 | break; 403 | case booleanValue: 404 | v = value.longValue != 0 ? 1 : 0; 405 | break; 406 | default: 407 | throw new IllegalStateException("Value cannot be converted to double: " + value.type); 408 | } 409 | array[i] = v; 410 | } 411 | return array; 412 | } 413 | 414 | /** Returns the children of this value as a newly allocated long array. 415 | * @throws IllegalStateException if this is not an array. */ 416 | public long[] asLongArray () { 417 | if (type != ValueType.array) throw new IllegalStateException("Value is not an array: " + type); 418 | long[] array = new long[size]; 419 | int i = 0; 420 | for (JsonValue value = child; value != null; value = value.next, i++) { 421 | long v; 422 | switch (value.type) { 423 | case stringValue: 424 | v = Long.parseLong(value.stringValue); 425 | break; 426 | case doubleValue: 427 | v = (long)value.doubleValue; 428 | break; 429 | case longValue: 430 | v = value.longValue; 431 | break; 432 | case booleanValue: 433 | v = value.longValue != 0 ? 1 : 0; 434 | break; 435 | default: 436 | throw new IllegalStateException("Value cannot be converted to long: " + value.type); 437 | } 438 | array[i] = v; 439 | } 440 | return array; 441 | } 442 | 443 | /** Returns the children of this value as a newly allocated int array. 444 | * @throws IllegalStateException if this is not an array. */ 445 | public int[] asIntArray () { 446 | if (type != ValueType.array) throw new IllegalStateException("Value is not an array: " + type); 447 | int[] array = new int[size]; 448 | int i = 0; 449 | for (JsonValue value = child; value != null; value = value.next, i++) { 450 | int v; 451 | switch (value.type) { 452 | case stringValue: 453 | v = Integer.parseInt(value.stringValue); 454 | break; 455 | case doubleValue: 456 | v = (int)value.doubleValue; 457 | break; 458 | case longValue: 459 | v = (int)value.longValue; 460 | break; 461 | case booleanValue: 462 | v = value.longValue != 0 ? 1 : 0; 463 | break; 464 | default: 465 | throw new IllegalStateException("Value cannot be converted to int: " + value.type); 466 | } 467 | array[i] = v; 468 | } 469 | return array; 470 | } 471 | 472 | /** Returns the children of this value as a newly allocated boolean array. 473 | * @throws IllegalStateException if this is not an array. */ 474 | public boolean[] asBooleanArray () { 475 | if (type != ValueType.array) throw new IllegalStateException("Value is not an array: " + type); 476 | boolean[] array = new boolean[size]; 477 | int i = 0; 478 | for (JsonValue value = child; value != null; value = value.next, i++) { 479 | boolean v; 480 | switch (value.type) { 481 | case stringValue: 482 | v = Boolean.parseBoolean(value.stringValue); 483 | break; 484 | case doubleValue: 485 | v = value.doubleValue == 0; 486 | break; 487 | case longValue: 488 | v = value.longValue == 0; 489 | break; 490 | case booleanValue: 491 | v = value.longValue != 0; 492 | break; 493 | default: 494 | throw new IllegalStateException("Value cannot be converted to boolean: " + value.type); 495 | } 496 | array[i] = v; 497 | } 498 | return array; 499 | } 500 | 501 | /** Returns the children of this value as a newly allocated byte array. 502 | * @throws IllegalStateException if this is not an array. */ 503 | public byte[] asByteArray () { 504 | if (type != ValueType.array) throw new IllegalStateException("Value is not an array: " + type); 505 | byte[] array = new byte[size]; 506 | int i = 0; 507 | for (JsonValue value = child; value != null; value = value.next, i++) { 508 | byte v; 509 | switch (value.type) { 510 | case stringValue: 511 | v = Byte.parseByte(value.stringValue); 512 | break; 513 | case doubleValue: 514 | v = (byte)value.doubleValue; 515 | break; 516 | case longValue: 517 | v = (byte)value.longValue; 518 | break; 519 | case booleanValue: 520 | v = value.longValue != 0 ? (byte)1 : 0; 521 | break; 522 | default: 523 | throw new IllegalStateException("Value cannot be converted to byte: " + value.type); 524 | } 525 | array[i] = v; 526 | } 527 | return array; 528 | } 529 | 530 | /** Returns the children of this value as a newly allocated short array. 531 | * @throws IllegalStateException if this is not an array. */ 532 | public short[] asShortArray () { 533 | if (type != ValueType.array) throw new IllegalStateException("Value is not an array: " + type); 534 | short[] array = new short[size]; 535 | int i = 0; 536 | for (JsonValue value = child; value != null; value = value.next, i++) { 537 | short v; 538 | switch (value.type) { 539 | case stringValue: 540 | v = Short.parseShort(value.stringValue); 541 | break; 542 | case doubleValue: 543 | v = (short)value.doubleValue; 544 | break; 545 | case longValue: 546 | v = (short)value.longValue; 547 | break; 548 | case booleanValue: 549 | v = value.longValue != 0 ? (short)1 : 0; 550 | break; 551 | default: 552 | throw new IllegalStateException("Value cannot be converted to short: " + value.type); 553 | } 554 | array[i] = v; 555 | } 556 | return array; 557 | } 558 | 559 | /** Returns the children of this value as a newly allocated char array. 560 | * @throws IllegalStateException if this is not an array. */ 561 | public char[] asCharArray () { 562 | if (type != ValueType.array) throw new IllegalStateException("Value is not an array: " + type); 563 | char[] array = new char[size]; 564 | int i = 0; 565 | for (JsonValue value = child; value != null; value = value.next, i++) { 566 | char v; 567 | switch (value.type) { 568 | case stringValue: 569 | v = value.stringValue.length() == 0 ? 0 : value.stringValue.charAt(0); 570 | break; 571 | case doubleValue: 572 | v = (char)value.doubleValue; 573 | break; 574 | case longValue: 575 | v = (char)value.longValue; 576 | break; 577 | case booleanValue: 578 | v = value.longValue != 0 ? (char)1 : 0; 579 | break; 580 | default: 581 | throw new IllegalStateException("Value cannot be converted to char: " + value.type); 582 | } 583 | array[i] = v; 584 | } 585 | return array; 586 | } 587 | 588 | /** Returns true if a child with the specified name exists and has a child. */ 589 | public boolean hasChild (String name) { 590 | return getChild(name) != null; 591 | } 592 | 593 | /** Finds the child with the specified name and returns its first child. 594 | * @return May be null. */ 595 | public @Null JsonValue getChild (String name) { 596 | JsonValue child = get(name); 597 | return child == null ? null : child.child; 598 | } 599 | 600 | /** Finds the child with the specified name and returns it as a string. Returns defaultValue if not found. 601 | * @param defaultValue May be null. */ 602 | public String getString (String name, @Null String defaultValue) { 603 | JsonValue child = get(name); 604 | return (child == null || !child.isValue() || child.isNull()) ? defaultValue : child.asString(); 605 | } 606 | 607 | /** Finds the child with the specified name and returns it as a float. Returns defaultValue if not found. */ 608 | public float getFloat (String name, float defaultValue) { 609 | JsonValue child = get(name); 610 | return (child == null || !child.isValue() || child.isNull()) ? defaultValue : child.asFloat(); 611 | } 612 | 613 | /** Finds the child with the specified name and returns it as a double. Returns defaultValue if not found. */ 614 | public double getDouble (String name, double defaultValue) { 615 | JsonValue child = get(name); 616 | return (child == null || !child.isValue() || child.isNull()) ? defaultValue : child.asDouble(); 617 | } 618 | 619 | /** Finds the child with the specified name and returns it as a long. Returns defaultValue if not found. */ 620 | public long getLong (String name, long defaultValue) { 621 | JsonValue child = get(name); 622 | return (child == null || !child.isValue() || child.isNull()) ? defaultValue : child.asLong(); 623 | } 624 | 625 | /** Finds the child with the specified name and returns it as an int. Returns defaultValue if not found. */ 626 | public int getInt (String name, int defaultValue) { 627 | JsonValue child = get(name); 628 | return (child == null || !child.isValue() || child.isNull()) ? defaultValue : child.asInt(); 629 | } 630 | 631 | /** Finds the child with the specified name and returns it as a boolean. Returns defaultValue if not found. */ 632 | public boolean getBoolean (String name, boolean defaultValue) { 633 | JsonValue child = get(name); 634 | return (child == null || !child.isValue() || child.isNull()) ? defaultValue : child.asBoolean(); 635 | } 636 | 637 | /** Finds the child with the specified name and returns it as a byte. Returns defaultValue if not found. */ 638 | public byte getByte (String name, byte defaultValue) { 639 | JsonValue child = get(name); 640 | return (child == null || !child.isValue() || child.isNull()) ? defaultValue : child.asByte(); 641 | } 642 | 643 | /** Finds the child with the specified name and returns it as a short. Returns defaultValue if not found. */ 644 | public short getShort (String name, short defaultValue) { 645 | JsonValue child = get(name); 646 | return (child == null || !child.isValue() || child.isNull()) ? defaultValue : child.asShort(); 647 | } 648 | 649 | /** Finds the child with the specified name and returns it as a char. Returns defaultValue if not found. */ 650 | public char getChar (String name, char defaultValue) { 651 | JsonValue child = get(name); 652 | return (child == null || !child.isValue() || child.isNull()) ? defaultValue : child.asChar(); 653 | } 654 | 655 | /** Finds the child with the specified name and returns it as a string. 656 | * @throws IllegalArgumentException if the child was not found. */ 657 | public String getString (String name) { 658 | JsonValue child = get(name); 659 | if (child == null) throw new IllegalArgumentException("Named value not found: " + name); 660 | return child.asString(); 661 | } 662 | 663 | /** Finds the child with the specified name and returns it as a float. 664 | * @throws IllegalArgumentException if the child was not found. */ 665 | public float getFloat (String name) { 666 | JsonValue child = get(name); 667 | if (child == null) throw new IllegalArgumentException("Named value not found: " + name); 668 | return child.asFloat(); 669 | } 670 | 671 | /** Finds the child with the specified name and returns it as a double. 672 | * @throws IllegalArgumentException if the child was not found. */ 673 | public double getDouble (String name) { 674 | JsonValue child = get(name); 675 | if (child == null) throw new IllegalArgumentException("Named value not found: " + name); 676 | return child.asDouble(); 677 | } 678 | 679 | /** Finds the child with the specified name and returns it as a long. 680 | * @throws IllegalArgumentException if the child was not found. */ 681 | public long getLong (String name) { 682 | JsonValue child = get(name); 683 | if (child == null) throw new IllegalArgumentException("Named value not found: " + name); 684 | return child.asLong(); 685 | } 686 | 687 | /** Finds the child with the specified name and returns it as an int. 688 | * @throws IllegalArgumentException if the child was not found. */ 689 | public int getInt (String name) { 690 | JsonValue child = get(name); 691 | if (child == null) throw new IllegalArgumentException("Named value not found: " + name); 692 | return child.asInt(); 693 | } 694 | 695 | /** Finds the child with the specified name and returns it as a boolean. 696 | * @throws IllegalArgumentException if the child was not found. */ 697 | public boolean getBoolean (String name) { 698 | JsonValue child = get(name); 699 | if (child == null) throw new IllegalArgumentException("Named value not found: " + name); 700 | return child.asBoolean(); 701 | } 702 | 703 | /** Finds the child with the specified name and returns it as a byte. 704 | * @throws IllegalArgumentException if the child was not found. */ 705 | public byte getByte (String name) { 706 | JsonValue child = get(name); 707 | if (child == null) throw new IllegalArgumentException("Named value not found: " + name); 708 | return child.asByte(); 709 | } 710 | 711 | /** Finds the child with the specified name and returns it as a short. 712 | * @throws IllegalArgumentException if the child was not found. */ 713 | public short getShort (String name) { 714 | JsonValue child = get(name); 715 | if (child == null) throw new IllegalArgumentException("Named value not found: " + name); 716 | return child.asShort(); 717 | } 718 | 719 | /** Finds the child with the specified name and returns it as a char. 720 | * @throws IllegalArgumentException if the child was not found. */ 721 | public char getChar (String name) { 722 | JsonValue child = get(name); 723 | if (child == null) throw new IllegalArgumentException("Named value not found: " + name); 724 | return child.asChar(); 725 | } 726 | 727 | /** Finds the child with the specified index and returns it as a string. 728 | * @throws IllegalArgumentException if the child was not found. */ 729 | public String getString (int index) { 730 | JsonValue child = get(index); 731 | if (child == null) throw new IllegalArgumentException("Indexed value not found: " + name); 732 | return child.asString(); 733 | } 734 | 735 | /** Finds the child with the specified index and returns it as a float. 736 | * @throws IllegalArgumentException if the child was not found. */ 737 | public float getFloat (int index) { 738 | JsonValue child = get(index); 739 | if (child == null) throw new IllegalArgumentException("Indexed value not found: " + name); 740 | return child.asFloat(); 741 | } 742 | 743 | /** Finds the child with the specified index and returns it as a double. 744 | * @throws IllegalArgumentException if the child was not found. */ 745 | public double getDouble (int index) { 746 | JsonValue child = get(index); 747 | if (child == null) throw new IllegalArgumentException("Indexed value not found: " + name); 748 | return child.asDouble(); 749 | } 750 | 751 | /** Finds the child with the specified index and returns it as a long. 752 | * @throws IllegalArgumentException if the child was not found. */ 753 | public long getLong (int index) { 754 | JsonValue child = get(index); 755 | if (child == null) throw new IllegalArgumentException("Indexed value not found: " + name); 756 | return child.asLong(); 757 | } 758 | 759 | /** Finds the child with the specified index and returns it as an int. 760 | * @throws IllegalArgumentException if the child was not found. */ 761 | public int getInt (int index) { 762 | JsonValue child = get(index); 763 | if (child == null) throw new IllegalArgumentException("Indexed value not found: " + name); 764 | return child.asInt(); 765 | } 766 | 767 | /** Finds the child with the specified index and returns it as a boolean. 768 | * @throws IllegalArgumentException if the child was not found. */ 769 | public boolean getBoolean (int index) { 770 | JsonValue child = get(index); 771 | if (child == null) throw new IllegalArgumentException("Indexed value not found: " + name); 772 | return child.asBoolean(); 773 | } 774 | 775 | /** Finds the child with the specified index and returns it as a byte. 776 | * @throws IllegalArgumentException if the child was not found. */ 777 | public byte getByte (int index) { 778 | JsonValue child = get(index); 779 | if (child == null) throw new IllegalArgumentException("Indexed value not found: " + name); 780 | return child.asByte(); 781 | } 782 | 783 | /** Finds the child with the specified index and returns it as a short. 784 | * @throws IllegalArgumentException if the child was not found. */ 785 | public short getShort (int index) { 786 | JsonValue child = get(index); 787 | if (child == null) throw new IllegalArgumentException("Indexed value not found: " + name); 788 | return child.asShort(); 789 | } 790 | 791 | /** Finds the child with the specified index and returns it as a char. 792 | * @throws IllegalArgumentException if the child was not found. */ 793 | public char getChar (int index) { 794 | JsonValue child = get(index); 795 | if (child == null) throw new IllegalArgumentException("Indexed value not found: " + name); 796 | return child.asChar(); 797 | } 798 | 799 | public ValueType type () { 800 | return type; 801 | } 802 | 803 | public void setType (ValueType type) { 804 | if (type == null) throw new IllegalArgumentException("type cannot be null."); 805 | this.type = type; 806 | } 807 | 808 | public boolean isArray () { 809 | return type == ValueType.array; 810 | } 811 | 812 | public boolean isObject () { 813 | return type == ValueType.object; 814 | } 815 | 816 | public boolean isString () { 817 | return type == ValueType.stringValue; 818 | } 819 | 820 | /** Returns true if this is a double or long value. */ 821 | public boolean isNumber () { 822 | return type == ValueType.doubleValue || type == ValueType.longValue; 823 | } 824 | 825 | public boolean isDouble () { 826 | return type == ValueType.doubleValue; 827 | } 828 | 829 | public boolean isLong () { 830 | return type == ValueType.longValue; 831 | } 832 | 833 | public boolean isBoolean () { 834 | return type == ValueType.booleanValue; 835 | } 836 | 837 | public boolean isNull () { 838 | return type == ValueType.nullValue; 839 | } 840 | 841 | /** Returns true if this is not an array or object. */ 842 | public boolean isValue () { 843 | switch (type) { 844 | case stringValue: 845 | case doubleValue: 846 | case longValue: 847 | case booleanValue: 848 | case nullValue: 849 | return true; 850 | } 851 | return false; 852 | } 853 | 854 | /** Returns the name for this object value. 855 | * @return May be null. */ 856 | public @Null String name () { 857 | return name; 858 | } 859 | 860 | /** @param name May be null. */ 861 | public void setName (@Null String name) { 862 | this.name = name; 863 | } 864 | 865 | /** Returns the parent for this value. 866 | * @return May be null. */ 867 | public @Null JsonValue parent () { 868 | return parent; 869 | } 870 | 871 | /** Returns the first child for this object or array. 872 | * @return May be null. */ 873 | public @Null JsonValue child () { 874 | return child; 875 | } 876 | 877 | /** Sets the name of the specified value and adds it after the last child. */ 878 | public void addChild (String name, JsonValue value) { 879 | if (name == null) throw new IllegalArgumentException("name cannot be null."); 880 | value.name = name; 881 | addChild(value); 882 | } 883 | 884 | /** Adds the specified value after the last child. */ 885 | public void addChild (JsonValue value) { 886 | value.parent = this; 887 | size++; 888 | JsonValue current = child; 889 | if (current == null) 890 | child = value; 891 | else { 892 | while (true) { 893 | if (current.next == null) { 894 | current.next = value; 895 | value.prev = current; 896 | return; 897 | } 898 | current = current.next; 899 | } 900 | } 901 | } 902 | 903 | /** Returns the next sibling of this value. 904 | * @return May be null. */ 905 | public @Null JsonValue next () { 906 | return next; 907 | } 908 | 909 | /** Sets the next sibling of this value. Does not change the parent {@link #size()}. 910 | * @param next May be null. */ 911 | public void setNext (@Null JsonValue next) { 912 | this.next = next; 913 | } 914 | 915 | /** Returns the previous sibling of this value. 916 | * @return May be null. */ 917 | public @Null JsonValue prev () { 918 | return prev; 919 | } 920 | 921 | /** Sets the next sibling of this value. Does not change the parent {@link #size()}. 922 | * @param prev May be null. */ 923 | public void setPrev (@Null JsonValue prev) { 924 | this.prev = prev; 925 | } 926 | 927 | /** @param value May be null. */ 928 | public void set (@Null String value) { 929 | stringValue = value; 930 | type = value == null ? ValueType.nullValue : ValueType.stringValue; 931 | } 932 | 933 | /** @param stringValue May be null if the string representation is the string value of the double (eg, no leading zeros). */ 934 | public void set (double value, @Null String stringValue) { 935 | doubleValue = value; 936 | longValue = (long)value; 937 | this.stringValue = stringValue; 938 | type = ValueType.doubleValue; 939 | } 940 | 941 | /** @param stringValue May be null if the string representation is the string value of the long (eg, no leading zeros). */ 942 | public void set (long value, @Null String stringValue) { 943 | longValue = value; 944 | doubleValue = value; 945 | this.stringValue = stringValue; 946 | type = ValueType.longValue; 947 | } 948 | 949 | public void set (boolean value) { 950 | longValue = value ? 1 : 0; 951 | type = ValueType.booleanValue; 952 | } 953 | 954 | public String toJson (OutputType outputType) { 955 | if (isValue()) return asString(); 956 | StringBuilder buffer = new StringBuilder(512); 957 | json(this, buffer, outputType); 958 | return buffer.toString(); 959 | } 960 | 961 | private void json (JsonValue object, StringBuilder buffer, OutputType outputType) { 962 | if (object.isObject()) { 963 | if (object.child == null) 964 | buffer.append("{}"); 965 | else { 966 | int start = buffer.length(); 967 | while (true) { 968 | buffer.append('{'); 969 | int i = 0; 970 | for (JsonValue child = object.child; child != null; child = child.next) { 971 | buffer.append(outputType.quoteName(child.name)); 972 | buffer.append(':'); 973 | json(child, buffer, outputType); 974 | if (child.next != null) buffer.append(','); 975 | } 976 | break; 977 | } 978 | buffer.append('}'); 979 | } 980 | } else if (object.isArray()) { 981 | if (object.child == null) 982 | buffer.append("[]"); 983 | else { 984 | int start = buffer.length(); 985 | while (true) { 986 | buffer.append('['); 987 | for (JsonValue child = object.child; child != null; child = child.next) { 988 | json(child, buffer, outputType); 989 | if (child.next != null) buffer.append(','); 990 | } 991 | break; 992 | } 993 | buffer.append(']'); 994 | } 995 | } else if (object.isString()) { 996 | buffer.append(outputType.quoteValue(object.asString())); 997 | } else if (object.isDouble()) { 998 | double doubleValue = object.asDouble(); 999 | long longValue = object.asLong(); 1000 | buffer.append(doubleValue == longValue ? longValue : doubleValue); 1001 | } else if (object.isLong()) { 1002 | buffer.append(object.asLong()); 1003 | } else if (object.isBoolean()) { 1004 | buffer.append(object.asBoolean()); 1005 | } else if (object.isNull()) { 1006 | buffer.append("null"); 1007 | } else 1008 | throw new JsonException("Unknown object type: " + object); 1009 | } 1010 | 1011 | public JsonIterator iterator () { 1012 | return new JsonIterator(); 1013 | } 1014 | 1015 | public String toString () { 1016 | if (isValue()) return name == null ? asString() : name + ": " + asString(); 1017 | return (name == null ? "" : name + ": ") + prettyPrint(OutputType.minimal, 0); 1018 | } 1019 | 1020 | /** Returns a human readable string representing the path from the root of the JSON object graph to this value. */ 1021 | public String trace () { 1022 | if (parent == null) { 1023 | if (type == ValueType.array) return "[]"; 1024 | if (type == ValueType.object) return "{}"; 1025 | return ""; 1026 | } 1027 | String trace; 1028 | if (parent.type == ValueType.array) { 1029 | trace = "[]"; 1030 | int i = 0; 1031 | for (JsonValue child = parent.child; child != null; child = child.next, i++) { 1032 | if (child == this) { 1033 | trace = "[" + i + "]"; 1034 | break; 1035 | } 1036 | } 1037 | } else if (name.indexOf('.') != -1) 1038 | trace = ".\"" + name.replace("\"", "\\\"") + "\""; 1039 | else 1040 | trace = '.' + name; 1041 | return parent.trace() + trace; 1042 | } 1043 | 1044 | public String prettyPrint (OutputType outputType, int singleLineColumns) { 1045 | PrettyPrintSettings settings = new PrettyPrintSettings(); 1046 | settings.outputType = outputType; 1047 | settings.singleLineColumns = singleLineColumns; 1048 | return prettyPrint(settings); 1049 | } 1050 | 1051 | public String prettyPrint (PrettyPrintSettings settings) { 1052 | StringBuilder buffer = new StringBuilder(512); 1053 | prettyPrint(this, buffer, 0, settings); 1054 | return buffer.toString(); 1055 | } 1056 | 1057 | private void prettyPrint (JsonValue object, StringBuilder buffer, int indent, PrettyPrintSettings settings) { 1058 | OutputType outputType = settings.outputType; 1059 | if (object.isObject()) { 1060 | if (object.child == null) 1061 | buffer.append("{}"); 1062 | else { 1063 | boolean newLines = !isFlat(object); 1064 | int start = buffer.length(); 1065 | outer: 1066 | while (true) { 1067 | buffer.append(newLines ? "{\n" : "{ "); 1068 | int i = 0; 1069 | for (JsonValue child = object.child; child != null; child = child.next) { 1070 | if (newLines) indent(indent, buffer); 1071 | buffer.append(outputType.quoteName(child.name)); 1072 | buffer.append(": "); 1073 | prettyPrint(child, buffer, indent + 1, settings); 1074 | if ((!newLines || outputType != OutputType.minimal) && child.next != null) buffer.append(','); 1075 | buffer.append(newLines ? '\n' : ' '); 1076 | if (!newLines && buffer.length() - start > settings.singleLineColumns) { 1077 | buffer.setLength(start); 1078 | newLines = true; 1079 | continue outer; 1080 | } 1081 | } 1082 | break; 1083 | } 1084 | if (newLines) indent(indent - 1, buffer); 1085 | buffer.append('}'); 1086 | } 1087 | } else if (object.isArray()) { 1088 | if (object.child == null) 1089 | buffer.append("[]"); 1090 | else { 1091 | boolean newLines = !isFlat(object); 1092 | boolean wrap = settings.wrapNumericArrays || !isNumeric(object); 1093 | int start = buffer.length(); 1094 | outer: 1095 | while (true) { 1096 | buffer.append(newLines ? "[\n" : "[ "); 1097 | for (JsonValue child = object.child; child != null; child = child.next) { 1098 | if (newLines) indent(indent, buffer); 1099 | prettyPrint(child, buffer, indent + 1, settings); 1100 | if ((!newLines || outputType != OutputType.minimal) && child.next != null) buffer.append(','); 1101 | buffer.append(newLines ? '\n' : ' '); 1102 | if (wrap && !newLines && buffer.length() - start > settings.singleLineColumns) { 1103 | buffer.setLength(start); 1104 | newLines = true; 1105 | continue outer; 1106 | } 1107 | } 1108 | break; 1109 | } 1110 | if (newLines) indent(indent - 1, buffer); 1111 | buffer.append(']'); 1112 | } 1113 | } else if (object.isString()) { 1114 | buffer.append(outputType.quoteValue(object.asString())); 1115 | } else if (object.isDouble()) { 1116 | double doubleValue = object.asDouble(); 1117 | long longValue = object.asLong(); 1118 | buffer.append(doubleValue == longValue ? longValue : doubleValue); 1119 | } else if (object.isLong()) { 1120 | buffer.append(object.asLong()); 1121 | } else if (object.isBoolean()) { 1122 | buffer.append(object.asBoolean()); 1123 | } else if (object.isNull()) { 1124 | buffer.append("null"); 1125 | } else 1126 | throw new JsonException("Unknown object type: " + object); 1127 | } 1128 | 1129 | /** More efficient than {@link #prettyPrint(PrettyPrintSettings)} but {@link PrettyPrintSettings#singleLineColumns} and 1130 | * {@link PrettyPrintSettings#wrapNumericArrays} are not supported. */ 1131 | public void prettyPrint (OutputType outputType, Writer writer) throws IOException { 1132 | PrettyPrintSettings settings = new PrettyPrintSettings(); 1133 | settings.outputType = outputType; 1134 | prettyPrint(this, writer, 0, settings); 1135 | } 1136 | 1137 | private void prettyPrint (JsonValue object, Writer writer, int indent, PrettyPrintSettings settings) throws IOException { 1138 | OutputType outputType = settings.outputType; 1139 | if (object.isObject()) { 1140 | if (object.child == null) 1141 | writer.append("{}"); 1142 | else { 1143 | boolean newLines = !isFlat(object) || object.size > 6; 1144 | writer.append(newLines ? "{\n" : "{ "); 1145 | int i = 0; 1146 | for (JsonValue child = object.child; child != null; child = child.next) { 1147 | if (newLines) indent(indent, writer); 1148 | writer.append(outputType.quoteName(child.name)); 1149 | writer.append(": "); 1150 | prettyPrint(child, writer, indent + 1, settings); 1151 | if ((!newLines || outputType != OutputType.minimal) && child.next != null) writer.append(','); 1152 | writer.append(newLines ? '\n' : ' '); 1153 | } 1154 | if (newLines) indent(indent - 1, writer); 1155 | writer.append('}'); 1156 | } 1157 | } else if (object.isArray()) { 1158 | if (object.child == null) 1159 | writer.append("[]"); 1160 | else { 1161 | boolean newLines = !isFlat(object); 1162 | writer.append(newLines ? "[\n" : "[ "); 1163 | int i = 0; 1164 | for (JsonValue child = object.child; child != null; child = child.next) { 1165 | if (newLines) indent(indent, writer); 1166 | prettyPrint(child, writer, indent + 1, settings); 1167 | if ((!newLines || outputType != OutputType.minimal) && child.next != null) writer.append(','); 1168 | writer.append(newLines ? '\n' : ' '); 1169 | } 1170 | if (newLines) indent(indent - 1, writer); 1171 | writer.append(']'); 1172 | } 1173 | } else if (object.isString()) { 1174 | writer.append(outputType.quoteValue(object.asString())); 1175 | } else if (object.isDouble()) { 1176 | double doubleValue = object.asDouble(); 1177 | long longValue = object.asLong(); 1178 | writer.append(Double.toString(doubleValue == longValue ? longValue : doubleValue)); 1179 | } else if (object.isLong()) { 1180 | writer.append(Long.toString(object.asLong())); 1181 | } else if (object.isBoolean()) { 1182 | writer.append(Boolean.toString(object.asBoolean())); 1183 | } else if (object.isNull()) { 1184 | writer.append("null"); 1185 | } else 1186 | throw new JsonException("Unknown object type: " + object); 1187 | } 1188 | 1189 | static private boolean isFlat (JsonValue object) { 1190 | for (JsonValue child = object.child; child != null; child = child.next) 1191 | if (child.isObject() || child.isArray()) return false; 1192 | return true; 1193 | } 1194 | 1195 | static private boolean isNumeric (JsonValue object) { 1196 | for (JsonValue child = object.child; child != null; child = child.next) 1197 | if (!child.isNumber()) return false; 1198 | return true; 1199 | } 1200 | 1201 | static private void indent (int count, StringBuilder buffer) { 1202 | for (int i = 0; i < count; i++) 1203 | buffer.append('\t'); 1204 | } 1205 | 1206 | static private void indent (int count, Writer buffer) throws IOException { 1207 | for (int i = 0; i < count; i++) 1208 | buffer.append('\t'); 1209 | } 1210 | 1211 | public class JsonIterator implements Iterator, Iterable { 1212 | JsonValue entry = child; 1213 | JsonValue current; 1214 | 1215 | public boolean hasNext () { 1216 | return entry != null; 1217 | } 1218 | 1219 | public JsonValue next () { 1220 | current = entry; 1221 | if (current == null) throw new NoSuchElementException(); 1222 | entry = current.next; 1223 | return current; 1224 | } 1225 | 1226 | public void remove () { 1227 | if (current.prev == null) { 1228 | child = current.next; 1229 | if (child != null) child.prev = null; 1230 | } else { 1231 | current.prev.next = current.next; 1232 | if (current.next != null) current.next.prev = current.prev; 1233 | } 1234 | size--; 1235 | } 1236 | 1237 | public Iterator iterator () { 1238 | return this; 1239 | } 1240 | } 1241 | 1242 | public enum ValueType { 1243 | object, array, stringValue, doubleValue, longValue, booleanValue, nullValue 1244 | } 1245 | 1246 | static public class PrettyPrintSettings { 1247 | public OutputType outputType; 1248 | 1249 | /** If an object on a single line fits this many columns, it won't wrap. */ 1250 | public int singleLineColumns; 1251 | 1252 | /** Arrays of floats won't wrap. */ 1253 | public boolean wrapNumericArrays; 1254 | } 1255 | } 1256 | -------------------------------------------------------------------------------- /src/com/esotericsoftware/jsonbeans/JsonWriter.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011 See AUTHORS file. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | 17 | package com.esotericsoftware.jsonbeans; 18 | 19 | import java.io.IOException; 20 | import java.io.Writer; 21 | import java.math.BigDecimal; 22 | import java.math.BigInteger; 23 | import java.util.ArrayList; 24 | 25 | /** Builder style API for emitting JSON. 26 | * @author Nathan Sweet */ 27 | public class JsonWriter extends Writer { 28 | final Writer writer; 29 | private final ArrayList stack = new ArrayList(); 30 | private JsonObject current; 31 | private boolean named; 32 | private OutputType outputType = OutputType.json; 33 | private boolean quoteLongValues = false; 34 | 35 | public JsonWriter (Writer writer) { 36 | this.writer = writer; 37 | } 38 | 39 | public Writer getWriter () { 40 | return writer; 41 | } 42 | 43 | /** Sets the type of JSON output. Default is {@link OutputType#minimal}. */ 44 | public void setOutputType (OutputType outputType) { 45 | this.outputType = outputType; 46 | } 47 | 48 | /** When true, quotes long, double, BigInteger, BigDecimal types to prevent truncation in languages like JavaScript and PHP. 49 | * This is not necessary when using libgdx, which handles these types without truncation. Default is false. */ 50 | public void setQuoteLongValues (boolean quoteLongValues) { 51 | this.quoteLongValues = quoteLongValues; 52 | } 53 | 54 | public JsonWriter name (String name) throws IOException { 55 | if (current == null || current.array) throw new IllegalStateException("Current item must be an object."); 56 | if (!current.needsComma) 57 | current.needsComma = true; 58 | else 59 | writer.write(','); 60 | writer.write(outputType.quoteName(name)); 61 | writer.write(':'); 62 | named = true; 63 | return this; 64 | } 65 | 66 | public JsonWriter object () throws IOException { 67 | requireCommaOrName(); 68 | stack.add(current = new JsonObject(false)); 69 | return this; 70 | } 71 | 72 | public JsonWriter array () throws IOException { 73 | requireCommaOrName(); 74 | stack.add(current = new JsonObject(true)); 75 | return this; 76 | } 77 | 78 | public JsonWriter value (Object value) throws IOException { 79 | if (quoteLongValues 80 | && (value instanceof Long || value instanceof Double || value instanceof BigDecimal || value instanceof BigInteger)) { 81 | value = value.toString(); 82 | } else if (value instanceof Number) { 83 | Number number = (Number)value; 84 | long longValue = number.longValue(); 85 | if (number.doubleValue() == longValue) value = longValue; 86 | } 87 | requireCommaOrName(); 88 | writer.write(outputType.quoteValue(value)); 89 | return this; 90 | } 91 | 92 | /** Writes the specified JSON value, without quoting or escaping. */ 93 | public JsonWriter json (String json) throws IOException { 94 | requireCommaOrName(); 95 | writer.write(json); 96 | return this; 97 | } 98 | 99 | private void requireCommaOrName () throws IOException { 100 | if (current == null) return; 101 | if (current.array) { 102 | if (!current.needsComma) 103 | current.needsComma = true; 104 | else 105 | writer.write(','); 106 | } else { 107 | if (!named) throw new IllegalStateException("Name must be set."); 108 | named = false; 109 | } 110 | } 111 | 112 | public JsonWriter object (String name) throws IOException { 113 | return name(name).object(); 114 | } 115 | 116 | public JsonWriter array (String name) throws IOException { 117 | return name(name).array(); 118 | } 119 | 120 | public JsonWriter set (String name, Object value) throws IOException { 121 | return name(name).value(value); 122 | } 123 | 124 | /** Writes the specified JSON value, without quoting or escaping. */ 125 | public JsonWriter json (String name, String json) throws IOException { 126 | return name(name).json(json); 127 | } 128 | 129 | public JsonWriter pop () throws IOException { 130 | if (named) throw new IllegalStateException("Expected an object, array, or value since a name was set."); 131 | stack.remove(stack.size() - 1).close(); 132 | current = stack.size() == 0 ? null : stack.get(stack.size() - 1); 133 | return this; 134 | } 135 | 136 | public void write (char[] cbuf, int off, int len) throws IOException { 137 | writer.write(cbuf, off, len); 138 | } 139 | 140 | public void flush () throws IOException { 141 | writer.flush(); 142 | } 143 | 144 | public void close () throws IOException { 145 | while (stack.size() > 0) 146 | pop(); 147 | writer.close(); 148 | } 149 | 150 | private class JsonObject { 151 | final boolean array; 152 | boolean needsComma; 153 | 154 | JsonObject (boolean array) throws IOException { 155 | this.array = array; 156 | writer.write(array ? '[' : '{'); 157 | } 158 | 159 | void close () throws IOException { 160 | writer.write(array ? ']' : '}'); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/com/esotericsoftware/jsonbeans/Null.java: -------------------------------------------------------------------------------- 1 | 2 | package com.esotericsoftware.jsonbeans; 3 | 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Target; 7 | 8 | /** An element with this annotation claims that the element may have a {@code null} value. Apart from documentation purposes this 9 | * annotation is intended to be used by static analysis tools to validate against probable runtime errors or contract violations. 10 | * @author maltaisn */ 11 | @Documented 12 | @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE}) 13 | public @interface Null { 14 | } 15 | -------------------------------------------------------------------------------- /src/com/esotericsoftware/jsonbeans/ObjectMap.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011 See AUTHORS file. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | 17 | package com.esotericsoftware.jsonbeans; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.Iterator; 22 | import java.util.List; 23 | import java.util.NoSuchElementException; 24 | 25 | /** An unordered map where the keys and values are objects. Null keys are not allowed. No allocation is done except when growing 26 | * the table size. 27 | *

28 | * This class performs fast contains and remove (typically O(1), worst case O(n) but that is rare in practice). Add may be 29 | * slightly slower, depending on hash collisions. Hashcodes are rehashed to reduce collisions and the need to resize. Load factors 30 | * greater than 0.91 greatly increase the chances to resize to the next higher POT size. 31 | *

32 | * Unordered sets and maps are not designed to provide especially fast iteration. 33 | *

34 | * This implementation uses linear probing with the backward shift algorithm for removal. Hashcodes are rehashed using Fibonacci 35 | * hashing, instead of the more common power-of-two mask, to better distribute poor hashCodes (see Malte 37 | * Skarupke's blog post). Linear probing continues to work even when all hashCodes collide, just more slowly. 38 | * @author Nathan Sweet 39 | * @author Tommy Ettinger */ 40 | public class ObjectMap implements Iterable> { 41 | static final Object dummy = new Object(); 42 | 43 | public int size; 44 | 45 | K[] keyTable; 46 | V[] valueTable; 47 | 48 | float loadFactor; 49 | int threshold; 50 | 51 | /** Used by {@link #place(Object)} to bit shift the upper bits of a {@code long} into a usable range (>= 0 and <= 52 | * {@link #mask}). The shift can be negative, which is convenient to match the number of bits in mask: if mask is a 7-bit 53 | * number, a shift of -7 shifts the upper 7 bits into the lowest 7 positions. This class sets the shift > 32 and < 64, 54 | * which if used with an int will still move the upper bits of an int to the lower bits due to Java's implicit modulus on 55 | * shifts. 56 | *

57 | * {@link #mask} can also be used to mask the low bits of a number, which may be faster for some hashcodes, if 58 | * {@link #place(Object)} is overridden. */ 59 | protected int shift; 60 | 61 | /** A bitmask used to confine hashcodes to the size of the table. Must be all 1 bits in its low positions, ie a power of two 62 | * minus 1. If {@link #place(Object)} is overriden, this can be used instead of {@link #shift} to isolate usable bits of a 63 | * hash. */ 64 | protected int mask; 65 | 66 | /** Creates a new map with an initial capacity of 51 and a load factor of 0.8. */ 67 | public ObjectMap () { 68 | this(51, 0.8f); 69 | } 70 | 71 | /** Creates a new map with a load factor of 0.8. 72 | * @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */ 73 | public ObjectMap (int initialCapacity) { 74 | this(initialCapacity, 0.8f); 75 | } 76 | 77 | /** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before 78 | * growing the backing table. 79 | * @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */ 80 | public ObjectMap (int initialCapacity, float loadFactor) { 81 | if (loadFactor <= 0f || loadFactor >= 1f) 82 | throw new IllegalArgumentException("loadFactor must be > 0 and < 1: " + loadFactor); 83 | this.loadFactor = loadFactor; 84 | 85 | int tableSize = tableSize(initialCapacity, loadFactor); 86 | threshold = (int)(tableSize * loadFactor); 87 | mask = tableSize - 1; 88 | shift = Long.numberOfLeadingZeros(mask); 89 | 90 | keyTable = (K[])new Object[tableSize]; 91 | valueTable = (V[])new Object[tableSize]; 92 | } 93 | 94 | /** Creates a new map identical to the specified map. */ 95 | public ObjectMap (ObjectMap map) { 96 | this((int)(map.keyTable.length * map.loadFactor), map.loadFactor); 97 | System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length); 98 | System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length); 99 | size = map.size; 100 | } 101 | 102 | /** Returns an index >= 0 and <= {@link #mask} for the specified {@code item}. 103 | *

104 | * The default implementation uses Fibonacci hashing on the item's {@link Object#hashCode()}: the hashcode is multiplied by a 105 | * long constant (2 to the 64th, divided by the golden ratio) then the uppermost bits are shifted into the lowest positions to 106 | * obtain an index in the desired range. Multiplication by a long may be slower than int (eg on GWT) but greatly improves 107 | * rehashing, allowing even very poor hashcodes, such as those that only differ in their upper bits, to be used without high 108 | * collision rates. Fibonacci hashing has increased collision rates when all or most hashcodes are multiples of larger 109 | * Fibonacci numbers (see Malte 111 | * Skarupke's blog post). 112 | *

113 | * This method can be overriden to customizing hashing. This may be useful eg in the unlikely event that most hashcodes are 114 | * Fibonacci numbers, if keys provide poor or incorrect hashcodes, or to simplify hashing if keys provide high quality 115 | * hashcodes and don't need Fibonacci hashing: {@code return item.hashCode() & mask;} */ 116 | protected int place (K item) { 117 | return (int)(item.hashCode() * 0x9E3779B97F4A7C15L >>> shift); 118 | } 119 | 120 | /** Returns the index of the key if already present, else -(index + 1) for the next empty index. This can be overridden in this 121 | * pacakge to compare for equality differently than {@link Object#equals(Object)}. */ 122 | int locateKey (K key) { 123 | if (key == null) throw new IllegalArgumentException("key cannot be null."); 124 | K[] keyTable = this.keyTable; 125 | for (int i = place(key);; i = i + 1 & mask) { 126 | K other = keyTable[i]; 127 | if (other == null) return -(i + 1); // Empty space is available. 128 | if (other.equals(key)) return i; // Same key was found. 129 | } 130 | } 131 | 132 | /** Returns the old value associated with the specified key, or null. */ 133 | @Null 134 | public V put (K key, @Null V value) { 135 | int i = locateKey(key); 136 | if (i >= 0) { // Existing key was found. 137 | V oldValue = valueTable[i]; 138 | valueTable[i] = value; 139 | return oldValue; 140 | } 141 | i = -(i + 1); // Empty space was found. 142 | keyTable[i] = key; 143 | valueTable[i] = value; 144 | if (++size >= threshold) resize(keyTable.length << 1); 145 | return null; 146 | } 147 | 148 | public void putAll (ObjectMap map) { 149 | ensureCapacity(map.size); 150 | K[] keyTable = map.keyTable; 151 | V[] valueTable = map.valueTable; 152 | K key; 153 | for (int i = 0, n = keyTable.length; i < n; i++) { 154 | key = keyTable[i]; 155 | if (key != null) put(key, valueTable[i]); 156 | } 157 | } 158 | 159 | /** Skips checks for existing keys, doesn't increment size. */ 160 | private void putResize (K key, @Null V value) { 161 | K[] keyTable = this.keyTable; 162 | for (int i = place(key);; i = (i + 1) & mask) { 163 | if (keyTable[i] == null) { 164 | keyTable[i] = key; 165 | valueTable[i] = value; 166 | return; 167 | } 168 | } 169 | } 170 | 171 | /** Returns the value for the specified key, or null if the key is not in the map. */ 172 | @Null 173 | public V get (T key) { 174 | int i = locateKey(key); 175 | return i < 0 ? null : valueTable[i]; 176 | } 177 | 178 | /** Returns the value for the specified key, or the default value if the key is not in the map. */ 179 | public V get (K key, @Null V defaultValue) { 180 | int i = locateKey(key); 181 | return i < 0 ? defaultValue : valueTable[i]; 182 | } 183 | 184 | @Null 185 | public V remove (K key) { 186 | int i = locateKey(key); 187 | if (i < 0) return null; 188 | K[] keyTable = this.keyTable; 189 | V[] valueTable = this.valueTable; 190 | V oldValue = valueTable[i]; 191 | int mask = this.mask, next = i + 1 & mask; 192 | while ((key = keyTable[next]) != null) { 193 | int placement = place(key); 194 | if ((next - placement & mask) > (i - placement & mask)) { 195 | keyTable[i] = key; 196 | valueTable[i] = valueTable[next]; 197 | i = next; 198 | } 199 | next = next + 1 & mask; 200 | } 201 | keyTable[i] = null; 202 | valueTable[i] = null; 203 | size--; 204 | return oldValue; 205 | } 206 | 207 | /** Returns true if the map has one or more items. */ 208 | public boolean notEmpty () { 209 | return size > 0; 210 | } 211 | 212 | /** Returns true if the map is empty. */ 213 | public boolean isEmpty () { 214 | return size == 0; 215 | } 216 | 217 | /** Reduces the size of the backing arrays to be the specified capacity / loadFactor, or less. If the capacity is already less, 218 | * nothing is done. If the map contains more items than the specified capacity, the next highest power of two capacity is used 219 | * instead. */ 220 | public void shrink (int maximumCapacity) { 221 | if (maximumCapacity < 0) throw new IllegalArgumentException("maximumCapacity must be >= 0: " + maximumCapacity); 222 | int tableSize = tableSize(maximumCapacity, loadFactor); 223 | if (keyTable.length > tableSize) resize(tableSize); 224 | } 225 | 226 | /** Clears the map and reduces the size of the backing arrays to be the specified capacity / loadFactor, if they are larger. */ 227 | public void clear (int maximumCapacity) { 228 | int tableSize = tableSize(maximumCapacity, loadFactor); 229 | if (keyTable.length <= tableSize) { 230 | clear(); 231 | return; 232 | } 233 | size = 0; 234 | resize(tableSize); 235 | } 236 | 237 | public void clear () { 238 | if (size == 0) return; 239 | size = 0; 240 | Arrays.fill(keyTable, null); 241 | Arrays.fill(valueTable, null); 242 | } 243 | 244 | /** Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may 245 | * be an expensive operation. 246 | * @param identity If true, uses == to compare the specified value with values in the map. If false, uses 247 | * {@link #equals(Object)}. */ 248 | public boolean containsValue (@Null Object value, boolean identity) { 249 | V[] valueTable = this.valueTable; 250 | if (value == null) { 251 | K[] keyTable = this.keyTable; 252 | for (int i = valueTable.length - 1; i >= 0; i--) 253 | if (keyTable[i] != null && valueTable[i] == null) return true; 254 | } else if (identity) { 255 | for (int i = valueTable.length - 1; i >= 0; i--) 256 | if (valueTable[i] == value) return true; 257 | } else { 258 | for (int i = valueTable.length - 1; i >= 0; i--) 259 | if (value.equals(valueTable[i])) return true; 260 | } 261 | return false; 262 | } 263 | 264 | public boolean containsKey (K key) { 265 | return locateKey(key) >= 0; 266 | } 267 | 268 | /** Returns the key for the specified value, or null if it is not in the map. Note this traverses the entire map and compares 269 | * every value, which may be an expensive operation. 270 | * @param identity If true, uses == to compare the specified value with values in the map. If false, uses 271 | * {@link #equals(Object)}. */ 272 | @Null 273 | public K findKey (@Null Object value, boolean identity) { 274 | V[] valueTable = this.valueTable; 275 | if (value == null) { 276 | K[] keyTable = this.keyTable; 277 | for (int i = valueTable.length - 1; i >= 0; i--) 278 | if (keyTable[i] != null && valueTable[i] == null) return keyTable[i]; 279 | } else if (identity) { 280 | for (int i = valueTable.length - 1; i >= 0; i--) 281 | if (valueTable[i] == value) return keyTable[i]; 282 | } else { 283 | for (int i = valueTable.length - 1; i >= 0; i--) 284 | if (value.equals(valueTable[i])) return keyTable[i]; 285 | } 286 | return null; 287 | } 288 | 289 | /** Increases the size of the backing array to accommodate the specified number of additional items / loadFactor. Useful before 290 | * adding many items to avoid multiple backing array resizes. */ 291 | public void ensureCapacity (int additionalCapacity) { 292 | int tableSize = tableSize(size + additionalCapacity, loadFactor); 293 | if (keyTable.length < tableSize) resize(tableSize); 294 | } 295 | 296 | final void resize (int newSize) { 297 | int oldCapacity = keyTable.length; 298 | threshold = (int)(newSize * loadFactor); 299 | mask = newSize - 1; 300 | shift = Long.numberOfLeadingZeros(mask); 301 | 302 | K[] oldKeyTable = keyTable; 303 | V[] oldValueTable = valueTable; 304 | 305 | keyTable = (K[])new Object[newSize]; 306 | valueTable = (V[])new Object[newSize]; 307 | 308 | if (size > 0) { 309 | for (int i = 0; i < oldCapacity; i++) { 310 | K key = oldKeyTable[i]; 311 | if (key != null) putResize(key, oldValueTable[i]); 312 | } 313 | } 314 | } 315 | 316 | public int hashCode () { 317 | int h = size; 318 | K[] keyTable = this.keyTable; 319 | V[] valueTable = this.valueTable; 320 | for (int i = 0, n = keyTable.length; i < n; i++) { 321 | K key = keyTable[i]; 322 | if (key != null) { 323 | h += key.hashCode(); 324 | V value = valueTable[i]; 325 | if (value != null) h += value.hashCode(); 326 | } 327 | } 328 | return h; 329 | } 330 | 331 | public boolean equals (Object obj) { 332 | if (obj == this) return true; 333 | if (!(obj instanceof ObjectMap)) return false; 334 | ObjectMap other = (ObjectMap)obj; 335 | if (other.size != size) return false; 336 | K[] keyTable = this.keyTable; 337 | V[] valueTable = this.valueTable; 338 | for (int i = 0, n = keyTable.length; i < n; i++) { 339 | K key = keyTable[i]; 340 | if (key != null) { 341 | V value = valueTable[i]; 342 | if (value == null) { 343 | if (other.get(key, dummy) != null) return false; 344 | } else { 345 | if (!value.equals(other.get(key))) return false; 346 | } 347 | } 348 | } 349 | return true; 350 | } 351 | 352 | /** Uses == for comparison of each value. */ 353 | public boolean equalsIdentity (@Null Object obj) { 354 | if (obj == this) return true; 355 | if (!(obj instanceof ObjectMap)) return false; 356 | ObjectMap other = (ObjectMap)obj; 357 | if (other.size != size) return false; 358 | K[] keyTable = this.keyTable; 359 | V[] valueTable = this.valueTable; 360 | for (int i = 0, n = keyTable.length; i < n; i++) { 361 | K key = keyTable[i]; 362 | if (key != null && valueTable[i] != other.get(key, dummy)) return false; 363 | } 364 | return true; 365 | } 366 | 367 | public String toString (String separator) { 368 | return toString(separator, false); 369 | } 370 | 371 | public String toString () { 372 | return toString(", ", true); 373 | } 374 | 375 | private String toString (String separator, boolean braces) { 376 | if (size == 0) return braces ? "{}" : ""; 377 | java.lang.StringBuilder buffer = new java.lang.StringBuilder(32); 378 | if (braces) buffer.append('{'); 379 | K[] keyTable = this.keyTable; 380 | V[] valueTable = this.valueTable; 381 | int i = keyTable.length; 382 | while (i-- > 0) { 383 | K key = keyTable[i]; 384 | if (key == null) continue; 385 | buffer.append(key == this ? "(this)" : key); 386 | buffer.append('='); 387 | V value = valueTable[i]; 388 | buffer.append(value == this ? "(this)" : value); 389 | break; 390 | } 391 | while (i-- > 0) { 392 | K key = keyTable[i]; 393 | if (key == null) continue; 394 | buffer.append(separator); 395 | buffer.append(key == this ? "(this)" : key); 396 | buffer.append('='); 397 | V value = valueTable[i]; 398 | buffer.append(value == this ? "(this)" : value); 399 | } 400 | if (braces) buffer.append('}'); 401 | return buffer.toString(); 402 | } 403 | 404 | public Entries iterator () { 405 | return entries(); 406 | } 407 | 408 | /** Returns an iterator for the entries in the map. Remove is supported. */ 409 | public Entries entries () { 410 | return new Entries(this); 411 | } 412 | 413 | /** Returns an iterator for the values in the map. Remove is supported. */ 414 | public Values values () { 415 | return new Values(this); 416 | } 417 | 418 | /** Returns an iterator for the keys in the map. Remove is supported. */ 419 | public Keys keys () { 420 | return new Keys(this); 421 | } 422 | 423 | static public int tableSize (int capacity, float loadFactor) { 424 | if (capacity < 0) throw new IllegalArgumentException("capacity must be >= 0: " + capacity); 425 | int tableSize = nextPowerOfTwo(Math.max(2, (int)Math.ceil(capacity / loadFactor))); 426 | if (tableSize > 1 << 30) throw new IllegalArgumentException("The required capacity is too large: " + capacity); 427 | return tableSize; 428 | } 429 | 430 | static public int nextPowerOfTwo (int value) { 431 | if (value == 0) return 1; 432 | value--; 433 | value |= value >> 1; 434 | value |= value >> 2; 435 | value |= value >> 4; 436 | value |= value >> 8; 437 | value |= value >> 16; 438 | return value + 1; 439 | } 440 | 441 | static public class Entry { 442 | public K key; 443 | @Null public V value; 444 | 445 | public String toString () { 446 | return key + "=" + value; 447 | } 448 | } 449 | 450 | static private abstract class MapIterator implements Iterable, Iterator { 451 | public boolean hasNext; 452 | 453 | final ObjectMap map; 454 | int nextIndex, currentIndex; 455 | 456 | public MapIterator (ObjectMap map) { 457 | this.map = map; 458 | reset(); 459 | } 460 | 461 | public void reset () { 462 | currentIndex = -1; 463 | nextIndex = -1; 464 | findNextIndex(); 465 | } 466 | 467 | void findNextIndex () { 468 | K[] keyTable = map.keyTable; 469 | for (int n = keyTable.length; ++nextIndex < n;) { 470 | if (keyTable[nextIndex] != null) { 471 | hasNext = true; 472 | return; 473 | } 474 | } 475 | hasNext = false; 476 | } 477 | 478 | public void remove () { 479 | int i = currentIndex; 480 | if (i < 0) throw new IllegalStateException("next must be called before remove."); 481 | K[] keyTable = map.keyTable; 482 | V[] valueTable = map.valueTable; 483 | int mask = map.mask, next = i + 1 & mask; 484 | K key; 485 | while ((key = keyTable[next]) != null) { 486 | int placement = map.place(key); 487 | if ((next - placement & mask) > (i - placement & mask)) { 488 | keyTable[i] = key; 489 | valueTable[i] = valueTable[next]; 490 | i = next; 491 | } 492 | next = next + 1 & mask; 493 | } 494 | keyTable[i] = null; 495 | valueTable[i] = null; 496 | map.size--; 497 | if (i != currentIndex) --nextIndex; 498 | currentIndex = -1; 499 | } 500 | } 501 | 502 | static public class Entries extends MapIterator> { 503 | Entry entry = new Entry(); 504 | 505 | public Entries (ObjectMap map) { 506 | super(map); 507 | } 508 | 509 | /** Note the same entry instance is returned each time this method is called. */ 510 | public Entry next () { 511 | if (!hasNext) throw new NoSuchElementException(); 512 | K[] keyTable = map.keyTable; 513 | entry.key = keyTable[nextIndex]; 514 | entry.value = map.valueTable[nextIndex]; 515 | currentIndex = nextIndex; 516 | findNextIndex(); 517 | return entry; 518 | } 519 | 520 | public boolean hasNext () { 521 | return hasNext; 522 | } 523 | 524 | public Entries iterator () { 525 | return this; 526 | } 527 | } 528 | 529 | static public class Values extends MapIterator { 530 | public Values (ObjectMap map) { 531 | super((ObjectMap)map); 532 | } 533 | 534 | public boolean hasNext () { 535 | return hasNext; 536 | } 537 | 538 | @Null 539 | public V next () { 540 | if (!hasNext) throw new NoSuchElementException(); 541 | V value = map.valueTable[nextIndex]; 542 | currentIndex = nextIndex; 543 | findNextIndex(); 544 | return value; 545 | } 546 | 547 | public Values iterator () { 548 | return this; 549 | } 550 | 551 | /** Returns a new list containing the remaining keys. */ 552 | public ArrayList toList () { 553 | return toList(new ArrayList(map.size)); 554 | } 555 | 556 | /** Adds the remaining keys to the list. */ 557 | public > T toList (T array) { 558 | while (hasNext) 559 | array.add(next()); 560 | return array; 561 | } 562 | } 563 | 564 | static public class Keys extends MapIterator { 565 | public Keys (ObjectMap map) { 566 | super((ObjectMap)map); 567 | } 568 | 569 | public boolean hasNext () { 570 | return hasNext; 571 | } 572 | 573 | public K next () { 574 | if (!hasNext) throw new NoSuchElementException(); 575 | K key = map.keyTable[nextIndex]; 576 | currentIndex = nextIndex; 577 | findNextIndex(); 578 | return key; 579 | } 580 | 581 | public Keys iterator () { 582 | return this; 583 | } 584 | 585 | /** Returns a new list containing the remaining keys. */ 586 | public ArrayList toList () { 587 | return toList(new ArrayList(map.size)); 588 | } 589 | 590 | /** Adds the remaining keys to the list. */ 591 | public > T toList (T array) { 592 | while (hasNext) 593 | array.add(next()); 594 | return array; 595 | } 596 | } 597 | } 598 | -------------------------------------------------------------------------------- /src/com/esotericsoftware/jsonbeans/OrderedMap.java: -------------------------------------------------------------------------------- 1 | 2 | package com.esotericsoftware.jsonbeans; 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.NoSuchElementException; 7 | 8 | /** An {@link ObjectMap} that also stores keys in an {@link ArrayList} using the insertion order. Null keys are not allowed. No 9 | * allocation is done except when growing the table size. 10 | *

11 | * Iteration over the {@link #entries()}, {@link #keys()}, and {@link #values()} is ordered and faster than an unordered map. Keys 12 | * can also be accessed and the order changed using {@link #orderedKeys()}. There is some additional overhead for put and remove. 13 | *

14 | * This class performs fast contains (typically O(1), worst case O(n) but that is rare in practice). Remove is somewhat slower due 15 | * to {@link #orderedKeys()}. Add may be slightly slower, depending on hash collisions. Hashcodes are rehashed to reduce 16 | * collisions and the need to resize. Load factors greater than 0.91 greatly increase the chances to resize to the next higher POT 17 | * size. 18 | *

19 | * Unordered sets and maps are not designed to provide especially fast iteration. Iteration is faster with OrderedSet and 20 | * OrderedMap. 21 | *

22 | * This implementation uses linear probing with the backward shift algorithm for removal. Hashcodes are rehashed using Fibonacci 23 | * hashing, instead of the more common power-of-two mask, to better distribute poor hashCodes (see Malte 25 | * Skarupke's blog post). Linear probing continues to work even when all hashCodes collide, just more slowly. 26 | * @author Nathan Sweet 27 | * @author Tommy Ettinger */ 28 | public class OrderedMap extends ObjectMap { 29 | final ArrayList keys; 30 | 31 | public OrderedMap () { 32 | keys = new ArrayList(); 33 | } 34 | 35 | public OrderedMap (int initialCapacity) { 36 | super(initialCapacity); 37 | keys = new ArrayList(initialCapacity); 38 | } 39 | 40 | public OrderedMap (int initialCapacity, float loadFactor) { 41 | super(initialCapacity, loadFactor); 42 | keys = new ArrayList(initialCapacity); 43 | } 44 | 45 | public OrderedMap (OrderedMap map) { 46 | super(map); 47 | keys = new ArrayList(map.keys); 48 | } 49 | 50 | public V put (K key, V value) { 51 | int i = locateKey(key); 52 | if (i >= 0) { // Existing key was found. 53 | V oldValue = valueTable[i]; 54 | valueTable[i] = value; 55 | return oldValue; 56 | } 57 | i = -(i + 1); // Empty space was found. 58 | keyTable[i] = key; 59 | valueTable[i] = value; 60 | keys.add(key); 61 | if (++size >= threshold) resize(keyTable.length << 1); 62 | return null; 63 | } 64 | 65 | public void putAll (OrderedMap map) { 66 | ensureCapacity(map.size); 67 | ArrayList keys = map.keys; 68 | for (int i = 0, n = map.keys.size(); i < n; i++) { 69 | K key = keys.get(i); 70 | put(key, map.get((T)key)); 71 | } 72 | } 73 | 74 | public V remove (K key) { 75 | keys.remove(key); 76 | return super.remove(key); 77 | } 78 | 79 | public V removeIndex (int index) { 80 | return super.remove(keys.remove(index)); 81 | } 82 | 83 | /** Changes the key {@code before} to {@code after} without changing its position in the order or its value. Returns true if 84 | * {@code after} has been added to the OrderedMap and {@code before} has been removed; returns false if {@code after} is 85 | * already present or {@code before} is not present. If you are iterating over an OrderedMap and have an index, you should 86 | * prefer {@link #alterIndex(int, Object)}, which doesn't need to search for an index like this does and so can be faster. 87 | * @param before a key that must be present for this to succeed 88 | * @param after a key that must not be in this map for this to succeed 89 | * @return true if {@code before} was removed and {@code after} was added, false otherwise */ 90 | public boolean alter (K before, K after) { 91 | if (containsKey(after)) return false; 92 | int index = keys.indexOf(before); 93 | if (index == -1) return false; 94 | super.put(after, super.remove(before)); 95 | keys.set(index, after); 96 | return true; 97 | } 98 | 99 | /** Changes the key at the given {@code index} in the order to {@code after}, without changing the ordering of other entries or 100 | * any values. If {@code after} is already present, this returns false; it will also return false if {@code index} is invalid 101 | * for the size of this map. Otherwise, it returns true. Unlike {@link #alter(Object, Object)}, this operates in constant time. 102 | * @param index the index in the order of the key to change; must be non-negative and less than {@link #size} 103 | * @param after the key that will replace the contents at {@code index}; this key must not be present for this to succeed 104 | * @return true if {@code after} successfully replaced the key at {@code index}, false otherwise */ 105 | public boolean alterIndex (int index, K after) { 106 | if (index < 0 || index >= size || containsKey(after)) return false; 107 | super.put(after, super.remove(keys.get(index))); 108 | keys.set(index, after); 109 | return true; 110 | } 111 | 112 | public void clear (int maximumCapacity) { 113 | keys.clear(); 114 | super.clear(maximumCapacity); 115 | } 116 | 117 | public void clear () { 118 | keys.clear(); 119 | super.clear(); 120 | } 121 | 122 | public ArrayList orderedKeys () { 123 | return keys; 124 | } 125 | 126 | public Entries iterator () { 127 | return entries(); 128 | } 129 | 130 | /** Returns an iterator for the entries in the map. Remove is supported. */ 131 | public Entries entries () { 132 | return new OrderedMapEntries(this); 133 | } 134 | 135 | /** Returns an iterator for the values in the map. Remove is supported. */ 136 | public Values values () { 137 | return new OrderedMapValues(this); 138 | } 139 | 140 | /** Returns an iterator for the keys in the map. Remove is supported. */ 141 | public Keys keys () { 142 | return new OrderedMapKeys(this); 143 | } 144 | 145 | public String toString () { 146 | if (size == 0) return "{}"; 147 | java.lang.StringBuilder buffer = new java.lang.StringBuilder(32); 148 | buffer.append('{'); 149 | ArrayList keys = this.keys; 150 | for (int i = 0, n = keys.size(); i < n; i++) { 151 | K key = keys.get(i); 152 | if (i > 0) buffer.append(", "); 153 | buffer.append(key); 154 | buffer.append('='); 155 | buffer.append(get(key)); 156 | } 157 | buffer.append('}'); 158 | return buffer.toString(); 159 | } 160 | 161 | static public class OrderedMapEntries extends Entries { 162 | private ArrayList keys; 163 | 164 | public OrderedMapEntries (OrderedMap map) { 165 | super(map); 166 | keys = map.keys; 167 | } 168 | 169 | public void reset () { 170 | currentIndex = -1; 171 | nextIndex = 0; 172 | hasNext = map.size > 0; 173 | } 174 | 175 | public Entry next () { 176 | if (!hasNext) throw new NoSuchElementException(); 177 | currentIndex = nextIndex; 178 | entry.key = keys.get(nextIndex); 179 | entry.value = map.get(entry.key); 180 | nextIndex++; 181 | hasNext = nextIndex < map.size; 182 | return entry; 183 | } 184 | 185 | public void remove () { 186 | if (currentIndex < 0) throw new IllegalStateException("next must be called before remove."); 187 | map.remove(entry.key); 188 | nextIndex--; 189 | currentIndex = -1; 190 | } 191 | } 192 | 193 | static public class OrderedMapKeys extends Keys { 194 | private ArrayList keys; 195 | 196 | public OrderedMapKeys (OrderedMap map) { 197 | super(map); 198 | keys = map.keys; 199 | } 200 | 201 | public void reset () { 202 | currentIndex = -1; 203 | nextIndex = 0; 204 | hasNext = map.size > 0; 205 | } 206 | 207 | public K next () { 208 | if (!hasNext) throw new NoSuchElementException(); 209 | K key = keys.get(nextIndex); 210 | currentIndex = nextIndex; 211 | nextIndex++; 212 | hasNext = nextIndex < map.size; 213 | return key; 214 | } 215 | 216 | public void remove () { 217 | if (currentIndex < 0) throw new IllegalStateException("next must be called before remove."); 218 | ((OrderedMap)map).removeIndex(currentIndex); 219 | nextIndex = currentIndex; 220 | currentIndex = -1; 221 | } 222 | } 223 | 224 | static public class OrderedMapValues extends Values { 225 | private ArrayList keys; 226 | 227 | public OrderedMapValues (OrderedMap map) { 228 | super(map); 229 | keys = map.keys; 230 | } 231 | 232 | public void reset () { 233 | currentIndex = -1; 234 | nextIndex = 0; 235 | hasNext = map.size > 0; 236 | } 237 | 238 | public V next () { 239 | if (!hasNext) throw new NoSuchElementException(); 240 | V value = map.get(keys.get(nextIndex)); 241 | currentIndex = nextIndex; 242 | nextIndex++; 243 | hasNext = nextIndex < map.size; 244 | return value; 245 | } 246 | 247 | public void remove () { 248 | if (currentIndex < 0) throw new IllegalStateException("next must be called before remove."); 249 | ((OrderedMap)map).removeIndex(currentIndex); 250 | nextIndex = currentIndex; 251 | currentIndex = -1; 252 | } 253 | 254 | public > T toList (T array) { 255 | int n = keys.size(); 256 | for (int i = nextIndex; i < n; i++) 257 | array.add(map.get(keys.get(i))); 258 | currentIndex = n - 1; 259 | nextIndex = n; 260 | hasNext = false; 261 | return array; 262 | } 263 | 264 | public ArrayList toList () { 265 | return toList(new ArrayList(keys.size() - nextIndex)); 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/com/esotericsoftware/jsonbeans/OutputType.java: -------------------------------------------------------------------------------- 1 | 2 | package com.esotericsoftware.jsonbeans; 3 | 4 | import java.util.regex.Pattern; 5 | 6 | public enum OutputType { 7 | /** Normal JSON, with all its double quotes. */ 8 | json, 9 | /** Like JSON, but names are only double quoted if necessary. */ 10 | javascript, 11 | /** Like JSON, but: 12 | *

    13 | *
  • Names only require double quotes if they start with space or any of ":,}/ or they contain 14 | * // or /* or :. 15 | *
  • Values only require double quotes if they start with space or any of ":,{[]/ or they contain 16 | * // or /* or any of }], or they are equal to true, false , 17 | * or null. 18 | *
  • Newlines are treated as commas, making commas optional in many cases. 19 | *
  • C style comments may be used: //... or /*...*/ 20 | *
21 | */ 22 | minimal; 23 | 24 | static private Pattern javascriptPattern = Pattern.compile("^[a-zA-Z_$][a-zA-Z_$0-9]*$"); 25 | static private Pattern minimalNamePattern = Pattern.compile("^[^\":,}/ ][^:]*$"); 26 | static private Pattern minimalValuePattern = Pattern.compile("^[^\":,{\\[\\]/ ][^}\\],]*$"); 27 | 28 | public String quoteValue (Object value) { 29 | if (value == null) return "null"; 30 | String string = value.toString(); 31 | if (value instanceof Number || value instanceof Boolean) return string; 32 | StringBuilder buffer = new StringBuilder(string); 33 | replace(buffer, '\\', "\\\\"); 34 | replace(buffer, '\r', "\\r"); 35 | replace(buffer, '\n', "\\n"); 36 | replace(buffer, '\t', "\\t"); 37 | if (this == OutputType.minimal && !string.equals("true") && !string.equals("false") && !string.equals("null") 38 | && !string.contains("//") && !string.contains("/*")) { 39 | int length = buffer.length(); 40 | if (length > 0 && buffer.charAt(length - 1) != ' ' && minimalValuePattern.matcher(buffer).matches()) 41 | return buffer.toString(); 42 | } 43 | return '"' + replace(buffer, '"', "\\\"").toString() + '"'; 44 | } 45 | 46 | public String quoteName (String value) { 47 | StringBuilder buffer = new StringBuilder(value); 48 | replace(buffer, '\\', "\\\\"); 49 | replace(buffer, '\r', "\\r"); 50 | replace(buffer, '\n', "\\n"); 51 | replace(buffer, '\t', "\\t"); 52 | switch (this) { 53 | case minimal: 54 | if (!value.contains("//") && !value.contains("/*") && minimalNamePattern.matcher(buffer).matches()) 55 | return buffer.toString(); 56 | case javascript: 57 | if (javascriptPattern.matcher(buffer).matches()) return buffer.toString(); 58 | } 59 | return '"' + replace(buffer, '"', "\\\"").toString() + '"'; 60 | } 61 | 62 | static private StringBuilder replace (StringBuilder buffer, char find, String replace) { 63 | int replaceLength = replace.length(); 64 | int index = 0; 65 | while (true) { 66 | while (true) { 67 | if (index == buffer.length()) return buffer; 68 | if (buffer.charAt(index) == find) break; 69 | index++; 70 | } 71 | buffer.replace(index, index + 1, replace); 72 | index += replaceLength; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/com/esotericsoftware/jsonbeans/ReadOnlySerializer.java: -------------------------------------------------------------------------------- 1 | 2 | package com.esotericsoftware.jsonbeans; 3 | 4 | public abstract class ReadOnlySerializer implements JsonSerializer { 5 | public void write (Json json, T object, Class knownType) { 6 | } 7 | 8 | abstract public T read (Json json, JsonValue jsonData, Class type); 9 | } 10 | --------------------------------------------------------------------------------